feat: add template detail and config APIs

This commit is contained in:
zwt13703
2026-07-01 20:31:54 +08:00
parent 611e089c9e
commit 217af9bc99
6 changed files with 188 additions and 4 deletions
+27
View File
@@ -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"}
+125
View File
@@ -1,8 +1,16 @@
import json
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from ..database import get_db 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.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 from ..services.template_service import create_template_record, save_template_blocks, store_template_file
@@ -37,3 +45,120 @@ def upload_template(
}, },
"message": "ok", "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,
}
+2
View File
@@ -1,6 +1,7 @@
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from .api.data_sources import router as data_sources_router
from .api.templates import router as templates_router from .api.templates import router as templates_router
app = FastAPI(title="AI 文档模板生成系统", version="0.1.0") app = FastAPI(title="AI 文档模板生成系统", version="0.1.0")
@@ -14,6 +15,7 @@ app.add_middleware(
) )
app.include_router(templates_router) app.include_router(templates_router)
app.include_router(data_sources_router)
@app.get("/health") @app.get("/health")
+18
View File
@@ -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
@@ -45,10 +45,10 @@
| 028 | 表格 → `<table data-block-id>` 转换 + 边框样式 | 后端 | 0.25d | ✅ 已完成 | | 028 | 表格 → `<table data-block-id>` 转换 + 边框样式 | 后端 | 0.25d | ✅ 已完成 |
| 029 | 拼接完整 HTML 字符串(带内联 CSS) | 后端 | 0.25d | ✅ 已完成 | | 029 | 拼接完整 HTML 字符串(带内联 CSS) | 后端 | 0.25d | ✅ 已完成 |
| | **查询 + 配置接口** | | | | | | **查询 + 配置接口** | | | |
| 030 | 实现 `GET /api/templates/{id}` 组装全部数据 | 后端 | 0.5d | ⏳ 未完成 | | 030 | 实现 `GET /api/templates/{id}` 组装全部数据 | 后端 | 0.5d | ✅ 已完成 |
| 031 | 实现 `POST /api/templates/{id}/blocks/{blockId}/config` upsert | 后端 | 0.25d | ⏳ 未完成 | | 031 | 实现 `POST /api/templates/{id}/blocks/{blockId}/config` upsert | 后端 | 0.25d | ✅ 已完成 |
| 032 | 实现 `GET /api/data-sources` 返回预设列表 | 后端 | 0.25d | ⏳ 未完成 | | 032 | 实现 `GET /api/data-sources` 返回预设列表 | 后端 | 0.25d | ✅ 已完成 |
| 033 | 实现 `POST /api/data-sources` 新增数据源 | 后端 | 0.25d | ⏳ 未完成 | | 033 | 实现 `POST /api/data-sources` 新增数据源 | 后端 | 0.25d | ✅ 已完成 |
| | **四栏布局** | | | | | | **四栏布局** | | | |
| 034 | 实现四栏 CSS Grid 布局 | 前端 | 0.5d | ⏳ 未完成 | | 034 | 实现四栏 CSS Grid 布局 | 前端 | 0.5d | ⏳ 未完成 |
| 035 | 实现左侧菜单 shell 组件 | 前端 | 0.25d | ⏳ 未完成 | | 035 | 实现左侧菜单 shell 组件 | 前端 | 0.25d | ⏳ 未完成 |
+12
View File
@@ -47,3 +47,15 @@
5. 增加 HTML 转义,避免 Word 文本中的特殊字符破坏预览结构。 5. 增加 HTML 转义,避免 Word 文本中的特殊字符破坏预览结构。
6. 使用示例 blocks 验证标题、段落、表格 HTML 均生成正确。 6. 使用示例 blocks 验证标题、段落、表格 HTML 均生成正确。
- **执行结果**: 完成任务 026-029,HTML 预览生成服务已满足基础预览要求。 - **执行结果**: 完成任务 026-029,HTML 预览生成服务已满足基础预览要求。
## 会话 ID: 20260701-query-config-api
- [2026-07-01 20:31:33]
- **执行原因**: 按任务清单继续完成查询与配置接口 030-033。
- **执行过程**:
1. 新增 `BlockConfigPayload``DataSourcePayload` 请求模型。
2. 实现 `GET /api/templates/{template_id}`,聚合模板、预览 HTML、blocks、tree 和 configs。
3. 实现 `POST /api/templates/{template_id}/blocks/{block_id}/config`,按 `(template_id, block_id)` 进行 upsert。
4. 新增 `GET /api/data-sources` 返回 MVP 预设数据源列表。
5. 新增 `POST /api/data-sources` 支持新增内存数据源并校验 code 唯一。
6. 执行语法检查和 FastAPI 路由注册检查。
- **执行结果**: 完成任务 030-033,模板详情、区域配置保存和数据源接口已可调用。