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")