<!-- GENERATED by scripts/build-llms.mjs from llms/conversation.md — do not edit this file. -->

# `lr-generation-metrics`

- **Import** `import '@aceshooting/lyra-ui/components/lr-generation-metrics.js';` (stable tag alias; registers the tag)
- **Class** `LyraGenerationMetrics`, also available unregistered from `@aceshooting/lyra-ui/components/conversation/generation-metrics/generation-metrics.class.js`
- **Family** `components/conversation/` — see `llms/index.md` for its siblings
- **Status** `stable` since `9.0.0` — see the maturity and deprecation policy in `llms/shared.md`
- **Release history** [CHANGELOG.md](../../CHANGELOG.md); family-wide breaking-change summaries: [llms-full.txt](../../llms-full.txt)
- **Deprecations** none
- **Optional peers** none
- **Themeable via** 5 parts, 0 custom properties — see this component's own `@csspart`/`@cssprop` list below
- **Library-wide behavior** (events, form association, `locale`/`strings`, tokens, TS types): `llms/shared.md`

---

## `lr-generation-metrics`

A compact, ticking status readout shown alongside an in-progress AI response: elapsed time, token
count, and token-throughput, plus a built-in Stop button. First-party invention (no Web Awesome
equivalent). Renders as e.g. `12.3s · 340 tokens · 27 tok/s [Stop]`.

The host handles `lr-stop` by stopping its own producer and setting `status` to `complete`. A
restart supplies a fresh `startedAt`, resets its token count, and retires the previous producer
timer. The live example wires this lifecycle on mount, so its first Stop and every Restart take
effect immediately.

**9.0 identity migration:** `lr-generation-status` → `lr-generation-metrics`,
`LyraGenerationStatus` → `LyraGenerationMetrics`, and `LyraGenerationStatusEventMap` →
`LyraGenerationMetricsEventMap`. The old tag, class, event-map name, registration route, and
generated framework members are removed rather than retained as aliases.

**Properties:**

- `status: GenerationMetricsStatus = 'idle'` (`'idle' | 'running' | 'complete'`, reflected) —
  generation lifecycle. `idle` is never-started/reset; `running` ticks and is the only state that
  exposes Stop; `complete` freezes the final metrics. Invalid attribute or property writes normalize
  to `idle`.
- `startedAt?: number` (attribute `started-at`) — epoch-ms timestamp of when generation began.
  Optional — when unset, or set to a value that fails to parse as a finite number (e.g. an ISO-8601
  date string, which `type: Number` conversion turns into `NaN`), while `status` is `running`, this
  component captures `Date.now()` itself the moment `status` becomes `running` and counts from there
  instead — an invalid value is treated identically to "unset", never rendered as literal `"NaNs"`.
- `tokenCount?: number` (attribute `token-count`) — finite values are rounded to a non-negative
  integer; unset/non-finite values omit the `tokens` segment entirely.
- `tokensPerSecond?: number` (attribute `tokens-per-second`) — finite values are clamped to zero or
  above; unset/non-finite values derive from `token-count`/elapsed time once one second has elapsed.
- `showStop: boolean = true` (attribute `show-stop`, **not reflected**) — whether the built-in Stop
  button renders at all. Uses a string-value-aware `ComplexAttributeConverter` (not Lit's default
  presence-based `type: Boolean`), so a plain-HTML `show-stop="false"` content attribute correctly
  turns it off — the literal string `"false"` maps to `false`; the attribute's mere presence with any
  other value (or no value) maps to `true`. A Lit template can instead use a `.showStop=${false}`
  property binding. **Caveat:** a `?show-stop=${false}` boolean-attribute _binding_ still can't turn
  it off when the attribute was never present in markup to begin with — that binding only ever
  removes the attribute when falsy, and removing an attribute that's already absent fires no
  `attributeChangedCallback` (see AGENTS.md); use `.showStop=${false}` or the plain
  `show-stop="false"` string form instead.

**Events:** `lr-stop` (`detail: null`) — fired when the built-in Stop button is clicked while
`status="running"`.

**Slots:** none.

**CSS parts:** `base`, `elapsed` (always rendered, reads `"0.0s"` while idle), `tokens` (only
rendered for a finite `token-count`), `throughput` (only rendered when
a value is available, host-supplied or derived), `stop-button` (only rendered while `show-stop` is
`true` and `status="running"`)

**Themeable custom properties:** shared tokens only — `--lr-color-text-quiet` (base readout and
tokens/throughput text color), `--lr-color-text` (the elapsed segment's higher-contrast color,
and the stop-button's icon color), `--lr-space-s` (stop-button margin), `--lr-icon-button-size`
(stop-button minimum sizing; the full shared 40px-equivalent hit floor applies), `--lr-color-border`/`-surface`/`-brand`
(stop-button border/background/hover), `--lr-focus-ring-width`/`-color`/`-offset`,
`--lr-transition-fast`.

**Optional peer deps:** none.

```html
<lr-generation-metrics
  status="running"
  started-at="1732000000000"
  token-count="340"
  show-stop
></lr-generation-metrics>
<script type="module">
  document
    .querySelector("lr-generation-metrics")
    .addEventListener("lr-stop", () => {
      controller.abort(); // stop the host's own generation
    });
</script>
```

This is deliberately a _different_ concern than `<lr-stream-status>`: that component is about
transport/connection health (idle/connecting/streaming/stalled, heartbeat-aware stall detection),
while this one is a user-facing metrics readout for a generation both components' hosts typically
already know is healthily in progress. Neither imports or depends on the other; compose both side
by side rather than picking one. A finite `tokens-per-second`, when supplied directly, is clamped
non-negative and used;
when omitted, this component derives a live figure from `token-count` divided by elapsed seconds,
but only once at least one full second of elapsed time has accumulated (dividing by a sub-second
window can produce wildly-swinging early readings, e.g. 3 tokens in 40ms reading as "75 tok/s").
Entering `complete` freezes the elapsed clock for a completed-state summary; entering `idle` resets
it to zero. Sub-minute elapsed values use one decimal place until the rounding boundary at 59.95s;
minute values use localized whole minutes and seconds. Token counts use locale-aware plural rules.
Throughput below 10 uses up to one fractional digit and higher values round to whole tokens/sec.

This readout ticks roughly once per second while running, which is exactly the kind of
high-frequency update `<lr-live-region>`/`Announcer` exists to _prevent_ from being read aloud
verbatim — this component therefore carries no `role="status"`/`aria-live` of its own and never
announces anything. A host that wants generation-start/-end announced should pair this with
something that announces state _transitions_ instead. The Stop button gets a normal, always-present
`aria-label="Stop generating"`, no different from any other icon-only button in this library.

**Known gotchas:**

- `showStop` defaults to `true` and is not a reflected property. Its `ComplexAttributeConverter`
  makes the plain content attribute `show-stop="false"` work correctly, but a `?show-stop=${false}`
  Lit boolean-attribute _binding_ still can't turn it off starting from absent markup — see the
  property list above for the exact footgun.
- The derived `tokens-per-second` figure only appears once `elapsedMs >= 1000`; before that, the
  `throughput` part simply doesn't render — supply `tokens-per-second` yourself for a stable figure
  from the very first tick.
- Entering `complete` freezes the elapsed display; entering `idle` resets it to `"0.0s"`.
- `started-at` only re-baselines the ticker at the moment it's read: mounting the component with
  `status="running"` but no `started-at` captures `Date.now()` at that first update, not at
  whatever earlier instant generation may actually have begun.

---
