feat: 增加内容生成配置管理
This commit is contained in:
@@ -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,
|
||||
}
|
||||
@@ -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"])
|
||||
|
||||
Reference in New Issue
Block a user