127 lines
6.0 KiB
Python
127 lines
6.0 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from fastapi import HTTPException
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.enums.private_portrait import (
|
|
PRIVATE_PORTRAIT_REMOTE_PROJECT_NAME,
|
|
PRIVATE_PORTRAIT_VIRTUAL_GROUP_TYPE,
|
|
PrivatePortraitAssetGroupStatus,
|
|
PrivatePortraitEventSource,
|
|
PrivatePortraitEventStatus,
|
|
PrivatePortraitEventType,
|
|
PrivatePortraitLibraryType,
|
|
PrivatePortraitProjectStatus,
|
|
)
|
|
from app.models.private_portrait import PrivatePortraitAssetGroup
|
|
from app.schemas.private_portrait import PrivatePortraitAssetCreate, PrivatePortraitProjectUpdate, PrivatePortraitVirtualProjectCreate
|
|
from app.services.operation_log_service import log_operation_error, log_operation_event
|
|
from app.services.private_portrait.ark_client import ArkPrivateAssetClient
|
|
from app.services.private_portrait.asset_service import create_asset
|
|
from app.services.private_portrait.project_service import create_project, refresh_project_counters, update_project
|
|
from app.utils.id_gen import generate_id
|
|
|
|
DOMAIN = "private_portrait"
|
|
|
|
|
|
def _json(data) -> str | None:
|
|
if data is None:
|
|
return None
|
|
return json.dumps(data, ensure_ascii=False, default=str)
|
|
|
|
|
|
def _remote_group_name(user_id: str, project_name: str) -> str:
|
|
safe_name = "".join(ch if ch.isalnum() or ch in "-_" else "_" for ch in project_name.strip())[:80]
|
|
return f"virtual-{user_id}-{safe_name}"[:128]
|
|
|
|
|
|
async def create_virtual_project(db: AsyncSession, *, user_id: str, payload: PrivatePortraitVirtualProjectCreate):
|
|
project = await create_project(
|
|
db,
|
|
user_id=user_id,
|
|
payload=payload, # type: ignore[arg-type]
|
|
library_type=PrivatePortraitLibraryType.AIGC_VIRTUAL.value,
|
|
status=PrivatePortraitProjectStatus.CREATING_REMOTE_GROUP.value,
|
|
remote_project_name=PRIVATE_PORTRAIT_REMOTE_PROJECT_NAME,
|
|
)
|
|
remote_group_name = _remote_group_name(user_id, project.name)
|
|
log_operation_event(
|
|
domain=DOMAIN,
|
|
event_type=PrivatePortraitEventType.VIRTUAL_ASSET_GROUP_CREATE_REMOTE_START.value,
|
|
event_status=PrivatePortraitEventStatus.PENDING.value,
|
|
source=PrivatePortraitEventSource.API.value,
|
|
user_id=user_id,
|
|
project_id=project.id,
|
|
detail={"remote_group_name": remote_group_name, "remote_project_name": project.remote_project_name, "group_type": PRIVATE_PORTRAIT_VIRTUAL_GROUP_TYPE},
|
|
)
|
|
try:
|
|
resp = await ArkPrivateAssetClient().create_asset_group(
|
|
project_name=project.remote_project_name,
|
|
name=remote_group_name,
|
|
description=project.description,
|
|
group_type=PRIVATE_PORTRAIT_VIRTUAL_GROUP_TYPE,
|
|
)
|
|
remote_group_id = resp.get("Id") or resp.get("GroupId") or resp.get("groupId")
|
|
if not remote_group_id:
|
|
raise RuntimeError("CreateAssetGroup 未返回素材组 ID")
|
|
group = PrivatePortraitAssetGroup(
|
|
id=generate_id(),
|
|
user_id=user_id,
|
|
project_id=project.id,
|
|
library_type=PrivatePortraitLibraryType.AIGC_VIRTUAL.value,
|
|
remote_group_id=remote_group_id,
|
|
remote_group_name=remote_group_name,
|
|
remote_project_name=project.remote_project_name,
|
|
group_type=PRIVATE_PORTRAIT_VIRTUAL_GROUP_TYPE,
|
|
status=PrivatePortraitAssetGroupStatus.ACTIVE.value,
|
|
raw_response_json=_json(resp),
|
|
)
|
|
db.add(group)
|
|
project.status = PrivatePortraitProjectStatus.ACTIVE.value
|
|
await refresh_project_counters(db, [project.id])
|
|
await db.flush()
|
|
await db.refresh(project)
|
|
log_operation_event(
|
|
domain=DOMAIN,
|
|
event_type=PrivatePortraitEventType.VIRTUAL_ASSET_GROUP_CREATE_REMOTE_SUCCESS.value,
|
|
event_status=PrivatePortraitEventStatus.SUCCESS.value,
|
|
source=PrivatePortraitEventSource.API.value,
|
|
user_id=user_id,
|
|
project_id=project.id,
|
|
group_id=group.id,
|
|
detail={"remote_group_id": remote_group_id, "remote_group_name": remote_group_name, "remote_project_name": project.remote_project_name},
|
|
)
|
|
return project
|
|
except Exception as exc:
|
|
project.status = PrivatePortraitProjectStatus.CREATE_GROUP_FAILED.value
|
|
await db.flush()
|
|
log_operation_error(
|
|
domain=DOMAIN,
|
|
event_type=PrivatePortraitEventType.VIRTUAL_ASSET_GROUP_CREATE_REMOTE_FAILED.value,
|
|
source=PrivatePortraitEventSource.API.value,
|
|
user_id=user_id,
|
|
project_id=project.id,
|
|
exc=exc,
|
|
)
|
|
raise HTTPException(status_code=502, detail=f"创建火山虚拟人像素材组失败:{exc}") from exc
|
|
|
|
|
|
async def update_virtual_project(db: AsyncSession, *, user_id: str, project_id: str, payload: PrivatePortraitProjectUpdate):
|
|
project = await update_project(db, user_id=user_id, project_id=project_id, payload=payload, library_type=PrivatePortraitLibraryType.AIGC_VIRTUAL.value)
|
|
# 远程同步失败不影响本地更新,记录日志便于排查。
|
|
try:
|
|
# 只同步当前激活组。
|
|
from app.services.private_portrait.asset_service import get_project_active_group
|
|
|
|
group = await get_project_active_group(db, user_id=user_id, project_id=project_id, library_type=PrivatePortraitLibraryType.AIGC_VIRTUAL.value)
|
|
await ArkPrivateAssetClient().update_asset_group(project_name=project.remote_project_name, group_id=group.remote_group_id, name=group.remote_group_name, title=project.name, description=project.description)
|
|
except Exception as exc:
|
|
log_operation_error(domain=DOMAIN, event_type=PrivatePortraitEventType.ASSET_GROUP_UPDATE_REMOTE_FAILED.value, source=PrivatePortraitEventSource.API.value, user_id=user_id, project_id=project_id, exc=exc)
|
|
return project
|
|
|
|
|
|
async def create_virtual_asset(db: AsyncSession, *, user_id: str, project_id: str, payload: PrivatePortraitAssetCreate):
|
|
return await create_asset(db, user_id=user_id, project_id=project_id, payload=payload, library_type=PrivatePortraitLibraryType.AIGC_VIRTUAL.value)
|