Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,33 @@

All notable changes to pytorch_sphinx_theme2 are documented here.

## v0.4.4

- **Tutorial Card Image Sizing Fix** — Constrained tutorial card images to a fixed width (`200px` desktop, `175px` tablet) instead of `25%`, and switched the `<img>` to `max-width: 100%; max-height: 100%; object-fit: contain` so images maintain their aspect ratio. Images are now centered within the container using flexbox.

---

## v0.4.3

- **Custom `llms.txt` Support** — Projects can provide their own hand-crafted `llms.txt` instead of relying on auto-generation. Two mechanisms are supported: (1) set the `llm_custom_file` theme option to point to a file relative to the source directory, or (2) place an `llms.txt` file in the Sphinx source root (next to `conf.py`) and it will be used automatically. The resolution order is: explicit `llm_custom_file` → source-root convention → auto-generation.
- **Version Switcher Compact Display** — The version-switcher dropdown button now truncates long version strings (e.g. `v2.11.0 (cu128)` → `v2.11.0`) using CSS `max-width` with `text-overflow: ellipsis`. The version text is styled in the theme's primary color with bold weight. The dropdown caret is absolutely positioned so it remains visible despite the overflow clipping.
- **Logo-to-Version Spacing** — Added `0.5rem` left margin between the navbar logo and the version switcher to prevent them from appearing too close together.
- Minor code formatting and comment cleanup in `docs/conf.py` and `theme.conf`.

---

## v0.4.2

### Bug Fixes

- **Oversized Navbar Logo** — Constrained the desktop navbar logo to `max-height: 20px` so it sits proportionally within the 45px header bar instead of rendering at full SVG size.

### Improvements

- **LLM URL Resolution Fallback** — The `llms.txt` generator now resolves page URLs through a three-tier fallback: `llm_domain` + `llm_base_path` → Sphinx `html_baseurl` → relative URLs. Previously, only `llm_domain` or relative URLs were supported, which meant projects using `html_baseurl` without `llm_domain` got relative-only links.

---

## v0.4.0

### New Features
Expand Down
28 changes: 27 additions & 1 deletion configuration-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,12 @@ When enabled (opt-in), the theme:
2. Adds machine-readable `<meta name="llm:*">` tags to every page
3. Includes a `<link rel="alternate">` pointing to the llms.txt file

By default, `llms.txt` uses **relative URLs** so it works in any environment (production, preview, localhost). If `llm_domain` is set, absolute URLs are generated instead.
URLs in `llms.txt` are resolved in this priority order:
1. **`llm_domain`** + `llm_base_path` theme options → fully constructed URLs (e.g., `https://docs.pytorch.org/docs/stable/index.html`)
2. **`html_baseurl`** Sphinx config → baseurl + relative path (e.g., `https://docs.pytorch.org/docs/stable/index.html`)
3. **Relative URLs** as a last resort (e.g., `index.html`)

Most PyTorch doc sets already define `html_baseurl` in their `conf.py`, so absolute URLs are generated automatically without any extra theme configuration.

