This commit is contained in:
2026-07-07 10:55:45 +08:00
23 changed files with 1552 additions and 80 deletions
+7 -6
View File
@@ -49,6 +49,7 @@ from app.services.admin_credit_record_service import list_admin_credit_records
from app.services.notification import create_notification
from app.services.auth import hash_password, verify_password
from app.services.operation_log import log_operation
from app.services.private_portrait.reference_resolver import batch_resolve_private_portrait_reference_display_urls
from app.services.resource_signed_url_service import build_resource_signed_url
from app.services.payment import sync_pending_orders, process_refund
from app.services.resource_capacity_service import batch_get_user_resource_capacity_usage, get_user_resource_capacity_usage
@@ -1449,15 +1450,15 @@ async def admin_list_generation_records(
query = query.offset(offset).limit(page_size)
result = await db.execute(query)
rows = result.all()
refs_map = await batch_resolve_private_portrait_reference_display_urls(
db,
{record.id: json.loads(record.media_references) if record.media_references else None for record, _username, _project_name, _industry, _industry_label in rows},
user_id=user_id,
)
items = []
for record, username, project_name, industry, industry_label in rows:
refs = None
if record.media_references:
try:
refs = json.loads(record.media_references)
except (json.JSONDecodeError, TypeError):
refs = None
refs = refs_map.get(record.id)
items.append({
"id": record.id,
"user_id": record.user_id,
+18 -8
View File
@@ -33,6 +33,7 @@ from app.services.resource_accounting_service import (
record_generation_record_generated_resource,
safe_file_size,
)
from app.services.private_portrait.reference_resolver import batch_resolve_private_portrait_reference_display_urls, resolve_private_portrait_reference_display_urls
from app.services.resource_signed_url_service import build_resource_signed_url
from app.services.resource_capacity_service import assert_user_resource_capacity_available
from app.services.generation_billing_service import (
@@ -59,9 +60,9 @@ router = APIRouter(prefix="/generation-records", tags=["generation"])
logger = logging.getLogger("videogen")
def _record_to_out(record: GenerationRecord, project_name: str) -> GenerationRecordOut:
refs = None
if record.media_references:
def _record_to_out(record: GenerationRecord, project_name: str, refs_override: list[dict] | None = None) -> GenerationRecordOut:
refs = refs_override
if refs is None and record.media_references:
try:
refs = json.loads(record.media_references)
except (json.JSONDecodeError, TypeError):
@@ -191,13 +192,18 @@ async def list_records(
result = await db.execute(query)
rows = result.all()
refs_map = await batch_resolve_private_portrait_reference_display_urls(
db,
{record.id: json.loads(record.media_references) if record.media_references else None for record, _project_name in rows},
user_id=current_user.id,
)
return {
"total": int(total),
"page": page,
"page_size": page_size,
"items": [
_record_to_out(record, project_name)
_record_to_out(record, project_name, refs_override=refs_map.get(record.id))
for record, project_name in rows
],
}
@@ -240,11 +246,12 @@ async def optimize(
row = existing.first()
if row:
record, project_name = row
refs = await resolve_private_portrait_reference_display_urls(db, json.loads(record.media_references) if record.media_references else None, user_id=current_user.id)
return OptimizeResult(
optimized_prompt=record.optimized_prompt or "",
text_credits_cost=record.text_credits_cost or 0.00,
text_tokens_used=record.text_tokens_used or 0,
record=_record_to_out(record, project_name),
record=_record_to_out(record, project_name, refs_override=refs),
)
# Check project exists and belongs to user
@@ -370,11 +377,12 @@ async def optimize(
record.text_tokens_used = token_usage["total_tokens"]
await db.flush()
refs = await resolve_private_portrait_reference_display_urls(db, json.loads(record.media_references) if record.media_references else None, user_id=current_user.id)
return OptimizeResult(
optimized_prompt=optimized,
text_credits_cost=round(text_credits, 2),
# text_tokens_used=token_usage["total_tokens"],
record=_record_to_out(record, project.name),
record=_record_to_out(record, project.name, refs_override=refs),
)
@@ -502,7 +510,8 @@ async def generate(
)
await db.flush()
return _record_to_out(record, project_name)
refs = await resolve_private_portrait_reference_display_urls(db, json.loads(record.media_references) if record.media_references else None, user_id=current_user.id)
return _record_to_out(record, project_name, refs_override=refs)
@router.post("/{record_id}/retry")
@@ -578,7 +587,8 @@ async def retry_generation(
)
await db.flush()
return _record_to_out(record, project_name)
refs = await resolve_private_portrait_reference_display_urls(db, json.loads(record.media_references) if record.media_references else None, user_id=current_user.id)
return _record_to_out(record, project_name, refs_override=refs)
@router.put("/{record_id}/prompt")
+11 -3
View File
@@ -36,6 +36,7 @@ from app.services.generation_billing_service import (
from app.services.generation_history_delete_service import batch_delete_generation_history_items
from app.services.generation_log_service import log_task_event
from app.services.generation_refund_service import mark_chat_generation_task_failed_and_refund_once
from app.services.private_portrait.reference_resolver import batch_resolve_private_portrait_reference_display_urls, resolve_private_portrait_reference_display_urls
from app.services.resource_capacity_service import assert_user_resource_capacity_available
from app.tasks.celery_app import celery_app
@@ -173,7 +174,8 @@ async def create_task(
await db.commit()
raise HTTPException(status_code=503, detail="任务队列投递失败,请稍后重试")
return record_to_out(task)
refs = await resolve_private_portrait_reference_display_urls(db, record_to_out(task).media_references, user_id=current_user.id)
return record_to_out(task, media_references=refs)
@router.get(
@@ -280,7 +282,12 @@ async def list_tasks(
)
else:
items_sorted = items
return GenerationAITaskListOut(total=total, items=[record_to_out(task=i, is_admin=is_admin) for i in items_sorted])
refs_map = await batch_resolve_private_portrait_reference_display_urls(
db,
{item.id: record_to_out(task=item, is_admin=is_admin).media_references for item in items_sorted},
user_id=None if is_admin else current_user.id,
)
return GenerationAITaskListOut(total=total, items=[record_to_out(task=i, is_admin=is_admin, media_references=refs_map.get(i.id)) for i in items_sorted])
@router.get(
@@ -521,7 +528,8 @@ async def get_task(
task = result.scalar_one_or_none()
if not task:
raise HTTPException(status_code=404, detail="任务不存在")
return record_to_out(task)
refs = await resolve_private_portrait_reference_display_urls(db, record_to_out(task).media_references, user_id=current_user.id)
return record_to_out(task, media_references=refs)
@router.delete(
+28 -11
View File
@@ -1,6 +1,6 @@
from __future__ import annotations
from urllib.parse import unquote
from urllib.parse import urlencode, unquote
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from fastapi.responses import RedirectResponse
@@ -12,6 +12,7 @@ from app.enums.private_portrait import (
PrivatePortraitEventSource,
PrivatePortraitEventStatus,
PrivatePortraitEventType,
PrivatePortraitProjectStatus,
PrivatePortraitRemoteDeleteStatus,
)
from app.models.private_portrait import PrivatePortraitAsset, PrivatePortraitProject
@@ -22,6 +23,7 @@ from app.schemas.private_portrait import (
PrivatePortraitDeleteOut,
PrivatePortraitConfigOut,
PrivatePortraitProjectCreate,
PrivatePortraitProjectCreateWithValidateOut,
PrivatePortraitProjectListOut,
PrivatePortraitProjectOut,
PrivatePortraitProjectUpdate,
@@ -88,10 +90,20 @@ async def get_my_private_portrait_config(current_user: User = Depends(get_curren
return await get_user_private_portrait_config(db, user_id=current_user.id)
@router.post("/private-portrait/projects", response_model=PrivatePortraitProjectOut)
@router.post("/private-portrait/projects", response_model=PrivatePortraitProjectCreateWithValidateOut)
async def create_private_portrait_project(payload: PrivatePortraitProjectCreate, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
project = await create_project(db, user_id=current_user.id, payload=payload)
out = project_to_out(project)
session = await create_validate_session(
db,
user_id=current_user.id,
project_id=project.id,
callback_redirect_url=payload.callback_redirect_url,
)
out = PrivatePortraitProjectCreateWithValidateOut(
project=project_to_out(project),
validate_session=validate_session_to_out(session),
poll_interval_ms=2000,
)
await db.commit()
return out
@@ -105,10 +117,11 @@ async def list_private_portrait_projects(
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
items, total = await list_projects(db, user_id=current_user.id, page=page, page_size=page_size, keyword=keyword, status=status)
query_status = status or PrivatePortraitProjectStatus.ACTIVE.value
items, total = await list_projects(db, user_id=current_user.id, page=page, page_size=page_size, keyword=keyword, status=query_status)
await refresh_project_counters(db, [item.id for item in items])
await db.commit()
items, total = await list_projects(db, user_id=current_user.id, page=page, page_size=page_size, keyword=keyword, status=status)
items, total = await list_projects(db, user_id=current_user.id, page=page, page_size=page_size, keyword=keyword, status=query_status)
return PrivatePortraitProjectListOut(items=[project_to_out(item) for item in items], total=total, page=page, page_size=page_size)
@@ -159,15 +172,19 @@ async def private_portrait_validate_callback(session_id: str, request: Request,
params.pop("session_id", None)
params.pop("redirect_url", None)
session = await handle_validate_callback(db, session_id=session_id, query_params=params)
redirect_session_id = session.id
redirect_status = session.status
redirect_result_code = session.result_code or ""
redirect_params = {
"session_id": session.id,
"status": session.status,
"resultCode": session.result_code or "",
}
if session.remote_group_id:
redirect_params["remote_group_id"] = session.remote_group_id
response = {"session_id": session.id, "status": session.status, "resultCode": session.result_code, "remote_group_id": session.remote_group_id}
await db.commit()
if redirect_url:
sep = "&" if "?" in redirect_url else "?"
url = f"{unquote(redirect_url)}{sep}session_id={redirect_session_id}&status={redirect_status}&resultCode={redirect_result_code}"
return RedirectResponse(url=url)
base_url = unquote(redirect_url)
sep = "&" if "?" in base_url else "?"
return RedirectResponse(url=f"{base_url}{sep}{urlencode(redirect_params)}")
return response