feat: 增加内容生成配置管理

This commit is contained in:
2026-08-03 17:23:42 +08:00
parent 3f8ff30bbc
commit 8ee4d65b7b
17 changed files with 1348 additions and 43 deletions

View File

@@ -0,0 +1,229 @@
from __future__ import annotations
from typing import Literal
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import func, select
from sqlalchemy.orm import Session
from app.api.pagination import page_result
from app.core.database import get_db
from app.core.dependencies import get_current_admin
from app.core.responses import api_success
from app.models.admin import Admin
from app.models.ai_config import ContentGenerationConfig
from app.schemas.admin import (
ContentGenerationConfigSaveRequest,
ContentGenerationPreviewRequest,
ContentGenerationTestRequest,
)
from app.services.admin_service import OperationLogService
from app.services.content_generation_config_service import (
SAMPLE_VALUES,
ContentGenerationConfigService,
ContentGenerationType,
config_detail,
)
router = APIRouter()
@router.get("/content-generation/config/{config_type}")
def get_content_generation_config(
config_type: Literal["help_card", "share_draft"],
db: Session = Depends(get_db),
current_admin: Admin = Depends(get_current_admin),
) -> dict:
row = _config_with_admin(db, config_type)
return api_success(config_detail(config_type, row[0], row[1].name if row and row[1] else None) if row else config_detail(config_type, None))
@router.put("/content-generation/config/{config_type}")
def save_content_generation_config(
config_type: Literal["help_card", "share_draft"],
payload: ContentGenerationConfigSaveRequest,
db: Session = Depends(get_db),
current_admin: Admin = Depends(get_current_admin),
) -> dict:
config = ContentGenerationConfigService.save(
db,
config_type=config_type,
template_content=payload.templateContent,
instruction_content=payload.instructionContent,
updated_by=current_admin.id,
)
OperationLogService.write(
db,
admin_id=current_admin.id,
module="content_generation",
action=f"save_{config_type}",
target_id=config.id,
)
db.commit()
db.refresh(config)
return api_success(config_detail(config_type, config, current_admin.name))
@router.post("/content-generation/config/{config_type}/reset")
def reset_content_generation_config(
config_type: Literal["help_card", "share_draft"],
db: Session = Depends(get_db),
current_admin: Admin = Depends(get_current_admin),
) -> dict:
config = ContentGenerationConfigService.reset(
db,
config_type=config_type,
updated_by=current_admin.id,
)
OperationLogService.write(
db,
admin_id=current_admin.id,
module="content_generation",
action=f"reset_{config_type}",
target_id=config.id,
)
db.commit()
db.refresh(config)
return api_success(config_detail(config_type, config, current_admin.name))
@router.get("/content-generation/config/{config_type}/history")
def content_generation_history(
config_type: Literal["help_card", "share_draft"],
page: int = Query(default=1, ge=1),
pageSize: int = Query(default=10, ge=5, le=100),
db: Session = Depends(get_db),
current_admin: Admin = Depends(get_current_admin),
) -> dict:
total = db.scalar(
select(func.count(ContentGenerationConfig.id)).where(ContentGenerationConfig.config_type == config_type)
) or 0
current_id = db.scalar(
select(ContentGenerationConfig.id)
.where(ContentGenerationConfig.config_type == config_type)
.order_by(ContentGenerationConfig.updated_at.desc(), ContentGenerationConfig.id.desc())
.limit(1)
)
rows = db.execute(
select(ContentGenerationConfig, Admin)
.outerjoin(Admin, Admin.id == ContentGenerationConfig.updated_by)
.where(ContentGenerationConfig.config_type == config_type)
.order_by(ContentGenerationConfig.updated_at.desc(), ContentGenerationConfig.id.desc())
.offset((page - 1) * pageSize)
.limit(pageSize)
).all()
items = [_history_item(config, admin, current_id=current_id) for config, admin in rows]
return api_success(page_result(items, total=total, page=page, page_size=pageSize))
@router.get("/content-generation/config/{config_type}/history/{config_id}")
def content_generation_history_detail(
config_type: Literal["help_card", "share_draft"],
config_id: int,
db: Session = Depends(get_db),
current_admin: Admin = Depends(get_current_admin),
) -> dict:
row = db.execute(
select(ContentGenerationConfig, Admin)
.outerjoin(Admin, Admin.id == ContentGenerationConfig.updated_by)
.where(
ContentGenerationConfig.id == config_id,
ContentGenerationConfig.config_type == config_type,
)
).first()
if row is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="内容生成配置版本不存在")
return api_success(config_detail(config_type, row[0], row[1].name if row[1] else None))
@router.post("/content-generation/config/{config_type}/history/{config_id}/restore")
def restore_content_generation_config(
config_type: Literal["help_card", "share_draft"],
config_id: int,
db: Session = Depends(get_db),
current_admin: Admin = Depends(get_current_admin),
) -> dict:
restored = ContentGenerationConfigService.restore(
db,
config_type=config_type,
source_config_id=config_id,
updated_by=current_admin.id,
)
OperationLogService.write(
db,
admin_id=current_admin.id,
module="content_generation",
action=f"restore_{config_type}",
target_id=restored.id,
)
db.commit()
db.refresh(restored)
return api_success(config_detail(config_type, restored, current_admin.name))
@router.post("/content-generation/preview")
def preview_content_generation(
payload: ContentGenerationPreviewRequest,
current_admin: Admin = Depends(get_current_admin),
) -> dict:
return api_success({"content": ContentGenerationConfigService.preview(payload.configType, payload.templateContent)})
@router.post("/content-generation/test")
def test_content_generation(
payload: ContentGenerationTestRequest,
db: Session = Depends(get_db),
current_admin: Admin = Depends(get_current_admin),
) -> dict:
values = dict(SAMPLE_VALUES)
values.update(
{
"issue": payload.sampleText.strip()[:3000],
"summary": payload.sampleText.strip()[:6000],
"current_focus": "(请由 AI 根据测试材料整理)",
"next_observation": "(请由 AI 根据测试材料整理)",
"teacher_question": "(请由 AI 根据测试材料整理)",
}
)
generated, used_fallback = ContentGenerationConfigService.generate_values(
db,
config_type=payload.configType,
instruction_content=payload.instructionContent,
values=values,
user_id=None,
)
content = ContentGenerationConfigService.render(payload.configType, payload.templateContent, generated)
OperationLogService.write(
db,
admin_id=current_admin.id,
module="content_generation",
action=f"test_{payload.configType}",
)
db.commit()
return api_success({"content": content, "usedFallback": used_fallback})
def _config_with_admin(db: Session, config_type: ContentGenerationType):
return db.execute(
select(ContentGenerationConfig, Admin)
.outerjoin(Admin, Admin.id == ContentGenerationConfig.updated_by)
.where(ContentGenerationConfig.config_type == config_type)
.order_by(ContentGenerationConfig.updated_at.desc(), ContentGenerationConfig.id.desc())
.limit(1)
).first()
def _history_item(config: ContentGenerationConfig, admin: Admin | None, *, current_id: int | None) -> dict:
compact = " ".join(config.template_content.split())
return {
"id": config.id,
"configType": config.config_type,
"preview": compact[:140],
"templateCharCount": len(config.template_content),
"instructionCharCount": len(config.instruction_content),
"changeType": config.change_type,
"sourceConfigId": config.source_config_id,
"updatedByName": admin.name if admin else "未知管理员",
"updatedAt": config.updated_at,
"isCurrent": config.id == current_id,
}

View File

@@ -4,6 +4,7 @@ from fastapi import APIRouter
from app.api import (
admin_auth,
admin_content_generation,
admin_agent_records,
admin_dashboard,
admin_entitlements,
@@ -24,6 +25,7 @@ api_router.include_router(auth.router, prefix="/auth", tags=["auth"])
api_router.include_router(user.router, prefix="/user", tags=["user"])
api_router.include_router(chat.router, prefix="/chat", tags=["chat"])
api_router.include_router(admin_auth.router, prefix="/admin", tags=["admin"])
api_router.include_router(admin_content_generation.router, prefix="/admin", tags=["admin-content-generation"])
api_router.include_router(admin_agent_records.router, prefix="/admin", tags=["admin-agent-records"])
api_router.include_router(admin_dashboard.router, prefix="/admin", tags=["admin"])
api_router.include_router(admin_entitlements.router, prefix="/admin", tags=["admin-entitlements"])