# Models & context

A chat app that switches between models and watches a live context-window meter fill as the conversation grows — both driven by kai-chat's built-in header controls.

A chat that lets the user pick the active model and see how much of its context window the conversation has consumed. The model switcher and the token meter both live in <code>&lt;kai-chat&gt;</code>'s header. Switch models to rescale the window; send a message to watch the meter fill as the reply streams.

## How it works

`<kai-chat>` renders both header controls for you. Set `models` and `currentModel` to get the switcher dropdown, set `context` to get the token meter, and listen for `kai-model-change` to react to a selection. No separate elements to mount.

```html
<kai-chat id="chat" chat-title="Board prep"></kai-chat>

<script type="module">

  await customElements.whenDefined('kai-chat');

  const chat = document.getElementById('chat');

  // Each model has its own context window, so switching rescales the meter.
  const MAX_TOKENS = {
    'claude-opus-4-8': 200_000,
    'claude-sonnet-4-6': 1_000_000,
    'claude-haiku-4-5': 200_000,
  };

  let modelId = 'claude-opus-4-8';
  let usedTokens = 12_400;

  // Arrays and objects go in as JS properties, not attributes.
  chat.messages = [];
  chat.models = [
    { id: 'claude-opus-4-8', name: 'Claude Opus 4.8', provider: 'Anthropic' },
    { id: 'claude-sonnet-4-6', name: 'Claude Sonnet 4.6', provider: 'Anthropic' },
    { id: 'claude-haiku-4-5', name: 'Claude Haiku 4.5', provider: 'Anthropic' },
  ];
  chat.currentModel = modelId;

  function pushContext() {
    // Reassign a fresh object so the meter re-renders.
    chat.context = {
      usedTokens,
      maxTokens: MAX_TOKENS[modelId],
      inputTokens: Math.round(usedTokens * 0.62),
      outputTokens: Math.round(usedTokens * 0.38),
    };
  }
  pushContext();

  // Selecting a model swaps the active label and rescales the window.
  chat.addEventListener('kai-model-change', (e) => {
    modelId = e.detail.modelId;
    chat.currentModel = modelId;
    pushContext();
  });

  chat.addEventListener('kai-submit', async (e) => {
    const aId = crypto.randomUUID();
    chat.messages = [
      ...chat.messages,
      { id: crypto.randomUUID(), role: 'user', parts: [{ type: 'text', text: e.detail.value }] },
      { id: aId, role: 'assistant', parts: [] },
    ];
    chat.loading = true;

    // Stream the reply and grow usedTokens as output arrives.
    let answer = '';
    for await (const token of streamReply(e.detail.value, modelId)) {
      answer += token;
      chat.messages = chat.messages.map((m) =>
        m.id === aId ? { ...m, parts: [{ type: 'text', text: answer }] } : m,
      );
      usedTokens += estimateTokens(token);
      pushContext();
    }

    chat.loading = false;
  });
</script>
```

The switcher only renders when you provide more than one model. The meter turns yellow once usage crosses `warnThreshold` (default 70%) and red past `dangerThreshold` (default 90%) — handy for warning users before a long thread overflows the window.

**`context` shape** — the usage object you assign to `chat.context`:

| field | type | notes |
|---|---|---|
| `usedTokens` | `number` | Tokens consumed so far — drives the meter fill |
| `maxTokens` | `number` | The model's full context window |
| `inputTokens` | `number?` | Prompt tokens, shown in the hover breakdown |
| `outputTokens` | `number?` | Generated tokens, shown in the hover breakdown |
| `estimatedCost` | `number?` | Running cost in dollars, shown in the footer |

## Next steps

- **[`kai-chat` reference](/components/chat/)** — every header prop, including `models`, `currentModel`, and `context`.
- **[`kai-model-switcher` reference](/components/model-switcher/)** — the standalone switcher with a `models` property or declarative `<kai-model>` children.
- **[`kai-context` reference](/components/context/)** — the standalone meter, configurable `warnThreshold` / `dangerThreshold`, and the `kai-threshold-change` event.
- **[Drop-in chat](/examples/drop-in-chat/)** — the baseline streaming loop this example extends.
