From b17e492ab8d61ffa20894e255b79207956ab17a8 Mon Sep 17 00:00:00 2001 From: CJACK Date: Sun, 1 Feb 2026 16:43:52 +0800 Subject: [PATCH] feat: Integrate and serve the WebUI, including Vercel build configuration and Python routes for static files. --- API.md | 50 ++++++++++++++++++++++++++++++++++++++++++++ core/config.py | 3 +++ routes/home.py | 32 ++++++++++++++++++++++++++-- tests/README.md | 17 +++++++++------ vercel.json | 11 +++++++++- webui/vite.config.js | 1 + 6 files changed, 105 insertions(+), 9 deletions(-) diff --git a/API.md b/API.md index 1bdc314..f3fed0a 100644 --- a/API.md +++ b/API.md @@ -49,6 +49,8 @@ Content-Type: application/json | `stream` | boolean | ❌ | 是否流式输出,默认 `false` | | `temperature` | number | ❌ | 温度参数,0-2 | | `max_tokens` | number | ❌ | 最大输出 token 数 | +| `tools` | array | ❌ | 工具定义列表(OpenAI 格式) | +| `tool_choice` | string | ❌ | 工具选择策略 | **支持的模型**: @@ -111,6 +113,54 @@ data: [DONE] } ``` +**工具调用请求示例**: + +```json +{ + "model": "deepseek-chat", + "messages": [{"role": "user", "content": "北京今天天气怎么样?"}], + "tools": [{ + "type": "function", + "function": { + "name": "get_weather", + "description": "获取指定城市的天气", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string", "description": "城市名称"} + }, + "required": ["location"] + } + } + }] +} +``` + +**工具调用响应格式**: + +```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\": \"北京\"}" + } + }] + }, + "finish_reason": "tool_calls" + }] +} +``` + --- ## 管理接口 diff --git a/core/config.py b/core/config.py index de74f2c..83e02ca 100644 --- a/core/config.py +++ b/core/config.py @@ -101,3 +101,6 @@ WASM_PATH = resolve_path("DS2API_WASM_PATH", "sha3_wasm_bg.7b9ca65ddd.wasm") # 模板目录 TEMPLATES_DIR = resolve_path("DS2API_TEMPLATES_DIR", "templates") + +# WebUI 静态文件目录 +STATIC_ADMIN_DIR = resolve_path("DS2API_STATIC_ADMIN_DIR", "static/admin") diff --git a/routes/home.py b/routes/home.py index cb08c73..69dce7f 100644 --- a/routes/home.py +++ b/routes/home.py @@ -1,9 +1,11 @@ # -*- coding: utf-8 -*- -"""首页路由""" +"""首页和 WebUI 路由""" +import os from fastapi import APIRouter, Request +from fastapi.responses import HTMLResponse, FileResponse from fastapi.templating import Jinja2Templates -from core.config import TEMPLATES_DIR +from core.config import TEMPLATES_DIR, STATIC_ADMIN_DIR router = APIRouter() templates = Jinja2Templates(directory=TEMPLATES_DIR) @@ -12,3 +14,29 @@ templates = Jinja2Templates(directory=TEMPLATES_DIR) @router.get("/") def index(request: Request): return templates.TemplateResponse("welcome.html", {"request": request}) + + +@router.get("/webui") +@router.get("/webui/{path:path}") +async def webui(request: Request, path: str = ""): + """提供 WebUI 静态文件""" + # 检查 static/admin 目录是否存在 + if not os.path.isdir(STATIC_ADMIN_DIR): + return HTMLResponse( + content="

WebUI not built

Run cd webui && npm run build first.

", + 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.html(SPA 路由) + 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) diff --git a/tests/README.md b/tests/README.md index 6823132..d02a19a 100644 --- a/tests/README.md +++ b/tests/README.md @@ -36,6 +36,9 @@ python3 tests/test_unit.py - WASM 缓存 - 模型配置获取 - 正则表达式模式 +- 流式响应解析 +- **工具调用解析**(`parse_tool_calls`) +- **Token 估算** ### 运行 API 集成测试 @@ -58,9 +61,11 @@ python3 tests/test_all.py --verbose | 类别 | 测试项 | |-----|--------| | 基础 | 服务健康检查 | -| OpenAI | 模型列表、非流式对话、流式对话、无效模型处理、认证错误 | +| OpenAI | 模型列表、非流式对话、流式对话、无效模型处理、认证错误、Reasoner 模式 | | Claude | 模型列表、非流式消息、流式消息、Token 计数 | -| 高级 | 多轮对话、长输入处理、Reasoner 模式 | +| 高级 | 多轮对话、长输入处理 | +| **工具调用** | OpenAI 工具调用(流式/非流式)、Claude 工具调用 | +| **搜索模式** | OpenAI 搜索模式 | ### 运行账号测试 @@ -93,7 +98,7 @@ python3 tests/test_accounts.py --all ### 单元测试 ``` -Ran 13 tests in 8.685s +Ran 32 tests in 9.0s OK ``` @@ -101,10 +106,10 @@ OK ``` 📊 测试报告 -总计: 10 个测试 -✅ 通过: 10 +总计: 18 个测试 +✅ 通过: 18 ❌ 失败: 0 -⏱️ 耗时: 15.32s +⏱️ 耗时: ~60s 📈 通过率: 100.0% ``` diff --git a/vercel.json b/vercel.json index ec6aded..5cfae21 100644 --- a/vercel.json +++ b/vercel.json @@ -1,15 +1,24 @@ { "version": 2, + "buildCommand": "cd webui && npm install && npm run build", "builds": [ { "src": "app.py", "use": "@vercel/python" + }, + { + "src": "static/admin/**", + "use": "@vercel/static" } ], "routes": [ + { + "src": "/webui/assets/(.*)", + "dest": "/static/admin/assets/$1" + }, { "src": "/(.*)", "dest": "app.py" } ] -} +} \ No newline at end of file diff --git a/webui/vite.config.js b/webui/vite.config.js index fee49b5..7c5b088 100644 --- a/webui/vite.config.js +++ b/webui/vite.config.js @@ -22,4 +22,5 @@ export default defineConfig({ outDir: '../static/admin', emptyOutDir: true, }, + base: '/webui/', })