Merge pull request #475 from presenton/feat/updated-image-asset
feat/updated image asset
This commit is contained in:
commit
61cc0d3d8c
6 changed files with 50 additions and 10 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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()}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue