Fix image generation model and purge logic

- Change NanoBanana Pro model from gemini-3-pro-image-preview to gemini-2.0-flash-exp-image (previous model doesn't exist)
- Purge endpoint now deletes generated images from /app_data/images/ when soft-deleted presentations are purged
- Add purged_images counter to purge response

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-02-27 16:18:34 +00:00
parent 8cbe01dfa6
commit 1280e40eb2
2 changed files with 37 additions and 4 deletions

View file

@ -12,6 +12,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from models.sql.client import ClientModel
from models.sql.master_deck import MasterDeckModel
from models.sql.presentation import PresentationModel
from models.sql.slide import SlideModel
from models.sql.user import UserModel
from services import audit_service
from services.access_service import get_accessible_client_ids
@ -361,7 +362,7 @@ async def purge_deleted_storage(
admin: UserModel = Depends(require_client_admin),
session: AsyncSession = Depends(get_async_session),
):
"""Hard-delete files for soft-deleted presentations."""
"""Hard-delete files for soft-deleted presentations (exports + generated images)."""
if admin.role != "super_admin":
raise HTTPException(status_code=403, detail="Super admin only")
@ -376,8 +377,10 @@ async def purge_deleted_storage(
purged_files = 0
purged_bytes = 0
purged_presentations = 0
purged_images = 0
for p in deleted_presentations:
# Delete export files (PDF/PPTX)
if p.file_paths:
for path in p.file_paths:
if path and os.path.isfile(path):
@ -389,6 +392,30 @@ async def purge_deleted_storage(
except OSError:
pass
p.file_paths = []
# Delete generated images from slides
slides_stmt = select(SlideModel).where(SlideModel.presentation == p.id)
slides_result = await session.execute(slides_stmt)
slides = slides_result.scalars().all()
for slide in slides:
if slide.content and isinstance(slide.content, dict):
# Extract image path from content.image.__image_url__
image_data = slide.content.get("image")
if image_data and isinstance(image_data, dict):
image_url = image_data.get("__image_url__")
if image_url and image_url.startswith("/app_data/images/"):
# Convert URL to filesystem path
image_path = image_url.lstrip("/")
if os.path.isfile(image_path):
try:
size = os.path.getsize(image_path)
os.remove(image_path)
purged_images += 1
purged_bytes += size
except OSError:
pass
purged_presentations += 1
await session.commit()
@ -397,12 +424,18 @@ async def purge_deleted_storage(
user_id=admin.id,
action="admin_purge",
resource_type="storage",
details={"presentations": purged_presentations, "files": purged_files, "bytes": purged_bytes},
details={
"presentations": purged_presentations,
"files": purged_files,
"images": purged_images,
"bytes": purged_bytes
},
)
return {
"ok": True,
"purged_presentations": purged_presentations,
"purged_files": purged_files,
"purged_images": purged_images,
"purged_bytes": purged_bytes,
}

View file

@ -201,9 +201,9 @@ class ImageGenerationService:
async def generate_image_nanobanana_pro(
self, prompt: str, output_directory: str
) -> str:
"""Generate image using NanoBanana Pro (gemini-3-pro-image-preview)."""
"""Generate image using NanoBanana Pro (gemini-2.0-flash-exp-image)."""
return await self._generate_image_google(
prompt, output_directory, "gemini-3-pro-image-preview"
prompt, output_directory, "gemini-2.0-flash-exp-image"
)
async def get_image_from_pexels(self, prompt: str) -> str: