from __future__ import annotations import base64 import copy import hashlib import html import json import os import re import shlex import shutil import subprocess import sys import tempfile import time import uuid from datetime import datetime, timezone from pathlib import Path PROJECT_DIR_NAME = ".trackio" LOGBOOK_SUBDIR = "logbook" METADATA_FILE = "metadata.json" SCHEMA_VERSION = 2 ROOT_SLUG = "index" VIEWER_DIR = Path(__file__).parent / "frontend_templates" / "logbook" VIEWER_FILES = [ "index.html", "logbook.css", "logbook.js", "trackio-logo.png", "trackio-logo-light.png", "trackio-wordmark-dark.png", "bucket-icon.svg", ] PRIVATE_STATE_IGNORE_PATTERNS = ( "/trace_sources.json", "/traces/", "/workspace_baselines/", "/workspace_bucket_state.json", "/trace_dataset/", "/imported_workspace.json", "/logbook/traces/", "/logbook/workspace.json", ) TOC_HEADING = "## Pages" TOC_HEADER = "| Page |" TOC_SEP = "| --- |" TOC_PLACEHOLDER_TOKENS = ("logbook note", "logbook cell markdown", "logbook page") STATUS_COL_RE = re.compile(r"\b(status|state)\b", re.I) LINK_COL_RE = re.compile(r"\b(page|experiment|name|title)\b", re.I) CELL_TYPES = {"markdown", "code", "figure", "artifact", "dashboard"} ARTIFACT_URI_PREFIX = "trackio-artifact://" PATH_ARTIFACT_URI_PREFIX = "trackio-local-path://" LOCAL_DASHBOARD_PREFIX = "trackio-local-dashboard://" SPACE_URL_RE = re.compile(r"https://huggingface\.co/spaces/[^\s<>)\"'`]+") BUCKET_URL_RE = re.compile(r"https://huggingface\.co/buckets/[^\s<>)\"'`]+") DEFAULT_HEAD = 3 DEFAULT_TAIL = 3 DEFAULT_RAW_LIMIT = 500 FENCE_RE = re.compile(r"(`{3,4}|~{3,4})([^\n]*)\n([\s\S]*?)\n\1") RUN_OUTPUT_LIMIT = 20_000 RUN_OUTPUT_HEAD = 2_000 RUN_OUTPUT_TAIL = RUN_OUTPUT_LIMIT - RUN_OUTPUT_HEAD TRY_NUM_PORTS = int(os.getenv("GRADIO_NUM_PORTS", "100")) CELL_RE = re.compile( r"(^|\n)---\n\n([\s\S]*?)(?=\n---\n" ) return f"{m.group(1)}---\n{marker}\n{m.group(3)}" new_text = CELL_RE.sub(_repl, text) if state["hit"]: path.write_text(new_text, encoding="utf-8") write_site_files(proj) return { "page": node["slug"], "page_title": node["title"], "title": state["title"], "pinned": pinned, } raise LogbookError(f"No cell with id '{cell_id}' in this logbook.") def remove_cell(proj: Path, cell_id: str, page: str | None = None) -> dict: """Delete a cell by id from its owning page and rewrite site files. Searches every page (or only ``page`` when given). Removes the whole cell block (marker + body) from the source ``page.md`` in place. """ root = logbook_root(proj) scope = _resolve_slug(proj, page) if page else None for node in _walk(build_manifest(proj)["root"]): if scope and node["slug"] != scope: continue path = root / node["file"] text = path.read_text(encoding="utf-8") state = {"hit": False, "title": None, "type": None} def _repl(m): try: meta = json.loads(m.group(2)) except json.JSONDecodeError: return m.group(0) if meta.get("id") != cell_id: return m.group(0) state["hit"] = True state["title"] = meta.get("title") state["type"] = meta.get("type") return "" new_text = CELL_RE.sub(_repl, text) if state["hit"]: path.write_text(new_text.rstrip() + "\n", encoding="utf-8") write_site_files(proj) return { "page": node["slug"], "page_title": node["title"], "title": state["title"], "type": state["type"], } raise LogbookError(f"No cell with id '{cell_id}' in this logbook.") # ---- auto-note helpers ---- # NOTE: these helpers are intentionally NOT invoked automatically from # trackio.init() / run.log_artifact() anymore. Doing so mutated (and corrupted) # curated logbooks that merely happened to sit in the current directory. They # remain as explicit, opt-in building blocks for logbook tooling/tests. def _autonote_enabled() -> bool: return os.environ.get("TRACKIO_LOGBOOK_AUTONOTE", "1").lower() not in ( "0", "false", "no", "off", ) def _new_cell_id() -> str: return f"cell_{uuid.uuid4().hex[:12]}" def _cell_marker(cell_type: str, title: str | None = None, **metadata) -> str: if cell_type not in CELL_TYPES: raise LogbookError(f"Unsupported logbook cell type: {cell_type}") title = (title or "").strip() or "Untitled" payload = { "type": cell_type, "id": metadata.pop("id", _new_cell_id()), "created_at": metadata.pop("created_at", _now_iso()), "title": title, } payload.update({k: v for k, v in metadata.items() if v not in (None, "", [])}) return "" def _shorten(text: str, limit: int = 80) -> str: text = re.sub(r"\s+", " ", text).strip() if len(text) <= limit: return text return text[: limit - 1].rstrip() + "…" def _strip_markdown_title(text: str) -> str: text = re.sub(r"!\[([^\]]*)\]\([^)]+\)", r"\1", text) text = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", text) text = re.sub(r"[*_`>#]+", "", text) return text.strip(" -:\t") def _default_cell_title(cell_type: str, body: str, metadata: dict) -> str: command = metadata.get("command") if command: try: return _shorten(f"Run: {shlex.join(command)}") except TypeError: return "Run" if cell_type == "code": titled = re.search(r"^````\w*\s+title=([^\n]+)", body, re.M) if titled: return _shorten(f"Code: {titled.group(1).strip()}") return "Code cell" if cell_type == "figure": return "Figure" for line in body.splitlines(): line = line.strip() if not line or line.startswith("````") or line.startswith("