Fix SSE route dependency and align architecture docs

This commit is contained in:
ash66
2026-05-18 16:32:42 +08:00
parent 86b9ac806a
commit 3f69cad404
149 changed files with 4786 additions and 5957 deletions

View File

@@ -1,9 +1,15 @@
from fastapi import APIRouter, UploadFile, File, HTTPException
from sse_starlette.sse import EventSourceResponse
import uuid
import os
import json
"""Define API routes for compliance."""
from __future__ import annotations
import asyncio
import json
from pathlib import Path
from typing import AsyncGenerator
from fastapi import APIRouter, File, UploadFile
from fastapi.responses import StreamingResponse
from app.schemas.compliance import (
AnalyzeResponse,
ComplianceChatRequest,
@@ -13,38 +19,42 @@ from app.services.mock_data import (
get_mock_compliance_result,
get_mock_compliance_chat_response,
)
# Keep route handlers close to their transport-layer wiring for easier auditing.
router = APIRouter(prefix="/compliance", tags=["合规分析"])
# 临时存储分析任务
# Keep route handlers close to their transport-layer wiring for easier auditing.
tasks_store: dict[str, dict] = {}
# Store uploaded compliance files inside the local backend data directory.
RAW_DATA_DIR = Path(__file__).resolve().parents[3] / "data" / "raw"
@router.post("/analyze", response_model=AnalyzeResponse)
async def analyze_document(file: UploadFile = File(...)):
"""上传设计方案进行分析"""
# 生成任务ID
"""Handle analyze document."""
# Keep route handlers close to their transport-layer wiring for easier auditing.
task_id = generate_task_id()
# 保存文件
raw_dir = "/airegulation/demo-mao/backend/data/raw"
os.makedirs(raw_dir, exist_ok=True)
file_path = os.path.join(raw_dir, f"compliance_{task_id}_{file.filename}")
# Keep route handlers close to their transport-layer wiring for easier auditing.
RAW_DATA_DIR.mkdir(parents=True, exist_ok=True)
file_path = RAW_DATA_DIR / f"compliance_{task_id}_{file.filename}"
content = await file.read()
with open(file_path, "wb") as f:
with file_path.open("wb") as f:
f.write(content)
# 记录任务
# Keep route handlers close to their transport-layer wiring for easier auditing.
tasks_store[task_id] = {
"task_id": task_id,
"file_path": file_path,
"file_path": str(file_path),
"status": "processing",
"result": None,
}
# 模拟异步处理完成(立即返回结果)
# 实际应用中这应该是后台任务
# Keep route handlers close to their transport-layer wiring for easier auditing.
# Keep route handlers close to their transport-layer wiring for easier auditing.
tasks_store[task_id]["status"] = "completed"
tasks_store[task_id]["result"] = get_mock_compliance_result(task_id)
@@ -53,9 +63,9 @@ async def analyze_document(file: UploadFile = File(...)):
@router.get("/result/{task_id}")
async def get_result(task_id: str):
"""获取分析结果"""
"""Return result."""
if task_id not in tasks_store:
# 如果任务ID不存在返回默认mock结果
# Keep route handlers close to their transport-layer wiring for easier auditing.
return get_mock_compliance_result(task_id)
task = tasks_store[task_id]
@@ -68,8 +78,8 @@ async def get_result(task_id: str):
@router.post("/chat/{segment_id}")
async def compliance_chat(segment_id: int, request: ComplianceChatRequest):
"""针对段落进行合规对话"""
# 根据segment_id获取对应的intent
"""Handle compliance chat."""
# Keep route handlers close to their transport-layer wiring for easier auditing.
intent_map = {
1: "车身结构设计",
2: "动力系统配置",
@@ -77,11 +87,12 @@ async def compliance_chat(segment_id: int, request: ComplianceChatRequest):
}
intent = intent_map.get(segment_id, "车身结构设计")
async def generate():
# 获取预设响应
async def generate() -> AsyncGenerator[str, None]:
# Keep route handlers close to their transport-layer wiring for easier auditing.
"""Handle generate."""
response = get_mock_compliance_chat_response(intent, request.query)
# 流式输出响应
# Keep route handlers close to their transport-layer wiring for easier auditing.
sentences = response.split("\n\n")
for sentence in sentences:
if sentence.strip():
@@ -89,8 +100,15 @@ async def compliance_chat(segment_id: int, request: ComplianceChatRequest):
for chunk in chunks:
if chunk.strip():
await asyncio.sleep(0.05)
yield {"event": "message", "data": json.dumps({"type": "chunk", "text": chunk + "\n"})}
yield (
"event: message\n"
f"data: {json.dumps({'type': 'chunk', 'text': chunk + chr(10)}, ensure_ascii=False)}\n\n"
)
yield {"event": "message", "data": json.dumps({"type": "done"})}
yield f"event: message\ndata: {json.dumps({'type': 'done'}, ensure_ascii=False)}\n\n"
return EventSourceResponse(generate())
return StreamingResponse(
generate(),
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "Connection": "keep-alive", "X-Accel-Buffering": "no"},
)