Skip to content

Add Settings page with Usage panel, data hook, and feature-flag gating #8379

Description

@valerydluski

Description

User Story

As a user of the DIAL chat app
I want to open a Settings page from the user menu and see my current rate-limit/usage status
So that I can monitor my usage without leaving the app — while the feature is still gated
behind a flag so it can be rolled out per environment

Acceptance Criteria

  • When SettingsPageEnabled (env SETTINGS_PAGE_ENABLED) resolves true, a gear icon appears in
    the UserMenu dropdown; activating it (click or keyboard) navigates to /settings.
  • When the flag resolves false (its default), the gear icon is omitted entirely from the menu,
    and direct navigation to /settings redirects (replacing history) to the root route instead of
    rendering the page.
  • /settings renders a vertical navigation panel (SettingsPanel, from the new
    @epam/ai-dial-settings-panel library) with a Usage row, selected by default.
  • Opening the Usage tab invokes useUsageData, which calls GET /api/v1/user/usage and exposes
    { usage, isLoading, usageError }. (Originally also called GET /api/v1/user/limits in
    parallel; that call was removed as a follow-up correction — see below — once it was confirmed
    usage's top-level cost fields already carry the real global budget.)
  • The Usage tab renders three period cards — Today, This week, This month — mapped from usage
    via apps/chat/src/utils/map-usage-data-to-dashboard.ts, using the new
    @epam/ai-dial-usage-dashboard library (UsageLimitCardGroup/UsageLimitCard). Each card shows
    the used amount, "used of $total", a progress bar, "$remaining left", and a used-percent caption,
    plus a Default ("Within limits") / RunningLow / LimitReached status badge — colors and
    layout matched against the Figma spec (DIAL 2.0 Concept, node 1106-189).
  • Per-model/per-function token and request metering (the "Model limits" / "Function limits"
    sections of the Figma design) is intentionally out of scope for this ticket and remains a
    follow-up
    delivered as a follow-up, see "Model limits" section below. Per-model metering
    is done; per-function (toolset) metering remains a further follow-up.
  • While GET /api/v1/user/limits / GET /api/v1/user/usage return 502 on the backend, the tab
    falls back to a temporary in-memory mock dataset (deliberately spanning all three statuses) so
    the UI is reviewable; the fallback and its flag are isolated in one file
    (usage-tab-temp-mock.ts) to delete once the backend is fixed.
  • On the backend, DIAL Core requests for /v1/user/limits and /v1/user/usage emit debug-level
    logs (request start, response status/body, error body) without changing response behavior, to
    aid support/troubleshooting.

Model limits (per-model table) — follow-up, delivered

Adds a "Model limits" table below the three aggregate cards, showing per-model Cost/Tokens/Requests
usage and status, with a period selector.

  • New ModelLimitsSection component (@epam/ai-dial-usage-dashboard): heading with model count, a
    controlled period selector (Last minute / Last hour / Last 24 hours / Last 7 days / Last 30 days),
    and a table with Item, Cost, Tokens, Requests, and Status columns — one row per model present in
    the fetched usage data. Fully presentational: normalized ModelLimitRow/ModelLimitMetricCell
    types, a ModelLimitStatus enum, and a five-value ModelLimitsPeriod enum, all free of Core DTO
    field names and the 2**53 unlimited sentinel.
  • New app-level adapter apps/chat/src/utils/map-user-usage-to-model-limits.ts: reads
    usage.deployments (already fetched by useUsageData — no new API call), joins deployment IDs
    with model/catalog metadata (useDeployments().items), maps the selected period to the correct
    *Stats fields, detects finite/unlimited/unavailable per metric, computes per-metric and
    per-row status (RunningLow at ≥75% used, LimitReached at ≥100%), and formats all display and
    accessible-label strings.
  • Row set and order are exactly Object.keys(usage.deployments) — the table shows only models
    present in the fetched usage data (never more, never fewer), independent of the deployments
    catalog's load state or order, so there's no visible reflow once the catalog finishes loading.
  • Period selector exposes all granularities the upstream data actually has: minute*Stats/
    hourRequestStats in addition to day/week/month. Cost/Tokens have no hour-level field
    (Unavailable for "Last hour"); Requests have no minute/week/month-level field (Unavailable
    for those periods) — never a silent fallback to a different period's field.
  • Per-model cost is always shown as attributed spend + "No limit" (never a progress bar or a
    finite cost status), since the upstream contract's per-deployment cost total is always the
    unlimited sentinel — the real budget is the global one already shown in the aggregate cards.
  • Correction — dropped the GET /api/v1/user/limits fetch entirely: confirmed against real
    production payloads that GET /api/v1/user/usage's top-level dayCostStats/weekCostStats/
    monthCostStats already carry the same real global cost budget GET /api/v1/user/limits would
    report, so the second endpoint call was redundant. useUsageData now calls only getUserUsage();
    UseUsageDataResult is { usage, isLoading, usageError } (dropped limits/limitsError); the
    previous partial-vs-full-failure notification distinction collapsed to one failure mode.
    getUserLimits() itself is left in place, unused, for a future feature that might need it.
  • Integrated below UsageLimitCardGroup in UsageTab, reusing the existing loading/error/
    notification behavior without emitting a duplicate notification; shows a localized empty state
    when usage.deployments is absent/empty.

Definition of Done

  • Unit tests pass: apps/chat (211 test files / 3016+ tests), libs/settings-panel (12/12),
    libs/usage-dashboard (32/32, including 17 new ModelLimitsSection tests), apps/chat-api
    (feature-flag registration + logging). New adapter test file map-user-usage-to-model-limits.spec.ts
    (29 tests).
  • nx lint / nx typecheck / nx build clean for chat, chat-api, usage-dashboard, and
    @epam/ai-dial-settings-panel. nx affected lint/test/build against origin/development clean.
  • i18n keys added for all new user-visible strings (including the Model limits column headers,
    period labels, and unavailable/no-limit text); no missing translations. The now-unreachable
    PartialLoadError key was removed.
  • SettingsPanel, UsageLimitCard/UsageLimitCardGroup, and ModelLimitsSection use only CSS
    logical properties (RTL-safe layout).
  • Manual visual QA against the Figma design (DIAL 2.0 Concept, node 1106-189) done for the three
    aggregate cost cards and the Model limits table.
  • npm run validate:docs passes; libs/usage-dashboard/README.md documents ModelLimitsSection
    and its types.

Related issues

No response

Details

  • apps/chat-api/src/deployments/details/deployments-details.service.tsgetUserLimits/
    getUserUsage now log: a debug line before each DIAL Core call; a debug line with status +
    body when DIAL Core returns an error (now also passed into mapDialHttpStatus so the mapped
    exception carries DIAL Core's own message); a debug line with the raw response body on success;
    and, in the catch block, a debug line that distinguishes an already-mapped HttpException
    being re-thrown from a genuine unexpected/network error.
  • apps/chat-api/src/app-config/feature-flags/feature-key.enum.ts /
    config-registry.constants.tsSettingsPageEnabled feature key (type: 'feature',
    visibility: 'client', defaultValue: false), driven by SETTINGS_PAGE_ENABLED, matching the
    existing scheduledTasksEnabled shape.
  • libs/settings-panel (@epam/ai-dial-settings-panel) and libs/usage-dashboard
    (@epam/ai-dial-usage-dashboard) — both tagged type:ui, purely presentational, no i18n, no
    apps/chat/src/server-api/*, no routing inside the lib; host-specific labels/icons/data are
    passed in as props, per the repo's library isolation rules.
  • apps/chat/vite.config.mts needed a resolve.alias entry for each new lib (pointing at its
    src/index.ts) — without it, a lib's CSS Modules/Tailwind-compiled styles never reach the app
    bundle; the dev server silently serves the library's stale prebuilt dist/index.js.
  • Spans several archived OpenSpec changes for full history:
    openspec/changes/archive/2026-08-19-settings-usage-page/,
    openspec/changes/archive/2026-08-19-settings-sidebar-panel/,
    openspec/changes/archive/2026-08-19-gate-settings-page-feature-flag/,
    openspec/changes/archive/2026-08-21-add-usage-dashboard-model-limits/ (the Model limits
    follow-up covered above).

Confidential information

  • I confirm that do not share any confidential information

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P3 – MediumSecondary flow or UX is degraded. No data loss. Reasonable workaround availableenhancementNew feature or requestto-be-documentedImprovements or additions to documentation

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions