---
title: "ADR-024: Dashboard Widgets"
manual: "TYPO3 LLM Extension"
version: "0.35"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:adr-024@0.35"
source: "Adr/Adr024DashboardWidgets.rst"
modified: "2026-09-16T22:09:16+00:00"
---

# ADR-024: Dashboard Widgets

-   *Status:* Accepted
-   *Date:* 2026-04
-   *Authors:* Netresearch DTT GmbH

## Context

`tx_nrllm_service_usage` has tracked per-request cost and usage from day
one, but the data was only reachable through the backend module's report
views. Administrators wanted an at-a-glance view next to everything else
they already follow — scheduled tasks, indexing, form submissions — which
lives on TYPO3's dashboard.

## Decision

Ship two widgets that reuse TYPO3's built-in widget classes and wire them
up with nr-llm-specific data providers:

-   **AI cost this month** — `NumberWithIconWidget` backed by
    `MonthlyCostDataProvider`, which delegates to
    `UsageTrackerService::getCurrentMonthCost()`. Returns dollars
    floored to an integer; the dashboard tile is a glance-value, not an
    accounting figure.
-   **AI requests by provider (7d)** — `BarChartWidget` backed by
    `RequestsByProviderDataProvider`, which aggregates every service
    type (chat, vision, translation, speech, image) by `service_provider`
    over the last seven days.

Both are registered in a dedicated `Configuration/Services.Dashboard.php`
imported conditionally from `Configuration/Services.php` only when
`interface_exists(TYPO3\CMS\Dashboard\Widgets\WidgetInterface::class)`.
A PHP config file (not YAML) is used so the import can be guarded by that
runtime `interface_exists()` check. Without the guard, TYPO3 instances that
do not have `typo3/cms-dashboard` installed would fail at container
compile time on the unresolved widget class.

Classes/Widgets/\* is excluded from the global auto-registration in
`Services.yaml` for the same reason — the data provider classes
import dashboard interfaces and must not be loaded when dashboard is
absent.

## Trade-offs

-   **\+ Reuse core widget classes.** Two core TYPO3 widget types cover the
    useful shapes. Writing a custom widget buys nothing.
-   **\+ Optional dependency.** `typo3/cms-dashboard` is a `suggest`,
    not a hard `require`. Installs without dashboard lose the widgets but
    pay no runtime cost and see no container errors.
-   **\- Two data-shape spots.** The row-shaping logic on
    `RequestsByProviderDataProvider::shapeChartData()` is static for
    unit-testability, but the SQL lives in an instance method bound to
    `ConnectionPool`. The trade-off keeps unit tests honest and
    functional coverage narrow.
-   **\- Flooring the cost.** Displaying `$12.97` as `12` is
    jarring for cost-sensitive users but the widget API returns `int`.
    Follow-up: a custom template could render the subtitle with fractional
    digits once we have one.

## Alternatives considered

-   **Custom widget classes** implementing `WidgetInterface` directly.
    Rejected — duplicates what the core widgets already do.
-   **Per-day time series** instead of per-provider aggregate. Interesting
    but the current 7-day window is short enough that the distribution is
    the more useful glance value.
-   **One combined widget** with cost + count + top provider in a single
    tile. Rejected — mixes two summary numbers into one, and forcing both
    to share the `NumberWithIconWidget` shape cripples both.
