yonagush Claude Sonnet 4.6 commited on
Commit Β·
012f7ff
1
Parent(s): ef3da7b
fix: CTA links to article URL not homepage; 'READ FULL STORY' button with domain underneath
Browse files- app.py +4 -2
- frontend/src/components/SocialStudio.jsx +3 -3
- services/carousel_service.py +20 -8
app.py
CHANGED
|
@@ -76,14 +76,16 @@ async def fetch_article_endpoint(url: str = Form(...)):
|
|
| 76 |
async def carousel_generate(
|
| 77 |
url: str = Form(...),
|
| 78 |
brand_name: str = Form("ChainStreet"),
|
| 79 |
-
cta_url: str = Form("
|
| 80 |
groq_api_key: str = Form(""),
|
| 81 |
logo_file: Optional[UploadFile] = File(None),
|
| 82 |
):
|
| 83 |
"""URL β fetch article β AI 5-slide carousel β PIL images."""
|
| 84 |
logo_bytes = (await logo_file.read()) if (logo_file and logo_file.filename) else None
|
|
|
|
|
|
|
| 85 |
jid = _new_job()
|
| 86 |
-
asyncio.create_task(_carousel_job(jid, url, brand_name,
|
| 87 |
return {"job_id": jid, "status": "queued"}
|
| 88 |
|
| 89 |
async def _carousel_job(jid, url, brand_name, cta_url, api_key, logo_bytes):
|
|
|
|
| 76 |
async def carousel_generate(
|
| 77 |
url: str = Form(...),
|
| 78 |
brand_name: str = Form("ChainStreet"),
|
| 79 |
+
cta_url: str = Form(""),
|
| 80 |
groq_api_key: str = Form(""),
|
| 81 |
logo_file: Optional[UploadFile] = File(None),
|
| 82 |
):
|
| 83 |
"""URL β fetch article β AI 5-slide carousel β PIL images."""
|
| 84 |
logo_bytes = (await logo_file.read()) if (logo_file and logo_file.filename) else None
|
| 85 |
+
# Default CTA to the article URL itself β drives traffic back to the exact piece
|
| 86 |
+
effective_cta = cta_url.strip() or url.strip()
|
| 87 |
jid = _new_job()
|
| 88 |
+
asyncio.create_task(_carousel_job(jid, url, brand_name, effective_cta, groq_api_key, logo_bytes))
|
| 89 |
return {"job_id": jid, "status": "queued"}
|
| 90 |
|
| 91 |
async def _carousel_job(jid, url, brand_name, cta_url, api_key, logo_bytes):
|
frontend/src/components/SocialStudio.jsx
CHANGED
|
@@ -151,7 +151,7 @@ export default function SocialStudio({ brand, setBrand }) {
|
|
| 151 |
|
| 152 |
// Carousel fields
|
| 153 |
const [url, setUrl] = useState('')
|
| 154 |
-
const [ctaUrl, setCtaUrl] = useState('
|
| 155 |
|
| 156 |
// Single-card fields
|
| 157 |
const [topic, setTopic] = useState('')
|
|
@@ -307,12 +307,12 @@ export default function SocialStudio({ brand, setBrand }) {
|
|
| 307 |
<input
|
| 308 |
className="input"
|
| 309 |
type="text"
|
| 310 |
-
placeholder=
|
| 311 |
value={ctaUrl}
|
| 312 |
onChange={e => setCtaUrl(e.target.value)}
|
| 313 |
disabled={loading}
|
| 314 |
/>
|
| 315 |
-
<p className="text-xs text-white/30">
|
| 316 |
</div>
|
| 317 |
</>
|
| 318 |
)}
|
|
|
|
| 151 |
|
| 152 |
// Carousel fields
|
| 153 |
const [url, setUrl] = useState('')
|
| 154 |
+
const [ctaUrl, setCtaUrl] = useState('')
|
| 155 |
|
| 156 |
// Single-card fields
|
| 157 |
const [topic, setTopic] = useState('')
|
|
|
|
| 307 |
<input
|
| 308 |
className="input"
|
| 309 |
type="text"
|
| 310 |
+
placeholder={url.trim() || "Leave blank to use article URL"}
|
| 311 |
value={ctaUrl}
|
| 312 |
onChange={e => setCtaUrl(e.target.value)}
|
| 313 |
disabled={loading}
|
| 314 |
/>
|
| 315 |
+
<p className="text-xs text-white/30">Leave blank to link back to the article automatically</p>
|
| 316 |
</div>
|
| 317 |
</>
|
| 318 |
)}
|
services/carousel_service.py
CHANGED
|
@@ -322,20 +322,32 @@ def _slide_cta(slide, bg_bytes, brand, cta_url, idx, total):
|
|
| 322 |
y2 += 44
|
| 323 |
|
| 324 |
# CTA button β gold, centred
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
bx0 = (W - btw - pad_x*2) // 2
|
| 330 |
-
by0 = max(y2 +
|
| 331 |
-
bx1, by1 = bx0 + btw + pad_x*2, by0 +
|
| 332 |
# Outer glow
|
| 333 |
for expand in [10, 6, 3]:
|
| 334 |
d.rounded_rectangle([bx0-expand, by0-expand, bx1+expand, by1+expand],
|
| 335 |
-
radius=
|
| 336 |
-
d.rounded_rectangle([bx0, by0, bx1, by1], radius=
|
| 337 |
d.text((bx0 + pad_x, by0 + pad_y), btn_label, font=btnf, fill=BLACK)
|
| 338 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
_num(d, idx, total)
|
| 340 |
return _jpeg(img)
|
| 341 |
|
|
|
|
| 322 |
y2 += 44
|
| 323 |
|
| 324 |
# CTA button β gold, centred
|
| 325 |
+
# Extract display domain from cta_url (show "chainstreet.io" even if full URL passed)
|
| 326 |
+
raw_cta = cta_url or "chainstreet.io"
|
| 327 |
+
import re as _re
|
| 328 |
+
domain_match = _re.search(r'(?:https?://)?([^/]+)', raw_cta)
|
| 329 |
+
display_domain = domain_match.group(1) if domain_match else raw_cta
|
| 330 |
+
btn_label = f"β READ FULL STORY"
|
| 331 |
+
domain_label = display_domain.lower()
|
| 332 |
+
|
| 333 |
+
btnf = _f(30, bold=True)
|
| 334 |
+
domf = _f(22, bold=False)
|
| 335 |
+
btw = d.textlength(btn_label, font=btnf)
|
| 336 |
+
pad_x, pad_y = 48, 16
|
| 337 |
bx0 = (W - btw - pad_x*2) // 2
|
| 338 |
+
by0 = max(y2 + 36, H - 215)
|
| 339 |
+
bx1, by1 = bx0 + btw + pad_x*2, by0 + 60
|
| 340 |
# Outer glow
|
| 341 |
for expand in [10, 6, 3]:
|
| 342 |
d.rounded_rectangle([bx0-expand, by0-expand, bx1+expand, by1+expand],
|
| 343 |
+
radius=34+expand, fill=(*GOLD, 28))
|
| 344 |
+
d.rounded_rectangle([bx0, by0, bx1, by1], radius=32, fill=GOLD)
|
| 345 |
d.text((bx0 + pad_x, by0 + pad_y), btn_label, font=btnf, fill=BLACK)
|
| 346 |
|
| 347 |
+
# Domain URL underneath button
|
| 348 |
+
dtw = d.textlength(domain_label, font=domf)
|
| 349 |
+
d.text(((W - dtw)//2, by1 + 14), domain_label, font=domf, fill=(*GRAY,))
|
| 350 |
+
|
| 351 |
_num(d, idx, total)
|
| 352 |
return _jpeg(img)
|
| 353 |
|