# Model Cache Table

Model Cache Table [#model-cache-table]

The **Model Cache Table** renders one row per cached model showing the model ID (monospace, truncated with a full-ID tooltip), a status badge (loaded / loading / error), the load duration (ms below one second, seconds above), and a relative last-used time. A size column renders only when at least one entry carries `sizeBytes` (human-formatted), and a per-row evict control renders only when `onEvict` is provided. With no models present it shows an empty state explaining that model loads are captured automatically once `enableDevTools()` runs. Entries are passed in as props — works with any backend (recommended producer: `useDevToolsModelCache` from `@localmode/devtools/react`, whose snapshot passes straight into `entries`).

Preview [#preview]

```tsx
'use client';

import * as React from 'react';
import { ModelCacheTable, type ModelCacheEntryLike } from '@/components/model-cache-table';

/** Fixture cache entries with last-used timestamps relative to mount. */
function buildFixtureEntries(): Record<string, ModelCacheEntryLike> {
  const now = Date.now();
  return {
    'Xenova/bge-small-en-v1.5': {
      modelId: 'Xenova/bge-small-en-v1.5',
      status: 'loaded',
      loadDurationMs: 1840,
      lastUsed: new Date(now - 42_000).toISOString(),
      sizeBytes: 34_100_000,
    },
    'onnx-community/Qwen2.5-0.5B-Instruct': {
      modelId: 'onnx-community/Qwen2.5-0.5B-Instruct',
      status: 'loading',
      loadDurationMs: 0,
      lastUsed: new Date(now).toISOString(),
    },
    'Xenova/whisper-tiny.en': {
      modelId: 'Xenova/whisper-tiny.en',
      status: 'error',
      loadDurationMs: 460,
      lastUsed: new Date(now - 5 * 60_000).toISOString(),
    },
  };
}

/**
 * Demo for ModelCacheTable. Renders three fixture entries — a loaded
 * embedding model carrying a size, an LLM mid-load (pulsing badge, no load
 * time yet), and a failed Whisper load (destructive badge). Evicting removes
 * the row from local state; evicting all three reveals the empty state. Wire
 * `entries` to `useDevToolsModelCache()` from `@localmode/devtools/react` in
 * your app. Fully presentational — no model download.
 */
export default function ModelCacheTableDemo() {
  const [entries, setEntries] =
    React.useState<Record<string, ModelCacheEntryLike>>(buildFixtureEntries);

  const evict = (modelId: string) => {
    setEntries((prev) => {
      const next = { ...prev };
      delete next[modelId];
      return next;
    });
  };

  return <ModelCacheTable entries={entries} onEvict={evict} />;
}
```

Installation [#installation]

```bash
npx shadcn@latest add @localmode/ui/devtools/model-cache-table
```

Data source & dependencies [#data-source--dependencies]

**Data source:** renders the cached-model record you pass — works with any backend. Recommended producer: `useDevToolsModelCache` from `@localmode/devtools/react` (on-device, optional) — its `Record<string, ModelCacheInfo>` snapshot matches `entries` field-for-field, no mapping layer needed.

* `lucide-react` — icons
* `clsx` + `tailwind-merge` — via the shared `cn()` util (installed automatically as a registry dependency)

Files installed [#files-installed]

* `model-cache-table.tsx` — the `ModelCacheTable` component
* `lib/utils.ts` — the `cn()` helper (if not already present)

Props [#props]

**ModelCacheTable**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `entries` | `Record<string, object>` | — | **Required.** Cached-model entries, keyed by model ID. |
| `onEvict` | `function` | — | When provided, renders a per-row evict control invoking this callback. |

**ModelCacheEntryLike**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `modelId` | `string` | — | **Required.** Model identifier. |
| `status` | `"error" \| "loading" \| "loaded"` | — | **Required.** Current status. |
| `loadDurationMs` | `number` | — | **Required.** Load duration in milliseconds. |
| `lastUsed` | `string` | — | **Required.** Last time the model was used (ISO timestamp). |
| `sizeBytes` | `number` | — | On-disk size in bytes, when known. |

Examples [#examples]

Wired to the LocalMode devtools bridge [#wired-to-the-localmode-devtools-bridge]

Enable devtools once, then feed the hook's snapshot straight into the table — every model load, load failure, and inference use is captured automatically:

```tsx
import { enableDevTools } from '@localmode/devtools';
import { useDevToolsModelCache } from '@localmode/devtools/react';

import { ModelCacheTable } from '@/components/model-cache-table';

// Once, at app init (before loading models)
enableDevTools();

// The monitoring surface
function ModelsPanel() {
  const models = useDevToolsModelCache();
  return <ModelCacheTable entries={models} />;
}
```

With sizes and eviction [#with-sizes-and-eviction]

The bridge snapshot doesn't carry sizes or eviction today — supply `sizeBytes` from your own cache accounting and wire `onEvict` to your provider's cache management (e.g. `refreshModel()` from `@localmode/wllama`):

```tsx
<ModelCacheTable
  entries={{
    'my-model.gguf': {
      modelId: 'my-model.gguf',
      status: 'loaded',
      loadDurationMs: 2140,
      lastUsed: new Date().toISOString(),
      sizeBytes: 668_000_000,
    },
  }}
  onEvict={(modelId) => evictFromCache(modelId)}
/>
```

Any backend [#any-backend]

`entries` is just a record of per-model info — feed it from your own model manager, a server metrics endpoint, or a test fixture:

```tsx
<ModelCacheTable
  entries={{
    'sentence-encoder': {
      modelId: 'sentence-encoder',
      status: 'loaded',
      loadDurationMs: 640,
      lastUsed: '2026-07-03T09:30:00.000Z',
    },
  }}
/>
```

Customization [#customization]

Loaded models badge emerald, in-flight loads badge amber with a pulsing dot (and render an em dash instead of a load time), and failed loads use the theme's `destructive` token. Load durations render as milliseconds below one second and seconds above; last-used renders relative ("3m ago") with the exact timestamp on hover; sizes are human-formatted by a local `formatBytes`. The size column and the evict column each appear only when their data (`sizeBytes` on any entry) or callback (`onEvict`) is supplied. Styled entirely with shadcn/ui CSS-variable utilities so it inherits your theme — because you own the copied file, every class, tone, and threshold is yours to change.