422 lines
12 KiB
Python
422 lines
12 KiB
Python
from pathlib import Path
|
|
from typing import Any, List
|
|
|
|
import shutil
|
|
|
|
from fastapi import APIRouter, BackgroundTasks, Depends, File, HTTPException, UploadFile
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.security import get_current_user
|
|
from app.db.session import get_db
|
|
from app.models.knowledge import KnowledgeBase
|
|
from app.models.tooling import SRSExtraction, TestingGeneration, ToolJob
|
|
from app.models.user import User
|
|
from app.schemas.tooling import (
|
|
SRSToolCreateJobResponse,
|
|
SRSToolHistoryItem,
|
|
SRSToolJobStatusResponse,
|
|
SRSToolRequirementsSaveRequest,
|
|
SRSToolResultResponse,
|
|
TestingGenerationCreateRequest,
|
|
TestingGenerationCreateResponse,
|
|
TestingGenerationHistoryItem,
|
|
TestingGenerationJobStatusResponse,
|
|
TestingGenerationResultResponse,
|
|
TestingGenerationSaveRequest,
|
|
ToolDefinitionResponse,
|
|
)
|
|
from app.services.srs_job_service import (
|
|
build_srs_upload_path,
|
|
build_result_response,
|
|
delete_srs_job,
|
|
ensure_upload_path,
|
|
list_srs_history,
|
|
replace_requirements,
|
|
run_srs_job,
|
|
)
|
|
from app.services.model_config import ModelConfigService
|
|
from app.services.testing_generation_service import (
|
|
build_testing_generation_response,
|
|
create_testing_generation,
|
|
delete_testing_generation,
|
|
list_testing_history,
|
|
)
|
|
from app.services.testing_generation_job_service import run_testing_generation_job
|
|
from app.tools.registry import ToolRegistry
|
|
from app.tools.srs_reqs_qwen import get_srs_tool
|
|
|
|
router = APIRouter()
|
|
|
|
# Register SRS tool when the router is imported.
|
|
get_srs_tool()
|
|
|
|
ALLOWED_EXTENSIONS = {".pdf", ".docx"}
|
|
|
|
|
|
@router.get("", response_model=List[ToolDefinitionResponse])
|
|
async def list_tools(
|
|
current_user: User = Depends(get_current_user),
|
|
) -> Any:
|
|
_ = current_user
|
|
return ToolRegistry.list()
|
|
|
|
|
|
@router.post("/srs/jobs", response_model=SRSToolCreateJobResponse)
|
|
async def create_srs_job(
|
|
background_tasks: BackgroundTasks,
|
|
file: UploadFile = File(...),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
) -> Any:
|
|
safe_name = Path(file.filename or "").name
|
|
ext = Path(safe_name).suffix.lower()
|
|
if ext not in ALLOWED_EXTENSIONS:
|
|
raise HTTPException(status_code=400, detail="仅支持 .pdf/.docx 文件")
|
|
|
|
try:
|
|
ModelConfigService.require_active_config(db, current_user.id)
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
|
|
job = ToolJob(
|
|
user_id=current_user.id,
|
|
tool_name="srs.requirement_extractor",
|
|
status="pending",
|
|
input_file_name=safe_name,
|
|
input_file_path="",
|
|
)
|
|
db.add(job)
|
|
db.commit()
|
|
db.refresh(job)
|
|
|
|
target_path = ensure_upload_path(job.id, safe_name)
|
|
try:
|
|
content = await file.read()
|
|
target_path.write_bytes(content)
|
|
except Exception as exc:
|
|
job.status = "failed"
|
|
job.error_message = f"保存上传文件失败: {exc}"
|
|
db.add(job)
|
|
db.commit()
|
|
raise HTTPException(status_code=500, detail="上传文件保存失败")
|
|
|
|
job.input_file_path = str(target_path.resolve())
|
|
db.add(job)
|
|
db.commit()
|
|
|
|
background_tasks.add_task(run_srs_job, job.id)
|
|
|
|
return {
|
|
"job_id": job.id,
|
|
"status": job.status,
|
|
}
|
|
|
|
|
|
@router.get("/srs/jobs/{job_id}", response_model=SRSToolJobStatusResponse)
|
|
async def get_srs_job_status(
|
|
job_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
) -> Any:
|
|
job = (
|
|
db.query(ToolJob)
|
|
.filter(ToolJob.id == job_id, ToolJob.user_id == current_user.id)
|
|
.first()
|
|
)
|
|
if not job:
|
|
raise HTTPException(status_code=404, detail="任务不存在")
|
|
|
|
extraction = (
|
|
db.query(SRSExtraction)
|
|
.filter(SRSExtraction.job_id == job.id)
|
|
.first()
|
|
)
|
|
|
|
return {
|
|
"job_id": job.id,
|
|
"tool_name": job.tool_name,
|
|
"status": job.status,
|
|
"error_message": job.error_message,
|
|
"extraction_id": extraction.id if extraction else None,
|
|
"started_at": job.started_at,
|
|
"completed_at": job.completed_at,
|
|
}
|
|
|
|
|
|
@router.get("/srs/jobs/{job_id}/result", response_model=SRSToolResultResponse)
|
|
async def get_srs_job_result(
|
|
job_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
) -> Any:
|
|
job = (
|
|
db.query(ToolJob)
|
|
.filter(ToolJob.id == job_id, ToolJob.user_id == current_user.id)
|
|
.first()
|
|
)
|
|
if not job:
|
|
raise HTTPException(status_code=404, detail="任务不存在")
|
|
if job.status != "completed":
|
|
raise HTTPException(status_code=409, detail="任务尚未完成")
|
|
|
|
extraction = (
|
|
db.query(SRSExtraction)
|
|
.filter(SRSExtraction.job_id == job.id)
|
|
.first()
|
|
)
|
|
if not extraction:
|
|
raise HTTPException(status_code=404, detail="任务结果不存在")
|
|
|
|
return build_result_response(job, extraction)
|
|
|
|
|
|
@router.put("/srs/jobs/{job_id}/requirements", response_model=SRSToolResultResponse)
|
|
async def save_srs_requirements(
|
|
job_id: int,
|
|
payload: SRSToolRequirementsSaveRequest,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
) -> Any:
|
|
job = (
|
|
db.query(ToolJob)
|
|
.filter(ToolJob.id == job_id, ToolJob.user_id == current_user.id)
|
|
.first()
|
|
)
|
|
if not job:
|
|
raise HTTPException(status_code=404, detail="任务不存在")
|
|
|
|
extraction = (
|
|
db.query(SRSExtraction)
|
|
.filter(SRSExtraction.job_id == job.id)
|
|
.first()
|
|
)
|
|
if not extraction:
|
|
raise HTTPException(status_code=404, detail="任务结果不存在")
|
|
|
|
replace_requirements(db, extraction, [item.dict() for item in payload.requirements])
|
|
db.add(extraction)
|
|
db.commit()
|
|
db.refresh(extraction)
|
|
|
|
return build_result_response(job, extraction)
|
|
|
|
|
|
@router.get("/srs/history", response_model=List[SRSToolHistoryItem])
|
|
async def get_srs_history(
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
) -> Any:
|
|
return list_srs_history(db, current_user.id)
|
|
|
|
|
|
@router.delete("/srs/jobs/{job_id}")
|
|
async def delete_srs_job_api(
|
|
job_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
) -> Any:
|
|
job = (
|
|
db.query(ToolJob)
|
|
.filter(ToolJob.id == job_id, ToolJob.user_id == current_user.id)
|
|
.first()
|
|
)
|
|
if not job:
|
|
raise HTTPException(status_code=404, detail="任务不存在")
|
|
|
|
upload_path = build_srs_upload_path(job_id)
|
|
delete_srs_job(db, job)
|
|
|
|
if upload_path.exists():
|
|
shutil.rmtree(upload_path, ignore_errors=True)
|
|
|
|
return {"message": "删除成功"}
|
|
|
|
|
|
@router.post("/testing/generations", response_model=TestingGenerationResultResponse)
|
|
async def save_testing_generation(
|
|
payload: TestingGenerationSaveRequest,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
) -> Any:
|
|
if payload.source_job_id is not None:
|
|
source_job = (
|
|
db.query(ToolJob)
|
|
.filter(
|
|
ToolJob.id == payload.source_job_id,
|
|
ToolJob.user_id == current_user.id,
|
|
)
|
|
.first()
|
|
)
|
|
if not source_job:
|
|
raise HTTPException(status_code=404, detail="来源文件不存在")
|
|
|
|
if payload.knowledge_base_id is not None:
|
|
knowledge_base = (
|
|
db.query(KnowledgeBase)
|
|
.filter(
|
|
KnowledgeBase.id == payload.knowledge_base_id,
|
|
KnowledgeBase.user_id == current_user.id,
|
|
)
|
|
.first()
|
|
)
|
|
if not knowledge_base:
|
|
raise HTTPException(status_code=404, detail="知识库不存在")
|
|
|
|
return create_testing_generation(db, current_user.id, payload)
|
|
|
|
|
|
@router.post("/testing/jobs", response_model=TestingGenerationCreateResponse)
|
|
async def create_testing_generation_job(
|
|
background_tasks: BackgroundTasks,
|
|
payload: TestingGenerationCreateRequest,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
) -> Any:
|
|
if payload.source_job_id is not None:
|
|
source_job = (
|
|
db.query(ToolJob)
|
|
.filter(
|
|
ToolJob.id == payload.source_job_id,
|
|
ToolJob.user_id == current_user.id,
|
|
)
|
|
.first()
|
|
)
|
|
if not source_job:
|
|
raise HTTPException(status_code=404, detail="来源文件不存在")
|
|
|
|
if payload.knowledge_base_id is not None:
|
|
knowledge_base = (
|
|
db.query(KnowledgeBase)
|
|
.filter(
|
|
KnowledgeBase.id == payload.knowledge_base_id,
|
|
KnowledgeBase.user_id == current_user.id,
|
|
)
|
|
.first()
|
|
)
|
|
if not knowledge_base:
|
|
raise HTTPException(status_code=404, detail="知识库不存在")
|
|
|
|
job = ToolJob(
|
|
user_id=current_user.id,
|
|
tool_name="testing.case_generator",
|
|
status="pending",
|
|
input_file_name=payload.source_document_name,
|
|
input_file_path="",
|
|
output_summary={
|
|
"source_document_name": payload.source_document_name,
|
|
"current_step": 0,
|
|
"total_steps": len(payload.requirements),
|
|
},
|
|
)
|
|
db.add(job)
|
|
db.commit()
|
|
db.refresh(job)
|
|
|
|
background_tasks.add_task(
|
|
run_testing_generation_job,
|
|
job.id,
|
|
payload.dict(),
|
|
)
|
|
|
|
return {
|
|
"job_id": job.id,
|
|
"status": job.status,
|
|
}
|
|
|
|
|
|
@router.get("/testing/jobs/{job_id}", response_model=TestingGenerationJobStatusResponse)
|
|
async def get_testing_generation_job_status(
|
|
job_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
) -> Any:
|
|
job = (
|
|
db.query(ToolJob)
|
|
.filter(
|
|
ToolJob.id == job_id,
|
|
ToolJob.user_id == current_user.id,
|
|
)
|
|
.first()
|
|
)
|
|
if not job:
|
|
raise HTTPException(status_code=404, detail="任务不存在")
|
|
|
|
summary = job.output_summary or {}
|
|
return {
|
|
"job_id": job.id,
|
|
"tool_name": job.tool_name,
|
|
"status": job.status,
|
|
"error_message": job.error_message,
|
|
"started_at": job.started_at,
|
|
"completed_at": job.completed_at,
|
|
"source_document_name": summary.get("source_document_name"),
|
|
"current_step": summary.get("current_step"),
|
|
"total_steps": summary.get("total_steps"),
|
|
"current_requirement_id": summary.get("current_requirement_id"),
|
|
}
|
|
|
|
|
|
@router.get("/testing/history", response_model=List[TestingGenerationHistoryItem])
|
|
async def get_testing_history(
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
) -> Any:
|
|
return list_testing_history(db, current_user.id)
|
|
|
|
|
|
@router.get("/testing/jobs/{job_id}/result", response_model=TestingGenerationResultResponse)
|
|
async def get_testing_generation_result(
|
|
job_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
) -> Any:
|
|
job = (
|
|
db.query(ToolJob)
|
|
.filter(
|
|
ToolJob.id == job_id,
|
|
ToolJob.user_id == current_user.id,
|
|
)
|
|
.first()
|
|
)
|
|
if not job:
|
|
raise HTTPException(status_code=404, detail="任务不存在")
|
|
|
|
generation = (
|
|
db.query(TestingGeneration)
|
|
.filter(TestingGeneration.job_id == job.id)
|
|
.first()
|
|
)
|
|
if not generation:
|
|
raise HTTPException(status_code=404, detail="任务结果不存在")
|
|
|
|
return build_testing_generation_response(job, generation)
|
|
|
|
|
|
@router.delete("/testing/jobs/{job_id}")
|
|
async def delete_testing_generation_api(
|
|
job_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
) -> Any:
|
|
job = (
|
|
db.query(ToolJob)
|
|
.filter(
|
|
ToolJob.id == job_id,
|
|
ToolJob.user_id == current_user.id,
|
|
)
|
|
.first()
|
|
)
|
|
if not job:
|
|
raise HTTPException(status_code=404, detail="任务不存在")
|
|
|
|
generation = (
|
|
db.query(TestingGeneration)
|
|
.filter(TestingGeneration.job_id == job.id)
|
|
.first()
|
|
)
|
|
if not generation:
|
|
raise HTTPException(status_code=404, detail="任务结果不存在")
|
|
|
|
delete_testing_generation(db, job)
|
|
return {"message": "删除成功"}
|