PMFlow/backend/app/services/task_brief/mixin_bundle_crud.py

171 lines
6.8 KiB
Python

from __future__ import annotations
from .shared import *
class TaskBriefBundleCrudMixin:
@classmethod
async def _get_or_init_bundle(cls, db: AsyncSession, *, tenant_id: int) -> tuple[Integration, dict[str, Any]]:
row = (
await db.execute(
select(Integration).where(
and_(
Integration.tenant_id == int(tenant_id),
Integration.type == INTEGRATION_TYPE,
)
)
)
).scalar_one_or_none()
if row is None:
row = Integration(
tenant_id=int(tenant_id),
type=INTEGRATION_TYPE,
is_active=True,
config={
"templates": [],
"runs": [],
"briefs": [],
},
)
db.add(row)
await db.flush()
bundle = cls._safe_dict(row.config)
changed = False
for key in ("templates", "runs", "briefs"):
if not isinstance(bundle.get(key), list):
bundle[key] = []
changed = True
existing_templates = [item for item in bundle.get("templates", []) if isinstance(item, dict)]
if not existing_templates:
bundle["templates"] = cls._build_system_templates()
existing_templates = [item for item in bundle["templates"] if isinstance(item, dict)]
changed = True
else:
upgraded_default_template = False
latest_default_content = cls._load_default_template_content()
for item in existing_templates:
if str(item.get("id") or "") != DEFAULT_TEMPLATE_ID:
continue
current_content = str(item.get("content") or "").strip()
if current_content and not cls._is_default_template_fallback(current_content):
break
item["name"] = str(item.get("name") or "").strip() or DEFAULT_TEMPLATE_NAME
item["description"] = str(item.get("description") or "").strip() or DEFAULT_TEMPLATE_DESCRIPTION
item["content"] = latest_default_content
item["updated_at"] = now_cn_naive().isoformat()
if item.get("updated_by") is None:
item["updated_by"] = 0
upgraded_default_template = True
break
if upgraded_default_template:
bundle["templates"] = existing_templates
changed = True
existing_templates, added_presets = cls._ensure_system_templates(existing_templates)
if added_presets:
bundle["templates"] = existing_templates
changed = True
existing_briefs = [item for item in bundle.get("briefs", []) if isinstance(item, dict)]
existing_runs = [item for item in bundle.get("runs", []) if isinstance(item, dict)]
if not existing_briefs and not existing_runs:
seed_briefs, seed_runs = cls._load_seed_history()
if seed_briefs and seed_runs:
bundle["briefs"] = seed_briefs
bundle["runs"] = seed_runs
changed = True
if changed:
row.config = bundle
await db.flush()
return row, bundle
@staticmethod
def _sort_by_time_desc(items: list[dict[str, Any]], key: str = "created_at") -> list[dict[str, Any]]:
return sorted(items, key=lambda x: str(x.get(key) or ""), reverse=True)
@classmethod
async def list_center(cls, db: AsyncSession, *, tenant_id: int) -> list[dict[str, Any]]:
_, bundle = await cls._get_or_init_bundle(db, tenant_id=tenant_id)
briefs = [dict(item) for item in bundle.get("briefs", []) if isinstance(item, dict)]
return cls._sort_by_time_desc(briefs, key="created_at")
@classmethod
async def list_templates(cls, db: AsyncSession, *, tenant_id: int) -> list[dict[str, Any]]:
_, bundle = await cls._get_or_init_bundle(db, tenant_id=tenant_id)
templates = [dict(item) for item in bundle.get("templates", []) if isinstance(item, dict)]
return cls._sort_by_time_desc(templates, key="updated_at")
@classmethod
async def create_template(
cls,
db: AsyncSession,
*,
tenant_id: int,
operator_user_id: int,
name: str,
content: str,
description: Optional[str] = None,
) -> dict[str, Any]:
row, bundle = await cls._get_or_init_bundle(db, tenant_id=tenant_id)
now_text = now_cn_naive().isoformat()
item = {
"id": f"tpl_{uuid4().hex[:12]}",
"name": str(name or "").strip()[:120] or "默认模板",
"description": str(description or "").strip()[:300] or None,
"content": str(content or "").strip(),
"updated_at": now_text,
"updated_by": int(operator_user_id),
"created_at": now_text,
}
templates = bundle.get("templates") if isinstance(bundle.get("templates"), list) else []
templates.append(item)
bundle["templates"] = templates
row.config = bundle
await db.flush()
return item
@classmethod
async def update_template(
cls,
db: AsyncSession,
*,
tenant_id: int,
operator_user_id: int,
template_id: str,
name: Optional[str] = None,
content: Optional[str] = None,
description: Optional[str] = None,
) -> dict[str, Any] | None:
row, bundle = await cls._get_or_init_bundle(db, tenant_id=tenant_id)
templates = bundle.get("templates") if isinstance(bundle.get("templates"), list) else []
target = None
for item in templates:
if not isinstance(item, dict):
continue
if str(item.get("id") or "") == str(template_id or ""):
target = item
break
if target is None:
return None
if name is not None:
target["name"] = str(name or "").strip()[:120] or target.get("name") or "默认模板"
if content is not None:
target["content"] = str(content or "").strip()
if description is not None:
target["description"] = str(description or "").strip()[:300] or None
target["updated_at"] = now_cn_naive().isoformat()
target["updated_by"] = int(operator_user_id)
bundle["templates"] = templates
row.config = bundle
await db.flush()
return dict(target)
@classmethod
async def list_runs(cls, db: AsyncSession, *, tenant_id: int) -> list[dict[str, Any]]:
_, bundle = await cls._get_or_init_bundle(db, tenant_id=tenant_id)
runs = [dict(item) for item in bundle.get("runs", []) if isinstance(item, dict)]
return cls._sort_by_time_desc(runs, key="generated_at")