Spaces:
Sleeping
Sleeping
Commit ·
8fb6f8f
1
Parent(s): c4a64b4
nomenclature fix
Browse files- backend/app/main.py +24 -5
- backend/app/r2.py +24 -5
- backend/app/variation.py +8 -3
backend/app/main.py
CHANGED
|
@@ -535,7 +535,12 @@ def list_image_models(_user: dict = Depends(get_current_user)):
|
|
| 535 |
GENERATE_ADS_CONCURRENCY = 3
|
| 536 |
|
| 537 |
|
| 538 |
-
async def _save_creative_to_r2(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 539 |
"""Download image from Replicate URL and upload to R2. Returns (R2 presigned URL or None, R2 key or None)."""
|
| 540 |
try:
|
| 541 |
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
@@ -547,7 +552,7 @@ async def _save_creative_to_r2(replicate_url: str, creative_id: int, concept_nam
|
|
| 547 |
return None, None
|
| 548 |
if not image_bytes:
|
| 549 |
return None, None
|
| 550 |
-
key = key_for_creative(creative_id, concept_name)
|
| 551 |
r2_url = await asyncio.to_thread(
|
| 552 |
upload_creative_image,
|
| 553 |
image_bytes,
|
|
@@ -761,7 +766,12 @@ async def generate_ads(
|
|
| 761 |
)
|
| 762 |
if err or not url:
|
| 763 |
return {"creative_id": cid, "image_url": None, "error": err}
|
| 764 |
-
r2_url, r2_key = await _save_creative_to_r2(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 765 |
final_url = r2_url if r2_url else url
|
| 766 |
if r2_key and username:
|
| 767 |
await asyncio.to_thread(
|
|
@@ -856,7 +866,12 @@ async def _stream_generate_ads(
|
|
| 856 |
)
|
| 857 |
if err or not url:
|
| 858 |
return {"creative_id": cid, "image_url": None, "error": err}
|
| 859 |
-
r2_url, r2_key = await _save_creative_to_r2(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 860 |
final_url = r2_url if r2_url else url
|
| 861 |
if r2_key and username:
|
| 862 |
await asyncio.to_thread(
|
|
@@ -1062,7 +1077,11 @@ async def correct_image_endpoint(
|
|
| 1062 |
image_bytes = r.content
|
| 1063 |
concept = (entry.get("concept_name") or "image").strip()[:64]
|
| 1064 |
cid = entry.get("creative_id", 0)
|
| 1065 |
-
key = key_for_creative(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1066 |
r2_url = await asyncio.to_thread(upload_creative_image, image_bytes, key=key, content_type="image/png")
|
| 1067 |
if r2_url:
|
| 1068 |
gallery_update_entry(
|
|
|
|
| 535 |
GENERATE_ADS_CONCURRENCY = 3
|
| 536 |
|
| 537 |
|
| 538 |
+
async def _save_creative_to_r2(
|
| 539 |
+
replicate_url: str,
|
| 540 |
+
creative_id: int,
|
| 541 |
+
concept_name: str = "",
|
| 542 |
+
product_name: str = "",
|
| 543 |
+
) -> tuple[str | None, str | None]:
|
| 544 |
"""Download image from Replicate URL and upload to R2. Returns (R2 presigned URL or None, R2 key or None)."""
|
| 545 |
try:
|
| 546 |
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
|
|
| 552 |
return None, None
|
| 553 |
if not image_bytes:
|
| 554 |
return None, None
|
| 555 |
+
key = key_for_creative(creative_id, concept_name, product_name)
|
| 556 |
r2_url = await asyncio.to_thread(
|
| 557 |
upload_creative_image,
|
| 558 |
image_bytes,
|
|
|
|
| 766 |
)
|
| 767 |
if err or not url:
|
| 768 |
return {"creative_id": cid, "image_url": None, "error": err}
|
| 769 |
+
r2_url, r2_key = await _save_creative_to_r2(
|
| 770 |
+
url,
|
| 771 |
+
cid,
|
| 772 |
+
(item.get("concept_name") or "").strip(),
|
| 773 |
+
(body.product_name or "").strip(),
|
| 774 |
+
)
|
| 775 |
final_url = r2_url if r2_url else url
|
| 776 |
if r2_key and username:
|
| 777 |
await asyncio.to_thread(
|
|
|
|
| 866 |
)
|
| 867 |
if err or not url:
|
| 868 |
return {"creative_id": cid, "image_url": None, "error": err}
|
| 869 |
+
r2_url, r2_key = await _save_creative_to_r2(
|
| 870 |
+
url,
|
| 871 |
+
cid,
|
| 872 |
+
(item.get("concept_name") or "").strip(),
|
| 873 |
+
(body.product_name or "").strip(),
|
| 874 |
+
)
|
| 875 |
final_url = r2_url if r2_url else url
|
| 876 |
if r2_key and username:
|
| 877 |
await asyncio.to_thread(
|
|
|
|
| 1077 |
image_bytes = r.content
|
| 1078 |
concept = (entry.get("concept_name") or "image").strip()[:64]
|
| 1079 |
cid = entry.get("creative_id", 0)
|
| 1080 |
+
key = key_for_creative(
|
| 1081 |
+
cid,
|
| 1082 |
+
f"{concept}-corrected",
|
| 1083 |
+
(entry.get("product_name") or "").strip(),
|
| 1084 |
+
)
|
| 1085 |
r2_url = await asyncio.to_thread(upload_creative_image, image_bytes, key=key, content_type="image/png")
|
| 1086 |
if r2_url:
|
| 1087 |
gallery_update_entry(
|
backend/app/r2.py
CHANGED
|
@@ -132,10 +132,29 @@ def put_object(
|
|
| 132 |
return False
|
| 133 |
|
| 134 |
|
| 135 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
"""Generate a stable R2 key for a creative image."""
|
| 137 |
-
|
| 138 |
-
#
|
| 139 |
-
|
| 140 |
unique = uuid.uuid4().hex[:8]
|
| 141 |
-
return f"creatives/{creative_id}-{
|
|
|
|
| 132 |
return False
|
| 133 |
|
| 134 |
|
| 135 |
+
def _sanitize_segment(value: str, *, sep: str = "-") -> str:
|
| 136 |
+
cleaned = (value or "").strip()
|
| 137 |
+
if not cleaned:
|
| 138 |
+
return ""
|
| 139 |
+
cleaned = cleaned.replace("/", " ").replace("\\", " ")
|
| 140 |
+
out_chars: list[str] = []
|
| 141 |
+
prev_sep = False
|
| 142 |
+
for ch in cleaned:
|
| 143 |
+
if ch.isalnum():
|
| 144 |
+
out_chars.append(ch)
|
| 145 |
+
prev_sep = False
|
| 146 |
+
else:
|
| 147 |
+
if not prev_sep:
|
| 148 |
+
out_chars.append(sep)
|
| 149 |
+
prev_sep = True
|
| 150 |
+
out = "".join(out_chars).strip(sep)
|
| 151 |
+
return out
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def key_for_creative(creative_id: int, concept_slug: str = "", product_name: str = "") -> str:
|
| 155 |
"""Generate a stable R2 key for a creative image."""
|
| 156 |
+
safe_concept = _sanitize_segment(concept_slug, sep="-")[:64] or "image"
|
| 157 |
+
# Requested naming style: <product>_AI_creative_...
|
| 158 |
+
safe_product = _sanitize_segment(product_name, sep="_")[:64] or "product"
|
| 159 |
unique = uuid.uuid4().hex[:8]
|
| 160 |
+
return f"creatives/{safe_product}_AI_creative_{creative_id}-{safe_concept}-{unique}.png"
|
backend/app/variation.py
CHANGED
|
@@ -159,7 +159,12 @@ Return JSON only:
|
|
| 159 |
return normalized
|
| 160 |
|
| 161 |
|
| 162 |
-
async def _save_variation_to_r2(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
try:
|
| 164 |
async with httpx.AsyncClient(timeout=60.0) as client:
|
| 165 |
resp = await client.get(image_url)
|
|
@@ -170,7 +175,7 @@ async def _save_variation_to_r2(image_url: str, variation_id: int, concept_name:
|
|
| 170 |
return None, None
|
| 171 |
if not image_bytes:
|
| 172 |
return None, None
|
| 173 |
-
key = key_for_creative(variation_id, concept_name)
|
| 174 |
r2_url = await asyncio.to_thread(upload_creative_image, image_bytes, key=key, content_type=content_type)
|
| 175 |
return (r2_url, key if r2_url else None)
|
| 176 |
|
|
@@ -223,7 +228,7 @@ async def generate_variations_stream(
|
|
| 223 |
await asyncio.sleep(2 * attempt)
|
| 224 |
if err or not image_out_url:
|
| 225 |
return {"variation_id": vid, "concept_name": concept_name, "image_url": None, "error": err or "Generation failed"}
|
| 226 |
-
r2_url, r2_key = await _save_variation_to_r2(image_out_url, vid, concept_name)
|
| 227 |
final_url = r2_url or image_out_url
|
| 228 |
if r2_key and username:
|
| 229 |
await asyncio.to_thread(
|
|
|
|
| 159 |
return normalized
|
| 160 |
|
| 161 |
|
| 162 |
+
async def _save_variation_to_r2(
|
| 163 |
+
image_url: str,
|
| 164 |
+
variation_id: int,
|
| 165 |
+
concept_name: str,
|
| 166 |
+
product_name: str | None = None,
|
| 167 |
+
) -> tuple[str | None, str | None]:
|
| 168 |
try:
|
| 169 |
async with httpx.AsyncClient(timeout=60.0) as client:
|
| 170 |
resp = await client.get(image_url)
|
|
|
|
| 175 |
return None, None
|
| 176 |
if not image_bytes:
|
| 177 |
return None, None
|
| 178 |
+
key = key_for_creative(variation_id, concept_name, product_name or "")
|
| 179 |
r2_url = await asyncio.to_thread(upload_creative_image, image_bytes, key=key, content_type=content_type)
|
| 180 |
return (r2_url, key if r2_url else None)
|
| 181 |
|
|
|
|
| 228 |
await asyncio.sleep(2 * attempt)
|
| 229 |
if err or not image_out_url:
|
| 230 |
return {"variation_id": vid, "concept_name": concept_name, "image_url": None, "error": err or "Generation failed"}
|
| 231 |
+
r2_url, r2_key = await _save_variation_to_r2(image_out_url, vid, concept_name, product_name)
|
| 232 |
final_url = r2_url or image_out_url
|
| 233 |
if r2_key and username:
|
| 234 |
await asyncio.to_thread(
|