-
-
Notifications
You must be signed in to change notification settings - Fork 16
Architecture
This page describes the internal architecture of models for contributors and developers. It covers the module structure, data flow, async patterns, and key design decisions.
- Language: Rust (stable)
- TUI framework: ratatui with crossterm backend
- Async runtime: tokio
-
HTTP client: reqwest with
rustls-tls-native-roots(loads certificates from the OS trust store) - Serialization: serde + serde_json + toml
- CLI framework: clap (derive)
- Markdown parsing: comrak (CLI only), custom regex-based converter (TUI)
┌──────────────┐
│ models.dev │
│ API │
└──────┬───────┘
│
┌──────────────┐ │ ┌──────────────┐
│ Artificial │ │ │ GitHub │
│ Analysis │ │ │ REST API │
│ (CDN) │ │ │ │
└──────┬───────┘ │ └──────┬────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────┐
│ tokio::spawn tasks │
│ (background fetch workers) │
└──────────────────┬──────────────────────────┘
│ mpsc channels
▼
┌─────────────────────────────────────────────┐
│ Message enum │
│ (ModelsLoaded, BenchmarksLoaded, │
│ AgentsLoaded, StatusLoaded, ...) │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ App::update() │
│ (state mutation) │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ draw() │
│ (render current state) │
└─────────────────────────────────────────────┘
The TUI follows an Elm-architecture pattern:
-
Events arrive as crossterm
KeyEvents or asyncMessages via mpsc channels -
event.rsmaps key events toMessagevariants using theNavActiondedup pattern -
App::update()processes messages and mutates state -
draw()renders the current state to the terminal
There are no callbacks. All state changes flow through the Message enum.
src/tui/
├── mod.rs # Startup, event loop, async channel handling
├── app.rs # App struct, Tab enum, Message enum, update() logic
├── event.rs # Keybinding -> Message mapping, NavAction dedup
├── ui.rs # draw(), shared helpers (focus_border, caret, selection_style)
├── markdown.rs # Custom regex-based markdown converter
├── models/
│ ├── app.rs # ModelsApp state, Focus, Filters, SortOrder
│ └── render.rs # Models tab rendering
├── agents/
│ ├── app.rs # AgentsApp state, AgentFocus, AgentSortOrder
│ └── render.rs # Agents tab rendering, picker modal
├── benchmarks/
│ ├── app.rs # BenchmarksApp state, BenchmarkFocus, BottomView
│ ├── render.rs # Benchmarks tab rendering
│ ├── compare.rs # H2H table, scatter plot
│ └── radar.rs # Radar chart rendering
├── status/
│ ├── app.rs # StatusApp state, StatusFocus, panel focus enums
│ ├── render.rs # Status tab dispatch + shared helpers
│ ├── overall.rs # Overall dashboard rendering
│ └── detail.rs # Provider detail rendering
└── widgets/
├── scrollable_panel.rs # ScrollablePanel (bordered scroll with scrollbar)
├── scroll_offset.rs # ScrollOffset (Cell<u16> for render-time writeback)
├── soft_card.rs # SoftCard (health-colored accent stripe cards)
└── comparison_legend.rs # ComparisonLegend (benchmarks compare views)
Each tab follows the same pattern: app.rs for state and types, render.rs for drawing. The tab's mod.rs re-exports via pub use app::*.
src/
├── data.rs # Provider/Model data structures (models.dev API)
├── api.rs # Model data fetching
├── config.rs # User config file (TOML)
├── formatting.rs # Shared utilities (truncate, parse_date, format_tokens, etc.)
├── provider_category.rs # Provider categorization logic
├── agents/
│ ├── data.rs # Agent, GitHubData, AgentEntry types
│ ├── loader.rs # Load embedded agents.json
│ ├── detect.rs # Version detection (runs CLI commands)
│ ├── github.rs # GitHub API fetching (releases, repo metadata)
│ ├── cache.rs # Disk cache with ETag conditional fetching
│ ├── changelog_parser.rs # Comrak AST -> normalized IR (ChangelogBlock)
│ └── health.rs # Agent-to-status-provider mapping
├── benchmarks/
│ ├── store.rs # BenchmarkStore/BenchmarkEntry types
│ ├── fetch.rs # CDN fetcher
│ └── traits.rs # AA <-> models.dev matching
└── status/
├── types.rs # Status IR types (ProviderStatus, Incident, etc.)
├── registry.rs # Provider registry and strategy mapping
├── assessment.rs # Health assessment logic
├── fetch.rs # Multi-source status fetching
└── adapters/ # Per-platform parsers (Statuspage, BetterStack, etc.)
src/cli/
├── mod.rs # Module index
├── picker.rs # PickerTerminal, nav helpers, shared styles
├── models.rs # Models picker (filter, sort, preview)
├── benchmarks.rs # Benchmarks picker
├── agents.rs # Clap schema, command dispatch
├── agents_ui.rs # Release browser, changelog search, source picker
├── list.rs # `models list` subcommand
├── search.rs # `models search` subcommand
├── show.rs # `models show` subcommand
└── styles.rs # Shared CLI colors
All three inline pickers (models, benchmarks, agents) use ratatui Viewport::Inline with the shared PickerTerminal wrapper for raw mode lifecycle.
Background fetches use tokio::spawn + mpsc channels:
- At startup, background tasks are spawned for model, benchmark, agent, and status data fetching
- Each task sends results through an mpsc channel as
Messagevariants - The main event loop polls both the terminal (key events) and the mpsc receiver
- When a message arrives,
App::update()processes it and the nextdraw()call reflects the new state
The app never blocks on network calls. Loading states are shown until data arrives.
event.rs defines a NavAction enum to avoid duplicating keybinding logic:
NavAction: Down | Up | First | Last | PageDown | PageUp | FocusLeft | FocusRight | Search | ClearEsc
parse_nav_key() maps crossterm KeyCode to NavAction (handling vim keys, arrow keys, Page Up/Down, etc. in one place). Each tab-specific handler then converts NavAction to tab-specific Message variants.
- No disk cache for benchmarks -- data is fetched fresh from CDN on every launch. The store is empty until the CDN responds.
-
Embedded agent catalog --
data/agents.jsonis compiled into the binary viainclude_str!. No runtime file dependency. - ETag conditional fetching for GitHub -- minimizes API calls. Cache stores ETags alongside data; 304 responses reuse cached data.
-
ScrollOffset(Cell<u16>)-- interior mutability enables render-time scroll clamping and writeback without requiring&mut selfin render functions. -
rustls-tls-native-roots-- uses OS certificate store instead of bundled certs, supporting corporate TLS-inspecting proxies. - Separate markdown renderers -- CLI uses comrak (CommonMark AST) for plain-text changelog output; TUI uses a custom regex-based converter that preserves inline formatting for styled ratatui rendering.
| Workflow | Trigger | Purpose |
|---|---|---|
ci.yml |
PR/push | Format check, clippy, tests |
release.yml |
v* tags |
Build 5 targets, package .deb/.rpm, publish to crates.io, update AUR |
update-benchmarks.yml |
Every 30 min | Fetch AA API, commit if data changed |
Pre-release tags (containing -) skip crates.io publish and AUR update, and mark the GitHub release as prerelease.
- Create
src/tui/{tab}/withmod.rs,app.rs,render.rs - In
mod.rs:pub mod app; pub(in crate::tui) mod render; pub use app::*; - Add
pub mod {tab};totui/mod.rs - Add
{Tab}variant toTabenum intui/app.rs - Add tab-specific
Messagevariants - Implement
update()handlers intui/app.rs - Add render call in
ui.rs - Add keybinding handlers to
event.rsusingNavActionpattern - Add footer hints and help text to
ui.rs
Repository · Issues · Releases · brew install models · MIT License