| 451 | |
| 452 | @router.post("/api/export") |
| 453 | async def export_code(request: ExportRequest) -> Response: |
| 454 | soup = BeautifulSoup(normalize_babel_cdn(request.code), "html.parser") |
| 455 | candidates = collect_asset_candidates(soup) |
| 456 | |
| 457 | async with httpx.AsyncClient( |
| 458 | timeout=20, |
| 459 | headers={"User-Agent": "screenshot-to-code-export/1.0"}, |
| 460 | ) as client: |
| 461 | fetched_assets = await asyncio.gather( |
| 462 | *[ |
| 463 | fetch_asset(client, candidate, index, request.baseUrl) |
| 464 | for index, candidate in enumerate(candidates) |
| 465 | ] |
| 466 | ) |
| 467 | |
| 468 | assets = [asset for asset in fetched_assets if asset is not None] |
| 469 | asset_path_by_url = { |
| 470 | candidates[index].url: asset.path |
| 471 | for index, asset in enumerate(fetched_assets) |
| 472 | if asset is not None |
| 473 | } |
| 474 | |
| 475 | if asset_path_by_url: |
| 476 | rewrite_html_assets(soup, asset_path_by_url) |
| 477 | |
| 478 | index_html = rewrite_raw_asset_urls(str(soup), asset_path_by_url) |
| 479 | zip_content = create_project_zip(index_html, assets) |
| 480 | print( |
| 481 | "Export complete: " |
| 482 | f"candidates={len(candidates)} assets={len(assets)} " |
| 483 | f"skipped={len(candidates) - len(assets)} responseBytes={len(zip_content)}" |
| 484 | ) |
| 485 | return Response( |
| 486 | content=zip_content, |
| 487 | media_type="application/zip", |
| 488 | headers={ |
| 489 | "Content-Disposition": 'attachment; filename="screenshot-to-code-export.zip"' |
| 490 | }, |
| 491 | ) |