增加/service/translate/file接口
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import base64
|
import base64
|
||||||
import binascii
|
import binascii
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
@@ -26,7 +27,16 @@ from typing import (
|
|||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from fastapi import FastAPI, HTTPException, APIRouter, Body, Path as FastApiPath
|
from fastapi import (
|
||||||
|
FastAPI,
|
||||||
|
HTTPException,
|
||||||
|
APIRouter,
|
||||||
|
Body,
|
||||||
|
Path as FastApiPath,
|
||||||
|
UploadFile,
|
||||||
|
File,
|
||||||
|
Form,
|
||||||
|
)
|
||||||
from fastapi.openapi.docs import (
|
from fastapi.openapi.docs import (
|
||||||
get_swagger_ui_html,
|
get_swagger_ui_html,
|
||||||
get_swagger_ui_oauth2_redirect_html,
|
get_swagger_ui_oauth2_redirect_html,
|
||||||
@@ -40,6 +50,7 @@ from pydantic import (
|
|||||||
model_validator,
|
model_validator,
|
||||||
AliasChoices,
|
AliasChoices,
|
||||||
ConfigDict,
|
ConfigDict,
|
||||||
|
Json,
|
||||||
)
|
)
|
||||||
|
|
||||||
from docutranslate import __version__
|
from docutranslate import __version__
|
||||||
@@ -250,7 +261,7 @@ DocuTranslate 后端服务 API,提供文档翻译、状态查询、结果下
|
|||||||
**注意**: 所有任务状态都保存在服务进程的内存中,服务重启将导致所有任务信息丢失。
|
**注意**: 所有任务状态都保存在服务进程的内存中,服务重启将导致所有任务信息丢失。
|
||||||
|
|
||||||
### 主要工作流程:
|
### 主要工作流程:
|
||||||
1. **`POST /service/translate`**: 提交文件和包含`workflow_type`的翻译参数,启动一个后台任务。服务会自动生成并返回一个唯一的 `task_id`。
|
1. **`POST /service/translate`** 或 **`POST /service/translate/file`**: 提交文件和包含`workflow_type`的翻译参数,启动一个后台任务。服务会自动生成并返回一个唯一的 `task_id`。
|
||||||
2. **`GET /service/status/{{task_id}}`**: 使用获取到的 `task_id` 轮询此端点,获取任务的实时状态。
|
2. **`GET /service/status/{{task_id}}`**: 使用获取到的 `task_id` 轮询此端点,获取任务的实时状态。
|
||||||
3. **`GET /service/logs/{{task_id}}`**: (可选) 获取实时的翻译日志。
|
3. **`GET /service/logs/{{task_id}}`**: (可选) 获取实时的翻译日志。
|
||||||
4. **`GET /service/download/{{task_id}}/{{file_type}}`**: 任务完成后 (当 `download_ready` 为 `true` 时),通过此端点下载结果文件。
|
4. **`GET /service/download/{{task_id}}/{{file_type}}`**: 任务完成后 (当 `download_ready` 为 `true` 时),通过此端点下载结果文件。
|
||||||
@@ -386,7 +397,16 @@ class BaseWorkflowParams(BaseModel):
|
|||||||
@model_validator(mode="before")
|
@model_validator(mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
def check_translation_fields(cls, values):
|
def check_translation_fields(cls, values):
|
||||||
|
# 修复: 当使用 FastAPI Form + Json 时,Pydantic V2 mode='before' 验证器可能会接收到 JSON 字符串
|
||||||
|
if isinstance(values, str):
|
||||||
|
try:
|
||||||
|
values = json.loads(values)
|
||||||
|
except ValueError:
|
||||||
|
# 无法解析为 JSON,可能是其他字符串,忽略,交由后续逻辑或抛出错误
|
||||||
|
pass
|
||||||
|
|
||||||
# 如果不跳过翻译 (值为False或字段不存在),则验证相关字段必须存在且不为空
|
# 如果不跳过翻译 (值为False或字段不存在),则验证相关字段必须存在且不为空
|
||||||
|
if isinstance(values, dict):
|
||||||
if not values.get("skip_translate"):
|
if not values.get("skip_translate"):
|
||||||
# Check for standard keys or their aliases
|
# Check for standard keys or their aliases
|
||||||
if not (values.get("base_url") or values.get("baseurl")):
|
if not (values.get("base_url") or values.get("baseurl")):
|
||||||
@@ -1653,6 +1673,72 @@ async def service_translate(
|
|||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
@service_router.post(
|
||||||
|
"/translate/file",
|
||||||
|
summary="提交翻译任务 (文件上传)",
|
||||||
|
description="""
|
||||||
|
接收一个上传的文件和包含工作流参数的JSON字符串,启动一个后台翻译任务。
|
||||||
|
|
||||||
|
- **工作流选择**: `payload` 表单字段中的 `workflow_type` 字段决定了本次任务的类型。
|
||||||
|
- **文件上传**: 通过 `file` 字段上传文件,替代JSON接口中的 `file_content` 和 `file_name`。
|
||||||
|
- **参数传递**: `payload` 字段应为一个符合 JSON 格式的字符串,其结构与 `/service/translate` 中的 `payload` 字段完全一致。
|
||||||
|
- **异步处理**: 此端点会立即返回任务ID,客户端需轮询状态接口获取进度。
|
||||||
|
""",
|
||||||
|
responses={
|
||||||
|
200: {
|
||||||
|
"description": "翻译任务已成功启动。",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"example": {
|
||||||
|
"task_started": True,
|
||||||
|
"task_id": "a1b2c3d4",
|
||||||
|
"message": "翻译任务已成功启动,请稍候...",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
422: {"description": "请求参数验证失败,例如 JSON 格式错误。"},
|
||||||
|
429: {
|
||||||
|
"description": "服务器已有一个同ID的任务在处理中(理论上不应发生,因为ID是新生成的)。"
|
||||||
|
},
|
||||||
|
500: {"description": "启动后台任务时发生未知错误。"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def service_translate_file(
|
||||||
|
file: UploadFile = File(..., description="要翻译的文件"),
|
||||||
|
payload: Json[TranslatePayload] = Form(
|
||||||
|
..., description="包含工作流参数的JSON字符串,结构与JSON接口的payload一致。"
|
||||||
|
),
|
||||||
|
):
|
||||||
|
task_id = uuid.uuid4().hex[:8]
|
||||||
|
|
||||||
|
try:
|
||||||
|
file_contents = await file.read()
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"读取上传文件失败: {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response_data = await _start_translation_task(
|
||||||
|
task_id=task_id,
|
||||||
|
payload=payload,
|
||||||
|
file_contents=file_contents,
|
||||||
|
original_filename=file.filename or "uploaded_file",
|
||||||
|
)
|
||||||
|
return JSONResponse(content=response_data)
|
||||||
|
except HTTPException as e:
|
||||||
|
if e.status_code == 429:
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=e.status_code,
|
||||||
|
content={"task_started": False, "message": e.detail},
|
||||||
|
)
|
||||||
|
if e.status_code == 500:
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=e.status_code,
|
||||||
|
content={"task_started": False, "message": e.detail},
|
||||||
|
)
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
@service_router.post(
|
@service_router.post(
|
||||||
"/cancel/{task_id}",
|
"/cancel/{task_id}",
|
||||||
summary="取消翻译任务",
|
summary="取消翻译任务",
|
||||||
|
|||||||
Reference in New Issue
Block a user