diff --git a/backend/app/api/data_sources.py b/backend/app/api/data_sources.py new file mode 100644 index 0000000..3a40c82 --- /dev/null +++ b/backend/app/api/data_sources.py @@ -0,0 +1,27 @@ +from fastapi import APIRouter, HTTPException + +from ..schemas.template import DataSourcePayload + + +router = APIRouter(prefix="/api/data-sources", tags=["data-sources"]) + +DATA_SOURCES: list[dict] = [ + {"code": "policy", "name": "政策文件", "description": "政策法规、制度文件等资料"}, + {"code": "business_data", "name": "业务数据", "description": "业务系统导出的结构化数据"}, + {"code": "research", "name": "调研材料", "description": "访谈、问卷、调研纪要等非结构化资料"}, +] + + +@router.get("") +def list_data_sources(): + return {"data": DATA_SOURCES, "message": "ok"} + + +@router.post("") +def create_data_source(payload: DataSourcePayload): + if any(item["code"] == payload.code for item in DATA_SOURCES): + raise HTTPException(status_code=400, detail="数据源编码已存在") + + item = payload.model_dump() + DATA_SOURCES.append(item) + return {"data": item, "message": "ok"} diff --git a/backend/app/api/templates.py b/backend/app/api/templates.py index 65499d7..c16d17b 100644 --- a/backend/app/api/templates.py +++ b/backend/app/api/templates.py @@ -1,8 +1,16 @@ +import json + from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile from sqlalchemy.orm import Session from ..database import get_db +from ..models.block_config import BlockConfig +from ..models.template import Template +from ..models.template_block import TemplateBlock +from ..schemas.template import BlockConfigPayload +from ..services.doc_parser import build_tree from ..services.doc_parser import parse_document +from ..services.html_generator import generate_preview_html from ..services.template_service import create_template_record, save_template_blocks, store_template_file @@ -37,3 +45,120 @@ def upload_template( }, "message": "ok", } + + +@router.get("/{template_id}") +def get_template_detail(template_id: int, db: Session = Depends(get_db)): + template = db.get(Template, template_id) + if not template: + raise HTTPException(status_code=404, detail="模板不存在") + + block_records = ( + db.query(TemplateBlock) + .filter(TemplateBlock.template_id == template_id) + .order_by(TemplateBlock.sort_order.asc(), TemplateBlock.id.asc()) + .all() + ) + blocks = [serialize_block(block) for block in block_records] + tree = build_tree([block.copy() for block in blocks]) + configs = { + config.block_id: serialize_config(config) + for config in db.query(BlockConfig).filter(BlockConfig.template_id == template_id).all() + } + + return { + "data": { + "template": serialize_template(template), + "preview_html": generate_preview_html(blocks), + "blocks": blocks, + "tree": tree, + "configs": configs, + }, + "message": "ok", + } + + +@router.post("/{template_id}/blocks/{block_id}/config") +def save_block_config( + template_id: int, + block_id: str, + payload: BlockConfigPayload, + db: Session = Depends(get_db), +): + block = ( + db.query(TemplateBlock) + .filter(TemplateBlock.template_id == template_id, TemplateBlock.block_id == block_id) + .first() + ) + if not block: + raise HTTPException(status_code=404, detail="模板区域不存在") + + config = ( + db.query(BlockConfig) + .filter(BlockConfig.template_id == template_id, BlockConfig.block_id == block_id) + .first() + ) + if not config: + config = BlockConfig(template_id=template_id, block_id=block_id) + db.add(config) + + config.region_name = payload.region_name + config.region_type = payload.region_type + config.data_sources = json.dumps(payload.data_sources, ensure_ascii=False) + config.prompt = payload.prompt + config.output_format = payload.output_format + config.need_review = payload.need_review + config.remark = payload.remark + config.enabled = payload.enabled + + db.commit() + db.refresh(config) + return {"data": serialize_config(config), "message": "ok"} + + +def serialize_template(template: Template) -> dict: + return { + "id": template.id, + "name": template.name, + "type": template.type, + "version": template.version, + "original_file_path": template.original_file_path, + "status": template.status, + "created_by": template.created_by, + "created_at": template.created_at.isoformat() if template.created_at else None, + "updated_at": template.updated_at.isoformat() if template.updated_at else None, + } + + +def serialize_block(block: TemplateBlock) -> dict: + return { + "id": block.id, + "template_id": block.template_id, + "block_id": block.block_id, + "parent_block_id": block.parent_block_id, + "type": block.block_type, + "block_type": block.block_type, + "block_name": block.block_name, + "text": block.text_preview or "", + "text_preview": block.text_preview, + "level": block.level, + "sort_order": block.sort_order, + "table_rows": block.table_rows, + "table_cols": block.table_cols, + } + + +def serialize_config(config: BlockConfig) -> dict: + return { + "id": config.id, + "template_id": config.template_id, + "block_id": config.block_id, + "region_name": config.region_name, + "region_type": config.region_type, + "data_sources": json.loads(config.data_sources or "[]"), + "prompt": config.prompt, + "output_format": config.output_format, + "need_review": config.need_review, + "remark": config.remark, + "enabled": config.enabled, + } diff --git a/backend/app/main.py b/backend/app/main.py index 8d3b017..c9bfeed 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,6 +1,7 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware +from .api.data_sources import router as data_sources_router from .api.templates import router as templates_router app = FastAPI(title="AI 文档模板生成系统", version="0.1.0") @@ -14,6 +15,7 @@ app.add_middleware( ) app.include_router(templates_router) +app.include_router(data_sources_router) @app.get("/health") diff --git a/backend/app/schemas/template.py b/backend/app/schemas/template.py new file mode 100644 index 0000000..caca0ad --- /dev/null +++ b/backend/app/schemas/template.py @@ -0,0 +1,18 @@ +from pydantic import BaseModel, Field + + +class BlockConfigPayload(BaseModel): + region_name: str | None = None + region_type: str = "ai_generate" + data_sources: list[str] = Field(default_factory=list) + prompt: str | None = None + output_format: str = "formal_paragraph" + need_review: int = 1 + remark: str | None = None + enabled: int = 1 + + +class DataSourcePayload(BaseModel): + name: str + code: str + description: str | None = None diff --git a/docs/AI文档模板系统-像素级任务清单-20260701.md b/docs/AI文档模板系统-像素级任务清单-20260701.md index 0e190f0..a52fa9d 100644 --- a/docs/AI文档模板系统-像素级任务清单-20260701.md +++ b/docs/AI文档模板系统-像素级任务清单-20260701.md @@ -45,10 +45,10 @@ | 028 | 表格 → `