@@ -37,11 +37,11 @@ def _build_sitemap_xml(specs: list) -> str:
3737 for spec in specs :
3838 if spec .impls :
3939 spec_id = html .escape (spec .id )
40- xml_lines .append (f" <url><loc>https://anyplot.ai/{ spec_id } </loc>{ _lastmod (spec .updated )} </url>" )
40+ xml_lines .append (f" <url><loc>https://anyplot.ai/python/ { spec_id } </loc>{ _lastmod (spec .updated )} </url>" )
4141 for impl in spec .impls :
4242 library_id = html .escape (impl .library_id )
4343 xml_lines .append (
44- f" <url><loc>https://anyplot.ai/{ spec_id } /{ library_id } </loc>{ _lastmod (impl .updated )} </url>"
44+ f" <url><loc>https://anyplot.ai/python/ { spec_id } /{ library_id } </loc>{ _lastmod (impl .updated )} </url>"
4545 )
4646
4747 xml_lines .append ("</urlset>" )
@@ -182,17 +182,15 @@ async def seo_mcp():
182182 )
183183
184184
185- @router .get ("/seo-proxy/{spec_id}" )
186- async def seo_spec_overview (spec_id : str , db : AsyncSession | None = Depends (optional_db )):
187- """Bot-optimized spec overview page with collage og:image."""
185+ async def _seo_spec_overview_html (spec_id : str , db : AsyncSession | None ) -> HTMLResponse :
186+ """Shared logic for bot-optimized spec overview page with collage og:image."""
188187 if db is None :
189- # Fallback when DB unavailable
190188 return HTMLResponse (
191189 BOT_HTML_TEMPLATE .format (
192190 title = f"{ html .escape (spec_id )} | anyplot.ai" ,
193191 description = DEFAULT_DESCRIPTION ,
194192 image = DEFAULT_HOME_IMAGE ,
195- url = f"https://anyplot.ai/{ html .escape (spec_id )} " ,
193+ url = f"https://anyplot.ai/python/ { html .escape (spec_id )} " ,
196194 )
197195 )
198196
@@ -206,31 +204,28 @@ async def seo_spec_overview(spec_id: str, db: AsyncSession | None = Depends(opti
206204 if not spec :
207205 raise HTTPException (status_code = 404 , detail = "Spec not found" )
208206
209- # Use collage og:image if implementations exist, otherwise default
210207 has_previews = any (i .preview_url for i in spec .impls )
211208 image = f"https://api.anyplot.ai/og/{ spec_id } .png" if has_previews else DEFAULT_HOME_IMAGE
212209
213210 result = BOT_HTML_TEMPLATE .format (
214211 title = f"{ html .escape (spec .title )} | anyplot.ai" ,
215212 description = html .escape (spec .description or DEFAULT_DESCRIPTION ),
216213 image = html .escape (image , quote = True ),
217- url = f"https://anyplot.ai/{ html .escape (spec_id )} " ,
214+ url = f"https://anyplot.ai/python/ { html .escape (spec_id )} " ,
218215 )
219216 set_cache (key , result )
220217 return HTMLResponse (result )
221218
222219
223- @router .get ("/seo-proxy/{spec_id}/{library}" )
224- async def seo_spec_implementation (spec_id : str , library : str , db : AsyncSession | None = Depends (optional_db )):
225- """Bot-optimized spec implementation page with branded og:image."""
220+ async def _seo_spec_impl_html (spec_id : str , library : str , db : AsyncSession | None ) -> HTMLResponse :
221+ """Shared logic for bot-optimized spec implementation page with branded og:image."""
226222 if db is None :
227- # Fallback when DB unavailable
228223 return HTMLResponse (
229224 BOT_HTML_TEMPLATE .format (
230225 title = f"{ html .escape (spec_id )} - { html .escape (library )} | anyplot.ai" ,
231226 description = DEFAULT_DESCRIPTION ,
232227 image = DEFAULT_HOME_IMAGE ,
233- url = f"https://anyplot.ai/{ html .escape (spec_id )} /{ html .escape (library )} " ,
228+ url = f"https://anyplot.ai/python/ { html .escape (spec_id )} /{ html .escape (library )} " ,
234229 )
235230 )
236231
@@ -244,16 +239,40 @@ async def seo_spec_implementation(spec_id: str, library: str, db: AsyncSession |
244239 if not spec :
245240 raise HTTPException (status_code = 404 , detail = "Spec not found" )
246241
247- # Find the implementation for this library
248242 impl = next ((i for i in spec .impls if i .library_id == library ), None )
249- # Use branded og:image endpoint if implementation has preview
250243 image = f"https://api.anyplot.ai/og/{ spec_id } /{ library } .png" if impl and impl .preview_url else DEFAULT_HOME_IMAGE
251244
252245 result = BOT_HTML_TEMPLATE .format (
253246 title = f"{ html .escape (spec .title )} - { html .escape (library )} | anyplot.ai" ,
254247 description = html .escape (spec .description or DEFAULT_DESCRIPTION ),
255248 image = html .escape (image , quote = True ),
256- url = f"https://anyplot.ai/{ html .escape (spec_id )} /{ html .escape (library )} " ,
249+ url = f"https://anyplot.ai/python/ { html .escape (spec_id )} /{ html .escape (library )} " ,
257250 )
258251 set_cache (key , result )
259252 return HTMLResponse (result )
253+
254+
255+ # New /python/ prefixed routes (canonical paths)
256+ @router .get ("/seo-proxy/python/{spec_id}" )
257+ async def seo_python_spec_overview (spec_id : str , db : AsyncSession | None = Depends (optional_db )):
258+ """Bot-optimized spec overview page (canonical /python/ path)."""
259+ return await _seo_spec_overview_html (spec_id , db )
260+
261+
262+ @router .get ("/seo-proxy/python/{spec_id}/{library}" )
263+ async def seo_python_spec_implementation (spec_id : str , library : str , db : AsyncSession | None = Depends (optional_db )):
264+ """Bot-optimized spec implementation page (canonical /python/ path)."""
265+ return await _seo_spec_impl_html (spec_id , library , db )
266+
267+
268+ # Legacy routes (old URLs without /python/ prefix) — serve same content with /python/ canonical
269+ @router .get ("/seo-proxy/{spec_id}" )
270+ async def seo_spec_overview (spec_id : str , db : AsyncSession | None = Depends (optional_db )):
271+ """Bot-optimized spec overview page (legacy path, canonical points to /python/)."""
272+ return await _seo_spec_overview_html (spec_id , db )
273+
274+
275+ @router .get ("/seo-proxy/{spec_id}/{library}" )
276+ async def seo_spec_implementation (spec_id : str , library : str , db : AsyncSession | None = Depends (optional_db )):
277+ """Bot-optimized spec implementation page (legacy path, canonical points to /python/)."""
278+ return await _seo_spec_impl_html (spec_id , library , db )
0 commit comments