#!/usr/bin/env python3 """ Beautiful custom timeline visualization for Transformers models using Flask. """ import glob import os import re import sys import time import webbrowser from datetime import datetime from typing import Dict, List, Optional from flask import Flask, jsonify, render_template, request import transformers class TransformersTimelineParser: """Parser for extracting model release dates from Transformers documentation.""" def __init__(self, docs_dir: str): self.docs_dir = docs_dir self.models_cache = None self.tasks_cache = {} # Add transformers source directory to Python path to import auto mappings transformers_src = os.path.join(os.path.dirname(docs_dir), "..", "..", "src") if transformers_src not in sys.path: sys.path.insert(0, transformers_src) # Modality definitions with modern color scheme self.modalities = { "text": { "name": "Text Models", "color": "#F59E0B", # Soft amber "models": [ "albert", "apertus", "arcee", "bamba", "bart", "barthez", "bartpho", "bert", "bert-generation", "bert-japanese", "bertweet", "big_bird", "bigbird_pegasus", "biogpt", "bitnet", "blenderbot", "blenderbot-small", "bloom", "bort", "byt5", "camembert", "canine", "codegen", "code_llama", "cohere", "cohere2", "convbert", "cpm", "cpmant", "ctrl", "dbrx", "deberta", "deberta-v2", "deepseek_v3", "dialogpt", "diffllama", "distilbert", "doge", "dots1", "dpr", "electra", "encoder-decoder", "ernie", "ernie4_5", "ernie4_5_moe", "ernie_m", "esm", "exaone4", "falcon", "falcon3", "falcon_h1", "falcon_mamba", "flan-t5", "flan-ul2", "flaubert", "fnet", "fsmt", "funnel", "fuyu", "gemma", "gemma2", "glm", "glm4", "glm4_moe", "openai-gpt", "gpt_neo", "gpt_neox", "gpt_neox_japanese", "gptj", "gpt2", "gpt_bigcode", "gpt_oss", "gptsan-japanese", "gpt-sw3", "granite", "granitemoe", "granitemoehybrid", "granitemoeshared", "helium", "herbert", "hgnet_v2", "hunyuan_v1_dense", "hunyuan_v1_moe", "ibert", "jamba", "jetmoe", "jukebox", "led", "lfm2", "llama", "llama2", "llama3", "longformer", "longt5", "luke", "m2m_100", "madlad-400", "mamba", "mamba2", "marian", "markuplm", "mbart", "mega", "megatron-bert", "megatron_gpt2", "minimax", "ministral", "mistral", "mixtral", "mluke", "mobilebert", "modernbert", "modernbert-decoder", "mpnet", "mpt", "mra", "mt5", "mvp", "myt5", "nemotron", "nezha", "nllb", "nllb-moe", "nystromformer", "olmo", "olmo2", "olmo3", "olmoe", "open-llama", "opt", "pegasus", "pegasus_x", "persimmon", "phi", "phi3", "phimoe", "phobert", "plbart", "prophetnet", "qdqbert", "qwen2", "qwen2_moe", "qwen3", "qwen3_moe", "qwen3_next", "rag", "realm", "recurrent_gemma", "reformer", "rembert", "retribert", "roberta", "roberta-prelayernorm", "roc_bert", "roformer", "rwkv", "seed_oss", "splinter", "squeezebert", "stablelm", "starcoder2", "switch_transformers", "t5", "t5gemma", "t5v1.1", "tapex", "transfo-xl", "ul2", "umt5", "vaultgemma", "xmod", "xglm", "xlm", "xlm-prophetnet", "xlm-roberta", "xlm-roberta-xl", "xlm-v", "xlnet", "xlstm", "yoso", "zamba", "zamba2", ], }, "vision": { "name": "Vision Models", "color": "#06B6D4", # Soft cyan "models": [ "aimv2", "beit", "bit", "conditional_detr", "convnext", "convnextv2", "cvt", "d_fine", "dab-detr", "deepseek_v2", "deepseek_vl", "deepseek_vl_hybrid", "deformable_detr", "deit", "depth_anything", "depth_anything_v2", "depth_pro", "deta", "detr", "dinat", "dinov2", "dinov2_with_registers", "dinov3", "dit", "dpt", "efficientformer", "efficientloftr", "efficientnet", "eomt", "focalnet", "glpn", "hgnet_v2", "hiera", "ijepa", "imagegpt", "levit", "lightglue", "mask2former", "maskformer", "mlcd", "mobilenet_v1", "mobilenet_v2", "mobilevit", "mobilevitv2", "nat", "poolformer", "prompt_depth_anything", "pvt", "pvt_v2", "regnet", "resnet", "rt_detr", "rt_detr_v2", "segformer", "seggpt", "superglue", "superpoint", "swiftformer", "swin", "swinv2", "swin2sr", "table-transformer", "textnet", "timm_wrapper", "upernet", "van", "vit", "vit_hybrid", "vitdet", "vit_mae", "vitmatte", "vit_msn", "vitpose", "yolos", "zoedepth", ], }, "audio": { "name": "Audio Models", "color": "#8B5CF6", # Soft purple "models": [ "audio-spectrogram-transformer", "bark", "clap", "csm", "dac", "dia", "encodec", "fastspeech2_conformer", "granite_speech", "hubert", "kyutai_speech_to_text", "mctct", "mimi", "mms", "moonshine", "moshi", "musicgen", "musicgen_melody", "pop2piano", "seamless_m4t", "seamless_m4t_v2", "sew", "sew-d", "speech_to_text", "speech_to_text_2", "speecht5", "unispeech", "unispeech-sat", "univnet", "vits", "wav2vec2", "wav2vec2-bert", "wav2vec2-conformer", "wav2vec2_phoneme", "wavlm", "whisper", "xcodec", "xls_r", "xlsr_wav2vec2", ], }, "video": { "name": "Video Models", "color": "#EC4899", # Soft pink "models": ["timesformer", "vjepa2", "videomae", "vivit"], }, "multimodal": { "name": "Multimodal Models", "color": "#10B981", # Soft emerald "models": [ "align", "altclip", "aria", "aya_vision", "blip", "blip-2", "bridgetower", "bros", "chameleon", "chinese_clip", "clip", "clipseg", "clvp", "cohere2_vision", "colpali", "colqwen2", "data2vec", "deplot", "donut", "emu3", "evolla", "flava", "florence2", "gemma3", "gemma3n", "git", "glm4v", "glm4v_moe", "got_ocr2", "granitevision", "grounding-dino", "groupvit", "idefics", "idefics2", "idefics3", "instructblip", "instructblipvideo", "internvl", "janus", "kosmos-2", "kosmos2_5", "layoutlm", "layoutlmv2", "layoutlmv3", "layoutxlm", "lilt", "llama4", "llava", "llava_next", "llava_next_video", "llava_onevision", "lxmert", "matcha", "metaclip_2", "mgp-str", "mistral3", "mllama", "mm-grounding-dino", "nougat", "omdet-turbo", "oneformer", "ovis2", "owlvit", "owlv2", "paligemma", "perceiver", "perception_lm", "phi4_multimodal", "pix2struct", "pixtral", "qwen2_5_omni", "qwen2_5_vl", "qwen2_audio", "qwen2_vl", "qwen3_vl", "qwen3_vl_moe", "sam2", "sam2_video", "sam", "sam_hq", "shieldgemma2", "siglip", "siglip2", "smollm3", "smolvlm", "speech-encoder-decoder", "tapas", "trocr", "tvlt", "tvp", "udop", "video_llava", "vilt", "vipllava", "vision-encoder-decoder", "vision-text-dual-encoder", "visual_bert", "voxtral", "xclip", ], }, "reinforcement": { "name": "Reinforcement Learning", "color": "#EF4444", # Soft red "models": ["decision_transformer", "trajectory_transformer"], }, "timeseries": { "name": "Time Series Models", "color": "#F97316", # Soft orange "models": ["autoformer", "informer", "patchtsmixer", "patchtst", "time_series_transformer", "timesfm"], }, "graph": { "name": "Graph Models", "color": "#6B7280", # Soft gray "models": ["graphormer"], }, } def get_model_modality(self, model_name: str) -> Dict[str, str]: """Determine the modality category for a given model.""" for modality_key, modality_info in self.modalities.items(): if model_name in modality_info["models"]: return {"key": modality_key, "name": modality_info["name"], "color": modality_info["color"]} # Default to text if not found (most common) return {"key": "text", "name": "Text Models", "color": "#F59E0B"} def parse_release_date_from_file(self, file_path: str) -> Optional[Dict[str, str]]: """Parse the release date line from a model documentation file.""" try: with open(file_path, "r", encoding="utf-8") as f: content = f.read() # Extract model name from file path (always available) model_name = os.path.basename(file_path).replace(".md", "") # Initialize default values release_date = None transformers_date = None # Focus on the end of the sentence - the Transformers addition date is what matters most pattern = ( r"\*This model was released on (.+?) and added to Hugging Face Transformers on (\d{4}-\d{2}-\d{2})\.\*" ) match = re.search(pattern, content) if match: release_date = match.group(1).strip() transformers_date = match.group(2) # Validate the Transformers date (this is the critical one for our timeline) try: datetime.strptime(transformers_date, "%Y-%m-%d") except ValueError: return None # Handle release_date - could be "None" or an actual date if release_date.lower() == "none": release_date = None else: # Try to validate as a date, but don't fail if it's not try: datetime.strptime(release_date, "%Y-%m-%d") except ValueError: # Keep the original value even if it's not a valid date pass else: # No release date pattern found - warn and skip (ignore auto.md intentionally) base = os.path.basename(file_path) if base != "auto.md": print(f"āš ļø Warning: No release/addition dates found in {file_path}; skipping.") return None # Get modality information modality = self.get_model_modality(model_name) # Extract model description description = self.extract_model_description(content) # Get supported tasks/pipelines tasks = self.get_model_tasks(model_name) return { "model_name": model_name, "file_path": file_path, "release_date": release_date, "transformers_date": transformers_date, "modality": modality["key"], "modality_name": modality["name"], "modality_color": modality["color"], "description": description, "tasks": tasks, } except Exception as e: print(f"Error processing {file_path}: {e}") return None def extract_model_description(self, content: str) -> str: """Extract the first 1000 characters of model description, excluding HTML/XML tags.""" try: # Remove HTML/XML tags content_no_tags = re.sub(r"<[^>]+>", "", content) # Find the start of the actual description (after the initial metadata) # Look for the first substantial paragraph after the initial lines lines = content_no_tags.split("\n") description_start = 0 # Skip initial metadata, imports, and short lines for i, line in enumerate(lines): stripped = line.strip() if ( len(stripped) > 50 and not stripped.startswith("#") and not stripped.startswith("*This model was released") and not stripped.startswith("
āˆ’
100%
+
Loading timeline...
-
Total Models
-
Displayed Models
-
Date Range
""" with open(os.path.join(template_dir, "timeline.html"), "w", encoding="utf-8") as f: f.write(html_content) def open_browser(): """Open the browser after a short delay.""" time.sleep(1.5) webbrowser.open("http://localhost:5000") def main(): """Main function to run the timeline app.""" print("šŸ¤— Transformers Models Timeline") print("=" * 50) # Create templates create_timeline_template() # Check if docs directory exists if not os.path.exists(docs_dir): print(f"āŒ Error: Documentation directory not found at {docs_dir}") print("Please update the 'docs_dir' variable in the script.") return # Parse models to check if any are found models = parser.parse_all_model_dates() if not models: print(f"āš ļø Warning: No models found with release dates in {docs_dir}") else: print(f"āœ… Found {len(models)} models with release dates") # Run Flask app try: app.run(host="0.0.0.0", port=7860, debug=False) except KeyboardInterrupt: print("\nšŸ‘‹ Timeline server stopped") if __name__ == "__main__": main()