CoolFace
Apppublic

lerobot/robot-learning-tutorial

sourceHugging Faceupdated 1y agoView on Hugging Face
508likes
index.astro341 linesDownload Raw Back to pages
1---2import * as ArticleMod from "../content/article.mdx";3 4import Hero from "../components/Hero.astro";5import Footer from "../components/Footer.astro";6import ThemeToggle from "../components/ThemeToggle.astro";7import Seo from "../components/Seo.astro";8import TableOfContents from "../components/TableOfContents.astro";9// Default OG image served from public/10const ogDefaultUrl = "/thumb.auto.jpg";11import "katex/dist/katex.min.css";12import "../styles/global.css";13const articleFM = (ArticleMod as any).frontmatter ?? {};14const Article = (ArticleMod as any).default;15const docTitle = articleFM?.title ?? "Untitled article";16// Allow explicit line breaks in the title via "\n" or YAML newlines17const docTitleHtml = (articleFM?.title ?? "Untitled article")18  .replace(/\\n/g, "<br/>")19  .replace(/\n/g, "<br/>");20const subtitle = articleFM?.subtitle ?? "";21const description = articleFM?.description ?? "";22// Accept authors as string[] or array of objects { name, url, affiliations? }23const rawAuthors = (articleFM as any)?.authors ?? [];24type Affiliation = { id: number; name: string; url?: string };25type Author = { name: string; url?: string; affiliationIndices?: number[] };26 27// Normalize affiliations from frontmatter: supports strings or objects { id?, name, url? }28const rawAffils =29  (articleFM as any)?.affiliations ?? (articleFM as any)?.affiliation ?? [];30const normalizedAffiliations: Affiliation[] = (() => {31  const seen: Map<string, number> = new Map();32  const list: Affiliation[] = [];33  const pushUnique = (name: string, url?: string) => {34    const key = `${String(name).trim()}|${url ? String(url).trim() : ""}`;35    if (seen.has(key)) return seen.get(key)!;36    const id = list.length + 1;37    list.push({38      id,39      name: String(name).trim(),40      url: url ? String(url) : undefined,41    });42    seen.set(key, id);43    return id;44  };45  const input = Array.isArray(rawAffils)46    ? rawAffils47    : rawAffils48      ? [rawAffils]49      : [];50  for (const a of input) {51    if (typeof a === "string") {52      pushUnique(a);53    } else if (a && typeof a === "object") {54      const name = a.name ?? a.label ?? a.text ?? a.affiliation ?? "";55      if (!String(name).trim()) continue;56      const url = a.url || a.link;57      // Respect provided numeric id for display stability if present and sequential; otherwise reassign58      pushUnique(String(name), url ? String(url) : undefined);59    }60  }61  return list;62})();63 64// Helper: ensure an affiliation exists and return its id65const ensureAffiliation = (val: any): number | undefined => {66  if (val == null) return undefined;67  if (typeof val === "number" && Number.isFinite(val) && val > 0) {68    return Math.floor(val);69  }70  const name =71    typeof val === "string"72      ? val73      : (val?.name ?? val?.label ?? val?.text ?? val?.affiliation);74  if (!name || !String(name).trim()) return undefined;75  const existing = normalizedAffiliations.find(76    (a) => a.name === String(name).trim(),77  );78  if (existing) return existing.id;79  const id = normalizedAffiliations.length + 1;80  normalizedAffiliations.push({81    id,82    name: String(name).trim(),83    url: val?.url || val?.link,84  });85  return id;86};87 88// Normalize authors and map affiliations -> indices (Distill-like)89const normalizedAuthors: Author[] = (90  Array.isArray(rawAuthors) ? rawAuthors : []91)92  .map((a: any) => {93    if (typeof a === "string") {94      return { name: a } as Author;95    }96    const name = String(a?.name || "").trim();97    const url = a?.url || a?.link;98    let indices: number[] | undefined = undefined;99    const raw = a?.affiliations ?? a?.affiliation ?? a?.affils;100    if (raw != null) {101      const entries = Array.isArray(raw) ? raw : [raw];102      const ids = entries103        .map(ensureAffiliation)104        .filter((x): x is number => typeof x === "number");105      const unique = Array.from(new Set(ids)).sort((x, y) => x - y);106      if (unique.length) indices = unique;107    }108    return { name, url, affiliationIndices: indices } as Author;109  })110  .filter((a: Author) => a.name && a.name.trim().length > 0);111const authorNames: string[] = normalizedAuthors.map((a) => a.name);112const published = articleFM?.published ?? undefined;113const tags = articleFM?.tags ?? [];114// Prefer seoThumbImage from frontmatter if provided115const fmOg = articleFM?.seoThumbImage as string | undefined;116const imageAbs: string =117  fmOg && fmOg.startsWith("http")118    ? fmOg119    : Astro.site120      ? new URL(fmOg ?? ogDefaultUrl, Astro.site).toString()121      : (fmOg ?? ogDefaultUrl);122 123// ---- Build citation text & BibTeX from frontmatter ----124const stripHtml = (text: string) => String(text || "").replace(/<[^>]*>/g, "");125const rawTitle = articleFM?.title ?? "Untitled article";126const titleFlat = stripHtml(String(rawTitle))127  .replace(/\\n/g, " ")128  .replace(/\n/g, " ")129  .replace(/\s+/g, " ")130  .trim();131const extractYear = (val: string | undefined): number | undefined => {132  if (!val) return undefined;133  const d = new Date(val);134  if (!Number.isNaN(d.getTime())) return d.getFullYear();135  const m = String(val).match(/(19|20)\d{2}/);136  return m ? Number(m[0]) : undefined;137};138 139const year = extractYear(published);140const citationAuthorsText = authorNames.join(", ");141const citationText = `${citationAuthorsText}${year ? ` (${year})` : ""}. "${titleFlat}".`;142 143const authorsBib = authorNames.join(" and ");144const keyAuthor = (authorNames[0] || "article")145  .split(/\s+/)146  .slice(-1)[0]147  .toLowerCase();148const keyTitle = titleFlat149  .toLowerCase()150  .replace(/[^a-z0-9]+/g, "_")151  .replace(/^_|_$/g, "")152  .slice(0, 24);153const bibKey = `${keyAuthor}${year ?? ""}_${keyTitle}`;154const doi = (ArticleMod as any)?.frontmatter?.doi155  ? String((ArticleMod as any).frontmatter.doi)156  : undefined;157const bibtex = `@misc{${bibKey},\n  title={${titleFlat}},\n  author={${authorsBib}},\n  ${year ? `year={${year}},\n  ` : ""}${doi ? `doi={${doi}}` : ""}\n}`;158const envCollapse = false;159const tableOfContentAutoCollapse = Boolean(160  (articleFM as any)?.tableOfContentAutoCollapse ??161    (articleFM as any)?.tableOfContentsAutoCollapse ??162    envCollapse,163);164// Licence note (HTML allowed)165const licence =166  (articleFM as any)?.licence ??167  (articleFM as any)?.license ??168  (articleFM as any)?.licenseNote;169---170 171<html172  lang="en"173  data-theme="light"174  data-toc-auto-collapse={tableOfContentAutoCollapse ? "1" : "0"}175>176  <head>177    <meta charset="utf-8" />178    <meta name="viewport" content="width=device-width, initial-scale=1" />179    <Seo180      title={docTitle}181      description={description}182      authors={authorNames}183      published={published}184      tags={tags}185      image={imageAbs}186    />187    <script is:inline>188      (() => {189        try {190          const saved = localStorage.getItem("theme");191          const prefersDark =192            window.matchMedia &&193            window.matchMedia("(prefers-color-scheme: dark)").matches;194          const theme = saved || (prefersDark ? "dark" : "light");195          document.documentElement.setAttribute("data-theme", theme);196        } catch {}197      })();198    </script>199    <script type="module" src="/scripts/color-palettes.js"></script>200 201    <!-- TO MANAGE PROPERLY -->202    <script src="https://cdn.plot.ly/plotly-3.0.0.min.js" charset="utf-8"203    ></script>204    <script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>205    <script206      src="https://cdn.jsdelivr.net/npm/medium-zoom@1.1.0/dist/medium-zoom.min.js"207    ></script>208    <script>209      // Debug and global zoom initialization210 211      function initializeZoom() {212        const zoomableImages = document.querySelectorAll(213          'img[data-zoomable="1"]',214        );215 216        if (window.mediumZoom && zoomableImages.length > 0) {217          zoomableImages.forEach((img, index) => {218            // Check if already initialized219            if (!img.classList.contains("medium-zoom-image")) {220              try {221                const instance = window.mediumZoom(img, {222                  background: "rgba(0,0,0,.85)",223                  margin: 24,224                  scrollOffset: 0,225                });226              } catch (error) {227                console.error(228                  `Global script: Error initializing zoom for image ${index}:`,229                  error,230                );231              }232            } else {233              console.log(`Global script: Image ${index} already has zoom`);234            }235          });236        } else {237          console.log(238            "Global script: mediumZoom not available or no images found",239          );240        }241      }242 243      // Try to initialize immediately244      if (document.readyState === "loading") {245        document.addEventListener("DOMContentLoaded", initializeZoom);246      } else {247        initializeZoom();248      }249 250      // Also try after complete loading251      window.addEventListener("load", () => {252        setTimeout(initializeZoom, 100);253      });254    </script>255  </head>256  <body>257    <ThemeToggle />258    <Hero259      title={docTitleHtml}260      titleRaw={docTitle}261      description={subtitle}262      authors={normalizedAuthors as any}263      affiliations={normalizedAffiliations as any}264      affiliation={articleFM?.affiliation}265      published={articleFM?.published}266      doi={doi}267      pdfProOnly={articleFM?.pdfProOnly}268    />269 270    <section class="content-grid">271      <TableOfContents272        tableOfContentAutoCollapse={tableOfContentAutoCollapse}273      />274      <main>275        <Article />276      </main>277    </section>278 279    <Footer280      citationText={citationText}281      bibtex={bibtex}282      licence={licence}283      doi={doi}284    />285 286    <script>287      // Open external links in a new tab; keep internal anchors in-page288      const setExternalTargets = () => {289        const isExternal = (href) => {290          try {291            const u = new URL(href, location.href);292            return u.origin !== location.origin;293          } catch {294            return false;295          }296        };297        document.querySelectorAll("a[href]").forEach((a) => {298          const href = a.getAttribute("href");299          if (!href) return;300          if (isExternal(href)) {301            a.setAttribute("target", "_blank");302            a.setAttribute("rel", "noopener noreferrer");303          } else {304            a.removeAttribute("target");305          }306        });307      };308      if (document.readyState === "loading") {309        document.addEventListener("DOMContentLoaded", setExternalTargets, {310          once: true,311        });312      } else {313        setExternalTargets();314      }315    </script>316 317    <script>318      // Delegate copy clicks for code blocks injected by rehypeCodeCopy319      document.addEventListener("click", async (e) => {320        const target = e.target instanceof Element ? e.target : null;321        const btn = target ? target.closest(".code-copy") : null;322        if (!btn) return;323        const card = btn.closest(".code-card");324        const pre = card && card.querySelector("pre");325        if (!pre) return;326        const text = pre.textContent || "";327        try {328          await navigator.clipboard.writeText(text.trim());329          const old = btn.innerHTML;330          btn.innerHTML =331            '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M9 16.2l-3.5-3.5-1.4 1.4L9 19 20.3 7.7l-1.4-1.4z"/></svg>';332          setTimeout(() => (btn.innerHTML = old), 1200);333        } catch {334          btn.textContent = "Error";335          setTimeout(() => (btn.textContent = "Copy"), 1200);336        }337      });338    </script>339  </body>340</html>341