Merge pull request #475 from presenton/feat/updated-image-asset

feat/updated image asset
This commit is contained in:
Shiva Raj Badu 2026-03-29 18:02:10 +05:45 committed by GitHub
commit 61cc0d3d8c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 50 additions and 10 deletions

View file

@ -7,10 +7,23 @@ from sqlalchemy import JSON, Column, DateTime
from sqlmodel import Field, SQLModel
from utils.datetime_utils import get_current_utc_datetime
from utils.get_env import get_app_data_directory_env
from utils.get_env import get_app_data_directory_env, get_next_public_fast_api_env
from utils.path_helpers import get_resource_path
def _with_fastapi_origin(path: str) -> str:
"""Prefix relative web paths with FastAPI origin when available."""
if path.startswith("http://") or path.startswith("https://"):
return path
fastapi_origin = (get_next_public_fast_api_env() or "").strip()
if not fastapi_origin:
return path
normalized_path = path if path.startswith("/") else f"/{path}"
return f"{fastapi_origin.rstrip('/')}{normalized_path}"
class ImageAsset(SQLModel, table=True):
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
created_at: datetime = Field(
@ -36,6 +49,10 @@ class ImageAsset(SQLModel, table=True):
if path.startswith("http://") or path.startswith("https://"):
return path
# Already a web path under known mounts
if path.startswith("/app_data/") or path.startswith("/static/"):
return _with_fastapi_origin(path)
# Normalize filesystem path
real_path = os.path.realpath(path)
@ -46,7 +63,7 @@ class ImageAsset(SQLModel, table=True):
if real_path.startswith(app_data_dir_real):
rel = os.path.relpath(real_path, app_data_dir_real)
rel_web = rel.replace(os.sep, "/")
return f"/app_data/{rel_web}"
return _with_fastapi_origin(f"/app_data/{rel_web}")
# Map packaged static assets to /static/...
static_root = get_resource_path("static")
@ -54,7 +71,7 @@ class ImageAsset(SQLModel, table=True):
if real_path.startswith(static_root_real):
rel = os.path.relpath(real_path, static_root_real)
rel_web = rel.replace(os.sep, "/")
return f"/static/{rel_web}"
return _with_fastapi_origin(f"/static/{rel_web}")
# Fallback: return the original path (may be absolute or relative);
# frontend can decide how to handle unusual cases.

View file

@ -1,5 +1,6 @@
import uvicorn
import argparse
import os
from api.main import app
if __name__ == "__main__":
@ -12,10 +13,14 @@ if __name__ == "__main__":
)
args = parser.parse_args()
reload = args.reload == "true"
host = "127.0.0.1"
# Provide a predictable public URL for services that need absolute asset links.
os.environ.setdefault("FASTAPI_PUBLIC_URL", f"http://{host}:{args.port}")
uvicorn.run(
"api.main:app",
host="127.0.0.1",
host=host,
port=args.port,
log_level="info",
reload=reload,

View file

@ -12,6 +12,7 @@ from models.sql.image_asset import ImageAsset
from utils.get_env import (
get_dall_e_3_quality_env,
get_gpt_image_1_5_quality_env,
get_next_public_fast_api_env,
get_pexels_api_key_env,
)
from utils.get_env import get_pixabay_api_key_env
@ -59,6 +60,17 @@ class ImageGenerationService:
def is_stock_provider_selected(self):
return is_pixels_selected() or is_pixabay_selected()
def _to_frontend_url(self, path: str) -> str:
if path.startswith("http://") or path.startswith("https://"):
return path
fastapi_origin = (get_next_public_fast_api_env() or "").strip()
if not fastapi_origin:
return path
normalized_path = path if path.startswith("/") else f"/{path}"
return f"{fastapi_origin.rstrip('/')}{normalized_path}"
async def generate_image(self, prompt: ImagePrompt) -> str | ImageAsset:
"""
Generates an image based on the provided prompt.
@ -69,11 +81,11 @@ class ImageGenerationService:
"""
if self.is_image_generation_disabled:
print("Image generation is disabled. Using placeholder image.")
return "/static/images/placeholder.jpg"
return self._to_frontend_url("/static/images/placeholder.jpg")
if not self.image_gen_func:
print("No image generation function found. Using placeholder image.")
return "/static/images/placeholder.jpg"
return self._to_frontend_url("/static/images/placeholder.jpg")
image_prompt = prompt.get_image_prompt(
with_theme=not self.is_stock_provider_selected()
@ -99,11 +111,13 @@ class ImageGenerationService:
"theme_prompt": prompt.theme_prompt,
},
)
elif image_path.startswith("/app_data/") or image_path.startswith("/static/"):
return self._to_frontend_url(image_path)
raise Exception(f"Image not found at {image_path}")
except Exception as e:
print(f"Error generating image: {e}")
return "/static/images/placeholder.jpg"
return self._to_frontend_url("/static/images/placeholder.jpg")
async def generate_image_openai(
self, prompt: str, output_directory: str, model: str, quality: str

View file

@ -142,3 +142,7 @@ def get_codex_model_env():
def get_migrate_database_on_startup_env():
return os.getenv("MIGRATE_DATABASE_ON_STARTUP")
def get_next_public_fast_api_env():
return os.getenv("FASTAPI_PUBLIC_URL")

View file

@ -111,14 +111,14 @@ export const PresentationCard = ({
<img src="/card_bg.svg" alt="" className="absolute top-0 left-0 w-full h-full object-cover" />
<div className="scale-[0.75] mt-4 border border-gray-300 rounded-lg overflow-hidden">
<SlideScale slide={firstSlide} />
<SlideScale slide={firstSlide} isClickable={true} />
</div>
<div className="w-full py-3 px-5 mt-auto z-40 relative bg-white border-t border-[#EDEEEF]">
<div className="flex items-center justify-between gap-7 w-full">
<div className="flex flex-col items-start gap-1">
<div className="text-sm text-[#191919] font-semibold overflow-hidden line-clamp-1">
<MarkdownRenderer content={title} className="text-sm mb-0 text-[#191919] font-semibold overflow-hidden line-clamp-1" />
<MarkdownRenderer content={title} className="text-sm mb-0 font-syne text-[#191919] font-semibold overflow-hidden line-clamp-1" />
</div>
<p className="text-[#808080] text-sm font-syne">
{new Date(presentation?.created_at).toLocaleDateString()}

View file

@ -76,7 +76,7 @@ const SlideScale = ({
} as React.CSSProperties}
>
{/* <div
{isClickable && <div
className="absolute inset-0 bg-transparent z-30 w-full h-full select-none"
aria-hidden="true"