#### `llm_disabled`
- **Type:** String (`"true"` or `"false"`)
Expand Down Expand Up @@ -263,6 +268,27 @@ html_theme_options = {
}
```

#### `llm_custom_file` (optional)
- **Type:** String
- **Default:** `""` (auto-generate or use convention)
- **Description:** Path to a custom `llms.txt` file, relative to the Sphinx source directory. When set, this file is copied verbatim to the output instead of auto-generating one.

```python
html_theme_options = {
"llm_disabled": "false",
"llm_custom_file": "my-custom-llms.txt", # relative to source dir
}
```

#### Source-Root Convention

If no `llm_custom_file` is specified, the theme automatically checks for a file named `llms.txt` in the Sphinx source root directory (the same directory as `conf.py`). If found, it is used as-is instead of auto-generating one. This allows a zero-config override — just drop an `llms.txt` file next to `conf.py`.

**Resolution order:**
1. `llm_custom_file` theme option → copy that file
2. `llms.txt` in the source root → copy that file
3. Auto-generate from the documentation pages

#### Generated Meta Tags

The theme automatically adds the following meta tags to every page:
Expand Down
18 changes: 9 additions & 9 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,10 @@
html_theme_options = {
"header_links_before_dropdown": 4,
# Test with a different logo to verify custom logo works
#"logo": {
# "logo": {
# "image_light": "_static/logo-icon.svg",
# "image_dark": "_static/logo-icon.svg",
#},
# },
"icon_links": [
{
"name": "X",
Expand Down Expand Up @@ -264,13 +264,13 @@
},
# "canonical_url": "https://pytorch.org/docs/stable/",
"canonical_url": "http://localhost:8000",
#"llm_domain": "docs.pytorch.org",
#"llm_base_path": "docs/",
#"llm_description": "PyTorch is an optimized tensor library for deep learning",
#"llm_important_pages": "index, notes/cuda",
#"enable_navbar_dropdowns": False,
# "llm_domain": "docs.pytorch.org",
# "llm_base_path": "docs/",
# "llm_description": "PyTorch is an optimized tensor library for deep learning",
# "llm_important_pages": "index, notes/cuda",
# "enable_navbar_dropdowns": False,
# "pytorch_project": "tutorials",
#"show_lf_header": False,
# "show_lf_header": False,
# "show_lf_footer": False,
# RunLLM Widget Configuration (uncomment and set assistant_id to enable)
# Each repository should have its own unique assistant_id from RunLLM
Expand Down Expand Up @@ -306,7 +306,7 @@ def setup(app):
# Solution: Extract glossary terms during doctree-resolved (read phase),
# store in app.env where it merges properly, then write JS files for
# glossary tooltips during html-page-context (immediately, not deferred).

return {"version": version, "parallel_read_safe": True}


Expand Down
56 changes: 44 additions & 12 deletions pytorch_sphinx_theme2/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
__version__ = "0.4.1"
__version__ = "0.4.4"

import json
import os
import posixpath
import re
import shutil
import subprocess
from pathlib import Path
from uuid import uuid4
Expand Down Expand Up @@ -200,13 +201,16 @@ def _build_llms_url(domain, base_path, version, relative_path=""):
def _generate_llms_txt(app, exception):
"""Dynamically generate llms.txt during documentation build.

Generates a simple, clean llms.txt following the format used by Hugging Face:
- Project header
- List of documentation pages as markdown links
The file is resolved in this order:

By default uses relative URLs so the file works in any environment
(production, preview, localhost). If llm_domain is set, absolute URLs
are generated instead.
1. **Explicit option** — ``llm_custom_file`` theme option pointing to a file
relative to the Sphinx source directory.
2. **Convention** — A file named ``llms.txt`` in the Sphinx source root.
3. **Auto-generation** — A simple page listing following the Hugging Face
style, with URLs resolved as:
a. ``llm_domain`` + ``llm_base_path`` theme options → fully constructed URLs
b. Sphinx ``html_baseurl`` config → baseurl + relative path
c. Relative URLs as a last resort

Opt-in: set ``llm_disabled = false`` in html_theme_options to enable.
"""
Expand All @@ -221,17 +225,46 @@ def _generate_llms_txt(app, exception):
if str(theme_options.get("llm_disabled", "true")).lower() == "true":
return

dest_path = Path(app.outdir) / "llms.txt"

# --- 1. Explicit option: llm_custom_file ---
custom_file = theme_options.get("llm_custom_file", "").strip()
if custom_file:
custom_path = Path(app.srcdir) / custom_file
if custom_path.is_file():
shutil.copy2(custom_path, dest_path)
print(f"Copied custom llms.txt from: {custom_path}")
return
else:
print(
f"Warning: llm_custom_file '{custom_file}' not found at "
f"{custom_path}, falling back to auto-generation"
)

# --- 2. Convention: llms.txt in the source root ---
source_llms = Path(app.srcdir) / "llms.txt"
if source_llms.is_file():
shutil.copy2(source_llms, dest_path)
print(f"Using project-provided llms.txt from: {source_llms}")
return

# --- 3. Auto-generation ---
# Get configuration
project = app.config.project or "Documentation"
version = app.config.version or "latest"
domain = theme_options.get("llm_domain", "").strip()
base_path = theme_options.get("llm_base_path", "").strip()
use_absolute = bool(domain)

# Helper to build URL (absolute if domain is set, relative otherwise)
# Resolve the base URL for links:
# Priority: llm_domain > html_baseurl > relative
html_baseurl = getattr(app.config, "html_baseurl", None) or ""
html_baseurl = html_baseurl.strip().rstrip("/")

def make_url(relative_path):
if use_absolute:
if domain:
return _build_llms_url(domain, base_path, version, relative_path)
if html_baseurl:
return f"{html_baseurl}/{relative_path}"
return relative_path

# Collect all documentation pages
Expand Down Expand Up @@ -275,7 +308,6 @@ def make_url(relative_path):
content = "\n".join(lines)

# Write to site root
dest_path = Path(app.outdir) / "llms.txt"
try:
dest_path.write_text(content, encoding="utf-8")
print(f"Generated llms.txt with {len(docs)} pages at: {dest_path}")
Expand Down Expand Up @@ -618,7 +650,7 @@ def setup(app):
)

return {
"version": "0.4.1",
"version": "0.4.4",
"parallel_read_safe": True,
"parallel_write_safe": True,
}
40 changes: 36 additions & 4 deletions pytorch_sphinx_theme2/static/css/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -2774,6 +2774,34 @@ h1 .toc-backref:hover, h1 .toc-backref:active, h1 .toc-backref:visited, h1 .toc-
display: none;
}
}
.navbar-header-items__start .navbar-brand .logo__image {
max-height: 20px;
width: auto;
}

.navbar-header-items__start .desktop-only-version {
margin-left: 0.5rem;
}

button.btn.version-switcher__button {
max-width: 5.5rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
position: relative;
padding-right: 1.2rem;
color: var(--pst-color-primary);
font-weight: 600;
}
button.btn.version-switcher__button::after {
position: absolute;
right: 0.3rem;
top: 50%;
transform: translateY(-50%);
}
button.btn.version-switcher__button:hover, button.btn.version-switcher__button:focus, button.btn.version-switcher__button:active {
color: var(--pst-color-primary);
}

@media (max-width: 959px) {
.desktop-only-version {
Expand Down Expand Up @@ -3152,21 +3180,25 @@ h1[id], h2[id], h3[id], h4[id], h5[id], h6[id] {
height: 96px;
width: 96px;
opacity: 0.5;
display: flex;
align-items: center;
justify-content: center;
}
#tutorial-cards-container .card.tutorials-card .tutorials-image img {
height: 100%;
width: 100%;
max-height: 100%;
max-width: 100%;
object-fit: contain;
}
@media screen and (min-width: 768px) {
#tutorial-cards-container .card.tutorials-card .tutorials-image {
height: 100%;
width: 25%;
width: 200px;
}
}
@media (min-width: 768px) and (max-width: 1239px) {
#tutorial-cards-container .card.tutorials-card .tutorials-image {
height: 100%;
width: 198px;
width: 175px;
}
}
#tutorial-cards-container .card.tutorials-card .tutorials-image:before {
Expand Down
35 changes: 35 additions & 0 deletions pytorch_sphinx_theme2/static/scss/_custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,41 @@ h1, h2, h3, h4, h5, h6 {
@media (max-width: 959px) {
display: none;
}

.logo__image {
max-height: 20px;
width: auto;
}
}

// Add spacing between logo and version/switcher in the navbar
.navbar-header-items__start .desktop-only-version {
margin-left: 0.5rem;
}

// Style the version switcher with truncation so "v2.11.0 (cu128)" shows as "v2.11.0"
button.btn.version-switcher__button {
max-width: 5.5rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
position: relative;
padding-right: 1.2rem;
color: var(--pst-color-primary);
font-weight: 600;

&::after {
position: absolute;
right: 0.3rem;
top: 50%;
transform: translateY(-50%);
}

&:hover,
&:focus,
&:active {
color: var(--pst-color-primary);
}
}

// Hide version and version-switcher in mobile header (shown in sidebar instead)
Expand Down
12 changes: 8 additions & 4 deletions pytorch_sphinx_theme2/static/scss/_tutorials.scss
Original file line number Diff line number Diff line change
Expand Up @@ -237,20 +237,24 @@
height: 96px;
width: 96px;
opacity: 0.5;
display: flex;
align-items: center;
justify-content: center;

img {
height: 100%;
width: 100%;
max-height: 100%;
max-width: 100%;
object-fit: contain;
}

@media screen and (min-width: 768px) {
height: 100%;
width: 25%;
width: 200px;
}

@media (min-width: 768px) and (max-width: 1239px) {
height: 100%;
width: 198px;
width: 175px;
}

&:before {
Expand Down
2 changes: 1 addition & 1 deletion pytorch_sphinx_theme2/templates/pytorch-site-link.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!-- PyTorch.org site link - desktop only, two-line layout -->
<!-- Note: The show_pytorch_org_link check is handled in layout.html's navbar_end block -->
<a href="https://pytorch.org" class="pytorch-site-link nav-link nav-external" title="Go to PyTorch.org">
<a href="https://pytorch.org" class="pytorch-site-link nav-link nav-external" data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-title="Go to PyTorch.org">
<span class="pytorch-site-link-text">
<span>Go to</span>
<span>pytorch.org <i class="fa-solid fa-arrow-up-right-from-square external-icon"></i></span>
Expand Down
5 changes: 5 additions & 0 deletions pytorch_sphinx_theme2/theme.conf
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,9 @@ llm_base_path =
# Description of the site for LLMs (appears in llm:description meta tag)
llm_description =
# Set to false to enable llms.txt generation
# When enabled, URLs are resolved: llm_domain > html_baseurl > relative
llm_disabled = true
# Path to a custom llms.txt file (relative to Sphinx source directory).
# When set, this file is copied to the output instead of auto-generating one.
# If not set, a file named llms.txt in the source root is used automatically.
llm_custom_file =
7 changes: 6 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
from setuptools import setup # noqa: F401

with open("README.md", encoding="utf-8") as f:
long_description = f.read()

setup(
name="pytorch_sphinx_theme2",
description="PyTorch Sphinx Theme",
long_description=long_description,
long_description_content_type="text/markdown",
author="PyTorch Team",
author_email="svekars@meta.com",
url="https://github.com/pytorch/pytorch_sphinx_theme",
license="MIT",
version="0.4.1",
version="0.4.4",
install_requires=[
"pydata-sphinx-theme==0.15.4",
"sphinx>=5.3.0,<=7.2.6",
Expand Down