diff --git a/API.en.md b/API.en.md new file mode 100644 index 0000000..6487f45 --- /dev/null +++ b/API.en.md @@ -0,0 +1,701 @@ +# DS2API API Reference + +Language: [中文](API.md) | [English](API.en.md) + +This document describes all DS2API API endpoints. + +--- + +## Table of Contents + +- [Basics](#basics) +- [OpenAI-Compatible API](#openai-compatible-api) + - [List Models](#list-models) + - [Chat Completions](#chat-completions) +- [Claude-Compatible API](#claude-compatible-api) + - [Claude Model List](#claude-model-list) + - [Claude Messages](#claude-messages) + - [Token Counting](#token-counting) +- [Admin API](#admin-api) + - [Login](#login) + - [Configuration](#configuration) + - [Account Management](#account-management) + - [Vercel Sync](#vercel-sync) +- [Error Handling](#error-handling) +- [Examples](#examples) + +--- + +## Basics + +| Item | Description | +|-----|------| +| **Base URL** | `https://your-domain.com` or `http://localhost:5001` | +| **OpenAI auth** | `Authorization: Bearer ` | +| **Claude auth** | `x-api-key: ` | +| **Response format** | JSON | + +--- + +## OpenAI-Compatible API + +### List Models + +```http +GET /v1/models +``` + +**Response example**: + +```json +{ + "object": "list", + "data": [ + {"id": "deepseek-chat", "object": "model", "owned_by": "deepseek"}, + {"id": "deepseek-reasoner", "object": "model", "owned_by": "deepseek"}, + {"id": "deepseek-chat-search", "object": "model", "owned_by": "deepseek"}, + {"id": "deepseek-reasoner-search", "object": "model", "owned_by": "deepseek"} + ] +} +``` + +--- + +### Chat Completions + +```http +POST /v1/chat/completions +Authorization: Bearer your-api-key +Content-Type: application/json +``` + +**Parameters**: + +| Parameter | Type | Required | Description | +|-----|------|:----:|------| +| `model` | string | ✅ | Model name (see below) | +| `messages` | array | ✅ | Chat messages | +| `stream` | boolean | ❌ | Stream responses (default `false`) | +| `temperature` | number | ❌ | Temperature (0-2) | +| `max_tokens` | number | ❌ | Max output tokens | +| `tools` | array | ❌ | Tool definitions (Function Calling) | +| `tool_choice` | string | ❌ | Tool selection strategy | + +**Supported models**: + +| Model | Reasoning | Search | Notes | +|-----|:--------:|:------:|------| +| `deepseek-chat` | ❌ | ❌ | Standard chat | +| `deepseek-reasoner` | ✅ | ❌ | Reasoning mode with trace | +| `deepseek-chat-search` | ❌ | ✅ | Search enhanced | +| `deepseek-reasoner-search` | ✅ | ✅ | Reasoning + search | + +**Basic request example**: + +```json +{ + "model": "deepseek-chat", + "messages": [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Hello"} + ] +} +``` + +**Streaming request example**: + +```json +{ + "model": "deepseek-reasoner-search", + "messages": [ + {"role": "user", "content": "What's in the news today?"} + ], + "stream": true +} +``` + +**Streaming response format** (`stream: true`): + +``` +data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"role":"assistant"},"index":0}]} + +data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"reasoning_content":"Let me think..."},"index":0}]} + +data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"content":"Based on search results..."},"index":0}]} + +data: {"id":"...","object":"chat.completion.chunk","choices":[{"index":0,"finish_reason":"stop"}]} + +data: [DONE] +``` + +> **Note**: Reasoning models emit `reasoning_content` with the trace. + +**Non-streaming response format** (`stream: false`): + +```json +{ + "id": "chatcmpl-xxx", + "object": "chat.completion", + "created": 1738400000, + "model": "deepseek-reasoner", + "choices": [{ + "index": 0, + "message": { + "role": "assistant", + "content": "Response text", + "reasoning_content": "Reasoning trace (reasoner only)" + }, + "finish_reason": "stop" + }], + "usage": { + "prompt_tokens": 10, + "completion_tokens": 50, + "total_tokens": 60, + "completion_tokens_details": { + "reasoning_tokens": 20 + } + } +} +``` + +#### Tool Calling (Function Calling) + +**Request example**: + +```json +{ + "model": "deepseek-chat", + "messages": [{"role": "user", "content": "What's the weather in Beijing?"}], + "tools": [{ + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the weather for a city", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string", "description": "City name"} + }, + "required": ["location"] + } + } + }] +} +``` + +**Response example**: + +```json +{ + "id": "chatcmpl-xxx", + "object": "chat.completion", + "choices": [{ + "index": 0, + "message": { + "role": "assistant", + "content": null, + "tool_calls": [{ + "id": "call_xxx", + "type": "function", + "function": { + "name": "get_weather", + "arguments": "{\"location\": \"Beijing\"}" + } + }] + }, + "finish_reason": "tool_calls" + }] +} +``` + +--- + +## Claude-Compatible API + +### Claude Model List + +```http +GET /anthropic/v1/models +``` + +**Response example**: + +```json +{ + "object": "list", + "data": [ + {"id": "claude-sonnet-4-20250514", "object": "model", "owned_by": "anthropic"}, + {"id": "claude-sonnet-4-20250514-fast", "object": "model", "owned_by": "anthropic"}, + {"id": "claude-sonnet-4-20250514-slow", "object": "model", "owned_by": "anthropic"} + ] +} +``` + +**Model mapping**: + +| Claude Model | Actual | Notes | +|------------|--------|------| +| `claude-sonnet-4-20250514` | deepseek-chat | Standard mode | +| `claude-sonnet-4-20250514-fast` | deepseek-chat | Fast mode | +| `claude-sonnet-4-20250514-slow` | deepseek-reasoner | Reasoning mode | + +--- + +### Claude Messages + +```http +POST /anthropic/v1/messages +x-api-key: your-api-key +Content-Type: application/json +anthropic-version: 2023-06-01 +``` + +**Parameters**: + +| Parameter | Type | Required | Description | +|-----|------|:----:|------| +| `model` | string | ✅ | Model name | +| `max_tokens` | integer | ✅ | Max output tokens | +| `messages` | array | ✅ | Chat messages | +| `stream` | boolean | ❌ | Stream responses (default `false`) | +| `system` | string | ❌ | System prompt | +| `temperature` | number | ❌ | Temperature | + +**Request example**: + +```json +{ + "model": "claude-sonnet-4-20250514", + "max_tokens": 1024, + "messages": [ + {"role": "user", "content": "Hello, please introduce yourself."} + ] +} +``` + +**Non-streaming response**: + +```json +{ + "id": "msg_xxx", + "type": "message", + "role": "assistant", + "content": [{ + "type": "text", + "text": "Hello! I'm an AI assistant..." + }], + "model": "claude-sonnet-4-20250514", + "stop_reason": "end_turn", + "usage": { + "input_tokens": 10, + "output_tokens": 50 + } +} +``` + +**Streaming response** (SSE): + +``` +event: message_start +data: {"type":"message_start","message":{"id":"msg_xxx","type":"message","role":"assistant","model":"claude-sonnet-4-20250514"}} + +event: content_block_start +data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}} + +event: content_block_delta +data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}} + +event: content_block_stop +data: {"type":"content_block_stop","index":0} + +event: message_delta +data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":50}} + +event: message_stop +data: {"type":"message_stop"} +``` + +--- + +### Token Counting + +```http +POST /anthropic/v1/messages/count_tokens +x-api-key: your-api-key +Content-Type: application/json +``` + +**Request example**: + +```json +{ + "model": "claude-sonnet-4-20250514", + "messages": [ + {"role": "user", "content": "Hello"} + ] +} +``` + +**Response example**: + +```json +{ + "input_tokens": 5 +} +``` + +--- + +## Admin API + +All admin endpoints (except login) require `Authorization: Bearer `. + +### Login + +```http +POST /admin/login +Content-Type: application/json +``` + +**Request body**: + +```json +{ + "key": "your-admin-key" +} +``` + +**Response**: + +```json +{ + "success": true, + "token": "jwt-token-string", + "expires_in": 86400 +} +``` + +> Tokens are valid for 24 hours by default. + +--- + +### Configuration + +#### Get configuration + +```http +GET /admin/config +Authorization: Bearer +``` + +**Response**: + +```json +{ + "keys": ["api-key-1", "api-key-2"], + "accounts": [ + { + "email": "user@example.com", + "password": "***", + "token": "session-token" + } + ] +} +``` + +#### Update configuration + +```http +POST /admin/config +Authorization: Bearer +Content-Type: application/json +``` + +**Request body**: + +```json +{ + "keys": ["new-api-key"], + "accounts": [...] +} +``` + +--- + +### Account Management + +#### Add account + +```http +POST /admin/accounts +Authorization: Bearer +Content-Type: application/json +``` + +**Request body**: + +```json +{ + "email": "user@example.com", + "password": "password123" +} +``` + +#### Batch import accounts + +```http +POST /admin/accounts/batch +Authorization: Bearer +Content-Type: application/json +``` + +**Request body**: + +```json +{ + "accounts": [ + {"email": "user1@example.com", "password": "pass1"}, + {"email": "user2@example.com", "password": "pass2"} + ] +} +``` + +#### Test one account + +```http +POST /admin/accounts/test +Authorization: Bearer +Content-Type: application/json +``` + +**Request body**: + +```json +{ + "email": "user@example.com" +} +``` + +#### Test all accounts + +```http +POST /admin/accounts/test-all +Authorization: Bearer +``` + +#### Queue status + +```http +GET /admin/queue/status +Authorization: Bearer +``` + +**Response**: + +```json +{ + "total_accounts": 5, + "healthy_accounts": 4, + "queue_size": 10, + "accounts": [ + { + "email": "user@example.com", + "status": "healthy", + "last_used": "2026-02-01T12:00:00Z" + } + ] +} +``` + +--- + +### Vercel Sync + +```http +POST /admin/vercel/sync +Authorization: Bearer +Content-Type: application/json +``` + +**Request body** (first sync only): + +```json +{ + "vercel_token": "your-vercel-token", + "project_id": "your-project-id" +} +``` + +> After a successful first sync, credentials are stored for future syncs. + +**Response**: + +```json +{ + "success": true, + "message": "Configuration synced to Vercel" +} +``` + +--- + +## Error Handling + +All error responses follow this structure: + +```json +{ + "error": { + "message": "Error description", + "type": "error_type", + "code": "error_code" + } +} +``` + +**Common error codes**: + +| HTTP Status | Error Type | Description | +|:----------:|---------|------| +| 400 | `invalid_request_error` | Invalid request parameters | +| 401 | `authentication_error` | Missing or invalid API key | +| 403 | `permission_denied` | Insufficient permissions | +| 429 | `rate_limit_error` | Too many requests | +| 500 | `internal_error` | Internal server error | +| 503 | `service_unavailable` | No available accounts | + +--- + +## Examples + +### Python (OpenAI SDK) + +```python +from openai import OpenAI + +client = OpenAI( + api_key="your-api-key", + base_url="https://your-domain.com/v1" +) + +# Basic chat +response = client.chat.completions.create( + model="deepseek-chat", + messages=[{"role": "user", "content": "Hello"}] +) +print(response.choices[0].message.content) + +# Streaming + reasoning +for chunk in client.chat.completions.create( + model="deepseek-reasoner", + messages=[{"role": "user", "content": "Explain relativity"}], + stream=True +): + delta = chunk.choices[0].delta + if hasattr(delta, 'reasoning_content') and delta.reasoning_content: + print(f"[Reasoning] {delta.reasoning_content}", end="") + if delta.content: + print(delta.content, end="") +``` + +### Python (Anthropic SDK) + +```python +import anthropic + +client = anthropic.Anthropic( + api_key="your-api-key", + base_url="https://your-domain.com/anthropic" +) + +response = client.messages.create( + model="claude-sonnet-4-20250514", + max_tokens=1024, + messages=[{"role": "user", "content": "Hello"}] +) +print(response.content[0].text) +``` + +### cURL + +```bash +# OpenAI format +curl https://your-domain.com/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer your-api-key" \ + -d '{ + "model": "deepseek-chat", + "messages": [{"role": "user", "content": "Hello"}] + }' + +# Claude format +curl https://your-domain.com/anthropic/v1/messages \ + -H "Content-Type: application/json" \ + -H "x-api-key: your-api-key" \ + -H "anthropic-version: 2023-06-01" \ + -d '{ + "model": "claude-sonnet-4-20250514", + "max_tokens": 1024, + "messages": [{"role": "user", "content": "Hello"}] + }' +``` + +### JavaScript / TypeScript + +```javascript +// OpenAI format - streaming request +const response = await fetch('https://your-domain.com/v1/chat/completions', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer your-api-key' + }, + body: JSON.stringify({ + model: 'deepseek-chat-search', + messages: [{ role: 'user', content: 'What is in the news today?' }], + stream: true + }) +}); + +const reader = response.body.getReader(); +const decoder = new TextDecoder(); + +while (true) { + const { done, value } = await reader.read(); + if (done) break; + + const chunk = decoder.decode(value); + const lines = chunk.split('\n').filter(line => line.startsWith('data: ')); + + for (const line of lines) { + const data = line.slice(6); + if (data === '[DONE]') continue; + + const json = JSON.parse(data); + const content = json.choices?.[0]?.delta?.content; + if (content) process.stdout.write(content); + } +} +``` + +### Node.js (OpenAI SDK) + +```javascript +import OpenAI from 'openai'; + +const client = new OpenAI({ + apiKey: 'your-api-key', + baseURL: 'https://your-domain.com/v1' +}); + +const stream = await client.chat.completions.create({ + model: 'deepseek-reasoner', + messages: [{ role: 'user', content: 'Explain black holes' }], + stream: true +}); + +for await (const chunk of stream) { + const content = chunk.choices[0]?.delta?.content; + if (content) process.stdout.write(content); +} +``` diff --git a/API.md b/API.md index a5e73db..58b22fa 100644 --- a/API.md +++ b/API.md @@ -1,5 +1,7 @@ # DS2API 接口文档 +语言 / Language: [中文](API.md) | [English](API.en.md) + 本文档详细介绍 DS2API 提供的所有 API 端点。 --- diff --git a/CONTRIBUTING.en.md b/CONTRIBUTING.en.md new file mode 100644 index 0000000..e8b8c7f --- /dev/null +++ b/CONTRIBUTING.en.md @@ -0,0 +1,94 @@ +# Contributing Guide + +Language: [中文](CONTRIBUTING.md) | [English](CONTRIBUTING.en.md) + +Thank you for contributing to DS2API! + +## Development Setup + +### Backend + +```bash +# 1. Clone the repo +git clone https://github.com/CJackHwang/ds2api.git +cd ds2api + +# 2. Create a virtual environment (recommended) +python -m venv venv +source venv/bin/activate # Windows: venv\Scripts\activate + +# 3. Install dependencies +pip install -r requirements.txt + +# 4. Configure +cp config.example.json config.json +# Edit config.json + +# 5. Run +python dev.py +``` + +### Frontend (WebUI) + +```bash +cd webui +npm install +npm run dev +``` + +WebUI language packs live in `webui/src/locales/`. Add new locale JSON files there. + +## Code Standards + +- **Python**: Follow PEP 8, use 4-space indentation +- **JavaScript/React**: Use 4-space indentation and function components +- **Commit messages**: Use semantic prefixes (e.g. `feat:`, `fix:`, `docs:`) + +## Submitting a PR + +1. Fork this repo +2. Create a feature branch (`git checkout -b feature/xxx`) +3. Commit your changes (`git commit -m 'feat: add xxx'`) +4. Push your branch (`git push origin feature/xxx`) +5. Open a Pull Request + +## WebUI Build + +> **Important**: After modifying `webui/`, **no manual build is required**. + +When a PR is merged into `main`, GitHub Actions will automatically: +1. Build the WebUI +2. Commit build artifacts to `static/admin/` + +If you need a local build (for testing): +```bash +./scripts/build-webui.sh +``` + +## Project Structure + +``` +ds2api/ +├── app.py # FastAPI entrypoint +├── dev.py # Development server +├── core/ # Core modules +│ ├── auth.py # Account auth & rotation +│ ├── config.py # Configuration management +│ ├── deepseek.py # DeepSeek API calls +│ ├── models.py # Model definitions +│ ├── pow.py # PoW calculations +│ └── sse_parser.py # SSE parsing +├── routes/ # API routes +│ ├── openai.py # OpenAI-compatible endpoints +│ ├── claude.py # Claude-compatible endpoints +│ ├── home.py # Landing page routes +│ └── admin/ # Admin endpoints +├── webui/ # React WebUI source +├── static/admin/ # WebUI build output (auto-generated) +└── scripts/ # Helper scripts +``` + +## Reporting Issues + +- Use [GitHub Issues](https://github.com/CJackHwang/ds2api/issues) +- Provide detailed reproduction steps and logs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4cb7f71..f35dcc8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,7 @@ # 贡献指南 +语言 / Language: [中文](CONTRIBUTING.md) | [English](CONTRIBUTING.en.md) + 感谢你对 DS2API 的贡献! ## 开发环境设置 @@ -34,6 +36,8 @@ npm install npm run dev ``` +WebUI 语言包位于 `webui/src/locales/`,新增语言请在此处添加对应 JSON 文件。 + ## 代码规范 - **Python**: 遵循 PEP 8,使用 4 空格缩进 diff --git a/DEPLOY.en.md b/DEPLOY.en.md new file mode 100644 index 0000000..69f3f54 --- /dev/null +++ b/DEPLOY.en.md @@ -0,0 +1,410 @@ +# DS2API Deployment Guide + +Language: [中文](DEPLOY.md) | [English](DEPLOY.en.md) + +This document covers all supported DS2API deployment methods. + +--- + +## Table of Contents + +- [Vercel Deployment (Recommended)](#vercel-deployment-recommended) +- [Docker Deployment (Recommended)](#docker-deployment-recommended) +- [Local Development](#local-development) +- [Production Deployment](#production-deployment) +- [FAQ](#faq) + +--- + +## Vercel Deployment (Recommended) + +### One-click deployment + +[![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=Admin%20console%20access%20key%20%28required%29&envLink=https%3A%2F%2Fgithub.com%2FCJackHwang%2Fds2api%23environment-variables&project-name=ds2api&repository-name=ds2api) + +### Steps + +1. **Click the deploy button** + - Sign in to GitHub + - Authorize Vercel access + +2. **Set environment variables** + - `DS2API_ADMIN_KEY`: Admin console password (**required**) + +3. **Wait for deployment** + - Vercel builds and deploys automatically + - You will receive a deployment URL + +4. **Configure accounts** + - Visit `https://your-project.vercel.app/admin` + - Log in with the admin key + - Add DeepSeek accounts + - Set custom API keys + +5. **Sync configuration** + - Click "Sync to Vercel" + - The first sync requires a Vercel token and project ID + - After sync, the configuration is persisted + +### Get Vercel credentials + +**Vercel token**: +1. Visit https://vercel.com/account/tokens +2. Click "Create Token" +3. Set a name and expiration +4. Copy the token + +**Project ID**: +1. Open your Vercel project +2. Go to Settings → General +3. Copy the "Project ID" + +--- + +## Local Development + +### Requirements + +- Python 3.9+ +- Node.js 18+ (WebUI development) +- pip + +### Quick start + +```bash +# 1. Clone the repo +git clone https://github.com/CJackHwang/ds2api.git +cd ds2api + +# 2. Install Python dependencies +pip install -r requirements.txt + +# 3. Configure accounts +cp config.example.json config.json +# Edit config.json and fill in DeepSeek account info + +# 4. Start the service +python dev.py +``` + +### Config example + +```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": "" + } + ] +} +``` + +**Notes**: +- `keys`: Custom API keys for calling the service +- `accounts`: DeepSeek Web accounts + - Supports `email` or `mobile` login + - Leave `token` blank; it will be fetched automatically + +### WebUI development + +```bash +# Enter the WebUI directory +cd webui + +# Install dependencies +npm install + +# Start the dev server +npm run dev +``` + +The WebUI dev server runs on `http://localhost:5173` and proxies API requests to `http://localhost:5001`. + +### WebUI build + +Build artifacts are located in `static/admin/`. + +**Automatic build (recommended)**: +- Vercel builds the WebUI during deployment (see `vercel.json` `buildCommand`) +- The GitHub Actions WebUI build workflow is disabled +- `static/admin/` build artifacts are no longer committed + +**Manual build**: +```bash +# Option 1: use script +./scripts/build-webui.sh + +# Option 2: run directly +cd webui +npm install +npm run build +``` + +> **Contributor note**: No manual build is required after modifying WebUI; Vercel deploys will build it automatically. + +--- + +## Docker Deployment (Recommended) + +Docker uses a **non-invasive, decoupled design**: +- Dockerfile executes standard Python steps and avoids hardcoded project configs +- WebUI is built during image build (for non-Vercel deployments) +- Configuration lives in environment variables and `.env` +- **Rebuild the image to update code without touching Docker config** + +### Quick start (Docker Compose) + +```bash +# 1. Copy the environment template +cp .env.example .env +# Edit .env with DS2API_ADMIN_KEY and DS2API_CONFIG_JSON + +# 2. Start the service +docker-compose up -d + +# 3. Check logs +docker-compose logs -f + +# 4. Rebuild after code updates +docker-compose up -d --build +``` + +### Mount a config file + +To use `config.json` instead of environment variables: + +```yaml +# docker-compose.yml +services: + ds2api: + build: . + ports: + - "5001:5001" + environment: + - DS2API_ADMIN_KEY=your-admin-key + volumes: + - ./config.json:/app/config.json:ro + restart: unless-stopped +``` + +### Docker CLI deployment + +```bash +# Build the image +docker build -t ds2api:latest . + +# Run with env variables +docker run -d \ + --name ds2api \ + -p 5001:5001 \ + -e DS2API_ADMIN_KEY=your-admin-key \ + -e DS2API_CONFIG_JSON='{"keys":["api-key"],"accounts":[...]}' \ + --restart unless-stopped \ + ds2api:latest + +# Or mount a config file +docker run -d \ + --name ds2api \ + -p 5001:5001 \ + -e DS2API_ADMIN_KEY=your-admin-key \ + -v $(pwd)/config.json:/app/config.json:ro \ + --restart unless-stopped \ + ds2api:latest +``` + +### Development mode (hot reload) + +```bash +# Use the dev compose file to enable hot reload +docker-compose -f docker-compose.dev.yml up +``` + +Development mode: +- Source code is mounted into the container +- Log level set to DEBUG +- Reads local `config.json` + +### Maintenance commands + +```bash +# Check container status +docker-compose ps + +# View logs +docker-compose logs -f ds2api + +# Restart +docker-compose restart + +# Stop +docker-compose down + +# Full rebuild (clear cache) +docker-compose down +docker-compose build --no-cache +docker-compose up -d +``` + +--- + +## Production Deployment + +### Using systemd (Linux) + +1. **Create the service file** + +```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. **Start the service** + +```bash +sudo systemctl daemon-reload +sudo systemctl enable ds2api +sudo systemctl start ds2api +``` + +3. **Check status** + +```bash +sudo systemctl status ds2api +sudo journalctl -u ds2api -f +``` + +### Nginx reverse proxy + +```nginx +server { + listen 80; + server_name api.yourdomain.com; + + # SSL configuration (recommended) + # 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; + + # Disable buffering for SSE + proxy_buffering off; + proxy_cache off; + + # Connection settings + 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 timeouts + proxy_read_timeout 300s; + proxy_send_timeout 300s; + + # Chunked transfer + chunked_transfer_encoding on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 120; + } +} +``` + +--- + +## FAQ + +### Q: What if account validation fails? + +**A**: Check the following: +1. Confirm the DeepSeek account password is correct +2. Ensure the account is not banned or requires verification +3. Log in once in a browser +4. Check logs for detailed errors + +### Q: Streaming responses disconnect? + +**A**: +1. Check Nginx / reverse proxy config and ensure `proxy_buffering` is off +2. Increase `proxy_read_timeout` +3. Verify network stability + +### Q: Configuration lost after Vercel deploy? + +**A**: +1. Ensure you clicked "Sync to Vercel" +2. Verify the Vercel token is valid and unexpired +3. Ensure the project ID is correct + +### Q: How to update to the latest version? + +**Local deployment**: +```bash +git pull origin main +pip install -r requirements.txt +# Restart the service +``` + +**Docker deployment**: +```bash +# Pull the latest code +git pull origin main + +# Rebuild and start (Docker config unchanged) +docker-compose up -d --build +``` + +**Vercel deployment**: +- The project auto-syncs from GitHub +- Or trigger a redeploy in the Vercel console + +### Q: How do I view logs? + +**Local dev**: +```bash +# Set log level +export LOG_LEVEL=DEBUG +python dev.py +``` + +**Vercel**: +- Vercel console → Project → Deployments → Logs + +### Q: Token counting is inaccurate? + +**A**: DS2API uses a heuristic estimate (characters / 4). The official OpenAI tokenizer may differ, so treat it as a reference only. + +--- + +## Get Help + +- **GitHub Issues**: https://github.com/CJackHwang/ds2api/issues +- **Docs**: https://github.com/CJackHwang/ds2api diff --git a/DEPLOY.md b/DEPLOY.md index 0fd87bc..b5c7748 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -1,5 +1,7 @@ # DS2API 部署指南 +语言 / Language: [中文](DEPLOY.md) | [English](DEPLOY.en.md) + 本文档详细介绍 DS2API 的各种部署方式。 --- diff --git a/README.MD b/README.MD index 37e5180..98a28c3 100644 --- a/README.MD +++ b/README.MD @@ -6,6 +6,8 @@ [![Version](https://img.shields.io/badge/version-1.6.11-blue.svg)](version.txt) [![Docker](https://img.shields.io/badge/docker-ready-blue.svg)](DEPLOY.md#docker-部署推荐) +语言 / Language: [中文](README.MD) | [English](README.en.md) + 将 DeepSeek 免费对话版转换为 **OpenAI & Claude 兼容 API**,支持多账号轮询、自动 Token 刷新、可视化管理界面。 ![p1](https://github.com/user-attachments/assets/07296a50-50d4-4f05-a9e5-280df14e9532) @@ -21,6 +23,7 @@ - 🚀 **多账号轮询** - Round-Robin 负载均衡,支持高并发场景 - 🔐 **Token 自动刷新** - 过期自动重新登录,无需手动维护 - 🌐 **WebUI 管理** - 可视化添加账号、测试 API、同步 Vercel 配置 +- 🌍 **多语言切换** - WebUI 内置中英双语,可随时切换 - 🔍 **联网搜索** - 支持 DeepSeek 原生搜索增强模式 - 🧠 **深度思考** - 支持推理模式,输出思考过程 - 🛠️ **工具调用** - 兼容 OpenAI Function Calling 格式 diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..f1b223c --- /dev/null +++ b/README.en.md @@ -0,0 +1,233 @@ +# DS2API + +[![License](https://img.shields.io/github/license/CJackHwang/ds2api.svg)](LICENSE) +![Stars](https://img.shields.io/github/stars/CJackHwang/ds2api.svg) +![Forks](https://img.shields.io/github/forks/CJackHwang/ds2api.svg) +[![Version](https://img.shields.io/badge/version-1.6.11-blue.svg)](version.txt) +[![Docker](https://img.shields.io/badge/docker-ready-blue.svg)](DEPLOY.md#docker-deployment-recommended) + +Language: [中文](README.MD) | [English](README.en.md) + +Convert DeepSeek Web into an **OpenAI & Claude compatible API**, with multi-account rotation, automatic token refresh, and a visual admin console. + +![p1](https://github.com/user-attachments/assets/07296a50-50d4-4f05-a9e5-280df14e9532) +![p2](https://github.com/user-attachments/assets/03b4a763-766f-4050-aea8-1a183e70ae6a) +![p3](https://github.com/user-attachments/assets/fc8b9836-11e3-4c38-a684-eb2c79b80fe9) +![p4](https://github.com/user-attachments/assets/513e9ca7-aa9e-45a6-8f7e-f362b1650675) + +## ✨ Features + +- 🔄 **Dual-protocol support** - OpenAI and Claude (Anthropic) compatible APIs +- 🚀 **Multi-account rotation** - Round-robin load balancing for high concurrency +- 🔐 **Automatic token refresh** - Re-auth on expiry without manual maintenance +- 🌐 **WebUI management** - Add accounts, test APIs, and sync Vercel settings visually +- 🌍 **Language toggle** - Built-in Chinese and English UI switcher +- 🔍 **Web search** - DeepSeek native search enhancement mode +- 🧠 **Deep reasoning** - Reasoning mode with trace output +- 🛠️ **Tool calling** - OpenAI Function Calling compatible +- ☁️ **One-click Vercel deploy** - No server required + +## 📋 Model Support + +### OpenAI compatible endpoint (`/v1/chat/completions`) + +| Model | Reasoning | Search | Notes | +|-----|:--------:|:------:|------| +| `deepseek-chat` | ❌ | ❌ | Standard chat | +| `deepseek-reasoner` | ✅ | ❌ | Reasoning (shows trace) | +| `deepseek-chat-search` | ❌ | ✅ | Web search mode | +| `deepseek-reasoner-search` | ✅ | ✅ | Reasoning + search | + +### Claude compatible endpoint (`/anthropic/v1/messages`) + +| Model | Notes | +|-----|------| +| `claude-sonnet-4-20250514` | Maps to deepseek-chat (standard) | +| `claude-sonnet-4-20250514-fast` | Maps to deepseek-chat (fast) | +| `claude-sonnet-4-20250514-slow` | Maps to deepseek-reasoner (reasoning) | + +> **Tip**: The Claude endpoint actually calls DeepSeek and returns Anthropic-format responses. + +## 🚀 Quick Start + +### Option 1: Vercel deployment (recommended) + +[![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=Admin%20console%20access%20key%20%28required%29&envLink=https%3A%2F%2Fgithub.com%2FCJackHwang%2Fds2api%23environment-variables&project-name=ds2api&repository-name=ds2api) + +1. Click the button above and set `DS2API_ADMIN_KEY` +2. After deployment, visit `/admin` +3. Add DeepSeek accounts and custom API keys +4. Click "Sync to Vercel" to persist configuration + +> **First sync validates accounts and stores tokens automatically.** + +### Option 2: Local development + +```bash +# 1. Clone the repo +git clone https://github.com/CJackHwang/ds2api.git +cd ds2api + +# 2. Install dependencies +pip install -r requirements.txt + +# 3. Configure accounts +cp config.example.json config.json +# Edit config.json to add DeepSeek account info + +# 4. Start the service +python dev.py +``` + +Visit `http://localhost:5001` after startup. + +## ⚙️ Configuration + +### Environment variables + +| Variable | Description | Required | +|-----|------|:----:| +| `DS2API_ADMIN_KEY` | Admin console password | Required on Vercel | +| `DS2API_CONFIG_JSON` | Config JSON or Base64 | Optional | +| `VERCEL_TOKEN` | Vercel API token (for sync) | Optional | +| `VERCEL_PROJECT_ID` | Vercel project ID | Optional | +| `PORT` | Service port (default 5001) | Optional | + +### Config file format (`config.json`) + +```json +{ + "keys": ["your-api-key-1", "your-api-key-2"], + "accounts": [ + { + "email": "user@example.com", + "password": "your-password", + "token": "" + }, + { + "mobile": "12345678901", + "password": "your-password", + "token": "" + } + ] +} +``` + +> **Notes**: +> - `keys`: Custom API keys for calling this service +> - `accounts`: DeepSeek Web accounts (email or mobile) +> - `token`: Leave blank; DS2API will fetch and refresh automatically + +## 📡 API Usage + +See **[API.md](API.md)** for full API documentation. + +### Quick examples + +**List models**: +```bash +curl http://localhost:5001/v1/models +``` + +**OpenAI-compatible call**: +```bash +curl http://localhost:5001/v1/chat/completions \ + -H "Authorization: Bearer your-api-key" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "deepseek-chat", + "messages": [{"role": "user", "content": "Hello"}], + "stream": true + }' +``` + +**Claude-compatible call**: +```bash +curl http://localhost:5001/anthropic/v1/messages \ + -H "x-api-key: your-api-key" \ + -H "Content-Type: application/json" \ + -H "anthropic-version: 2023-06-01" \ + -d '{ + "model": "claude-sonnet-4-20250514", + "max_tokens": 1024, + "messages": [{"role": "user", "content": "Hello"}] + }' +``` + +### Python SDK usage + +```python +from openai import OpenAI + +client = OpenAI( + api_key="your-api-key", + base_url="http://localhost:5001/v1" +) + +response = client.chat.completions.create( + model="deepseek-reasoner", + messages=[{"role": "user", "content": "Explain quantum entanglement"}], + stream=True +) + +for chunk in response: + if chunk.choices[0].delta.content: + print(chunk.choices[0].delta.content, end="") +``` + +## 🔧 Deployment Notes + +### Nginx reverse proxy + +```nginx +location / { + proxy_pass http://localhost:5001; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_buffering off; + proxy_cache off; + chunked_transfer_encoding on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 120; +} +``` + +### Option 3: Docker deployment + +```bash +# 1. Clone the repo and enter the directory +git clone https://github.com/CJackHwang/ds2api.git +cd ds2api + +# 2. Configure environment variables +cp .env.example .env +# Edit .env and fill in DS2API_ADMIN_KEY and DS2API_CONFIG_JSON + +# 3. Start the service +docker-compose up -d + +# 4. Check logs +docker-compose logs -f +``` + +> **Docker advantage**: Zero-intrusion design; update the main code with `docker-compose up -d --build` without changing Docker configuration. See [DEPLOY.md](DEPLOY.md#docker-deployment-recommended). + +## ⚠️ Disclaimer + +**This project is based on reverse engineering and stability is not guaranteed.** + +- For learning and research only. **No commercial use or public service is allowed.** +- For production, use the official [DeepSeek API](https://platform.deepseek.com/) +- You assume all risks from using this project + +## 📜 Acknowledgements + +This project is based on the following open-source projects: + +- [iidamie/deepseek2api](https://github.com/iidamie/deepseek2api) +- [LLM-Red-Team/deepseek-free-api](https://github.com/LLM-Red-Team/deepseek-free-api) + +## 📊 Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=CJackHwang/ds2api&type=Date)](https://star-history.com/#CJackHwang/ds2api&Date) diff --git a/webui/index.html b/webui/index.html index c7d9ff6..556ed1e 100644 --- a/webui/index.html +++ b/webui/index.html @@ -6,16 +6,16 @@ - DS2API - DeepSeek API 管理面板 - - + DS2API - 管理面板 / Admin Console + + - - + + @@ -39,4 +39,4 @@ - \ No newline at end of file + diff --git a/webui/src/App.jsx b/webui/src/App.jsx index 38b1cbc..53d0b4a 100644 --- a/webui/src/App.jsx +++ b/webui/src/App.jsx @@ -25,19 +25,22 @@ import BatchImport from './components/BatchImport' import VercelSync from './components/VercelSync' import Login from './components/Login' import LandingPage from './components/LandingPage' - -const NAV_ITEMS = [ - { id: 'accounts', label: '账号管理', icon: Users, description: '管理 DeepSeek 账号池' }, - { id: 'test', label: 'API 测试', icon: Server, description: '测试 API 连接与响应' }, - { id: 'import', label: '批量导入', icon: Upload, description: '批量导入账号配置' }, - { id: 'vercel', label: 'Vercel 同步', icon: Cloud, description: '同步配置到 Vercel' }, -] +import LanguageToggle from './components/LanguageToggle' +import { useI18n } from './i18n' function Dashboard({ token, onLogout, config, fetchConfig, showMessage, message }) { + const { t } = useI18n() const [activeTab, setActiveTab] = useState('accounts') const [sidebarOpen, setSidebarOpen] = useState(false) const [loading, setLoading] = useState(false) + const navItems = [ + { id: 'accounts', label: t('nav.accounts.label'), icon: Users, description: t('nav.accounts.desc') }, + { id: 'test', label: t('nav.test.label'), icon: Server, description: t('nav.test.desc') }, + { id: 'import', label: t('nav.import.label'), icon: Upload, description: t('nav.import.desc') }, + { id: 'vercel', label: t('nav.vercel.label'), icon: Cloud, description: t('nav.vercel.desc') }, + ] + const authFetch = async (url, options = {}) => { const headers = { ...options.headers, @@ -47,7 +50,7 @@ function Dashboard({ token, onLogout, config, fetchConfig, showMessage, message if (res.status === 401) { onLogout() - throw new Error('认证已过期,请重新登录') + throw new Error(t('auth.expired')) } return res } @@ -87,11 +90,14 @@ function Dashboard({ token, onLogout, config, fetchConfig, showMessage, message DS2API -

在线管理面板

+
+

{t('sidebar.onlineAdminConsole')}

+ +