Skip to content
Brok UI

Loading…

No results

AI Stop Reason

Open source

Completion status card mapping a finish reason to a coloured badge and token count.

Version
v1.1.2
Stability
stable
License
MIT
Related
Chat Bubble
AI Function Call
AI Chain of Thought

Preview

Completed The model finished its turn. 512 tokens 128 in, 384 out
Cut off Hit the max tokens limit before finishing. 4,096 tokens
Tool call Called get_weather('Amsterdam'). 318 tokens
Filtered The output was withheld by the safety filter. 64 tokens
Stopped Generation was stopped by the user. 212 tokens 0.6s
Completed 512 Cut off 4,096 Tool call Error
previews.components.ai-stop-reason.default.blade.php Blade
<div class="mx-auto w-full max-w-md space-y-2">
    <x-ui.ai.stop-reason reason="stop" :input-tokens="128" :output-tokens="384" :duration="1.8" />
    <x-ui.ai.stop-reason reason="length" :tokens="4096" />
    <x-ui.ai.stop-reason reason="tool_use" :tokens="318" detail="Called get_weather('Amsterdam')." />
    <x-ui.ai.stop-reason reason="content_filter" :tokens="64" />
    <x-ui.ai.stop-reason reason="cancelled" :tokens="212" :duration="0.6" />
    <div class="flex flex-wrap gap-2 pt-2">
        <x-ui.ai.stop-reason variant="pill" reason="stop" :tokens="512" />
        <x-ui.ai.stop-reason variant="pill" reason="length" :tokens="4096" />
        <x-ui.ai.stop-reason variant="pill" reason="tool_use" />
        <x-ui.ai.stop-reason variant="pill" reason="error" />
    </div>
</div>

Installation

terminal
php artisan ui:add ai-stop-reason

Registry contract

php artisan ui:add ai-stop-reason writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.

  • blade resources/views/components/ui/ai/stop-reason.blade.php
Registry dependencies
None — installs on its own.
Packages
composer: jml/brok:^0.2

Use with AI

A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.

ai-stop-reason.md
# Brok UI: AI Stop Reason (`ai-stop-reason`)

Completion status card mapping a finish reason to a coloured badge and token count.

Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.

## Install

```bash
php artisan ui:add ai-stop-reason
```

## Usage

```blade
<div class="mx-auto w-full max-w-md space-y-2">
    <x-ui.ai.stop-reason reason="stop" :input-tokens="128" :output-tokens="384" :duration="1.8" />
    <x-ui.ai.stop-reason reason="length" :tokens="4096" />
    <x-ui.ai.stop-reason reason="tool_use" :tokens="318" detail="Called get_weather('Amsterdam')." />
    <x-ui.ai.stop-reason reason="content_filter" :tokens="64" />
    <x-ui.ai.stop-reason reason="cancelled" :tokens="212" :duration="0.6" />
    <div class="flex flex-wrap gap-2 pt-2">
        <x-ui.ai.stop-reason variant="pill" reason="stop" :tokens="512" />
        <x-ui.ai.stop-reason variant="pill" reason="length" :tokens="4096" />
        <x-ui.ai.stop-reason variant="pill" reason="tool_use" />
        <x-ui.ai.stop-reason variant="pill" reason="error" />
    </div>
</div>
```

## Props

- `reason` (string, default `stop`)
- `tokens` (mixed|null, default `null`)
- `inputTokens` (mixed|null, default `null`)
- `outputTokens` (mixed|null, default `null`)
- `duration` (mixed|null, default `null`)
- `detail` (mixed|null, default `null`)
- `variant` (string, default `row`)

## Use when

- Use to expose AI-specific prompts, states, or reasoning metadata when those details help users supervise or understand an AI workflow.
- Reporting why a completion ended, with its token usage, under a response.
- Debugging generation in an eval or playground view where finish reasons matter.

## Avoid when

- Do not surface internal AI mechanics when they add confusion or implementation detail without user value.
- Showing an ongoing request; this reports a finished one.
- Conveying a status unrelated to a completion; status is the general primitive.

## Anti-patterns

- Decorative AI chrome
- Exposing internal mechanics without user value

## Rules

- Use the `<brok:ai-stop-reason>` tag (or `<x-ui.ai-stop-reason>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.

## Links

- Docs: https://brokui.dev/docs/components/ai-stop-reason
- Registry JSON (files, props, contract): https://brokui.dev/r/open/ai-stop-reason.json

Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.

API

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
Prop Type Default Description
reason string stop
tokens mixed | null null
inputTokens mixed | null null
outputTokens mixed | null null
duration mixed | null null
detail mixed | null null
variant string row

Slots

  • default —

Data slots

Stable hooks for CSS overrides and browser tests.

ai-stop-reason

Behavior

  • reason selects the badge colour and wording; unknown reasons render neutrally instead of failing.
  • Token, duration and detail lines appear only when given, and variant switches between a row and a card.
  • Declares registry capability flags: a11y, responsive, rtl, darkMode, localized.

Guidance

AI-assisted interaction

Help users prompt, inspect, or supervise an AI-assisted operation.

Use when

  • Use to expose AI-specific prompts, states, or reasoning metadata when those details help users supervise or understand an AI workflow.
  • Reporting why a completion ended, with its token usage, under a response.
  • Debugging generation in an eval or playground view where finish reasons matter.

Avoid when

  • Do not surface internal AI mechanics when they add confusion or implementation detail without user value.
  • Showing an ongoing request; this reports a finished one.
  • Conveying a status unrelated to a completion; status is the general primitive.

Use instead

  • Deterministic conventional workflow

Anti-patterns

  • Decorative AI chrome
  • Exposing internal mechanics without user value
Anatomy
root
Theming hooks
ai-stop-reason

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
Keyboard
No component-owned keyboard interaction; native element behavior applies.
Focus
none
  • The card is a role="status" region, so a finish reason arriving after the response is announced.
  • The reason is stated as text beside its badge; the colour is reinforcement, not the signal.
  • Semantic HTML and a stable data-slot attribute for styling and scripting hooks.
  • Focus-visible rings use the ring token, so keyboard focus is always visible.
  • Disabled and invalid states are conveyed to assistive tech, not by color alone.
  • Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
  • Labels go through __() and layout uses logical properties (ms-*, text-start), so it mirrors under dir="rtl" — flip the preview to RTL to confirm.
  • Dark mode uses the same semantic tokens under the dark class; high contrast follows forced-color system tokens.

Livewire

Safe

Livewire can update this component through forwarded wire:* attributes.

Source

The exact, editable file ui:add writes into your app. Previews render this same code; there are no preview-only components.

resources/views/components/ui/ai/stop-reason.blade.php Blade
@props([
    // stop | end_turn | length | max_tokens | tool_use | content_filter | refusal | error | cancelled
    'reason' => 'stop',
    'tokens' => null,
    'inputTokens' => null,
    'outputTokens' => null,
    // Seconds; shown as "1.8s".
    'duration' => null,
    // A one-line explanation under the label; the reason's default is used when null.
    'detail' => null,
    // row (a full-width status line) | pill (an inline chip)
    'variant' => 'row',
])

@php
    $styles = require base_path(config('ui.component_path', 'resources/views/components/ui').'/_styles.php');
    $variant = $styles['normalizeVariant']($variant);
    $variant = $variant === 'pill' ? 'pill' : 'row';

    // Map the finish reason → label, one-line explanation, tone and glyph.
    // Unknown reasons fall back to the neutral "stop" mapping.
    $map = [
        'stop' => ['label' => __('Completed'), 'detail' => __('The model finished its turn.'), 'tone' => 'success', 'icon' => 'M20 6 9 17l-5-5'],
        'length' => ['label' => __('Cut off'), 'detail' => __('Hit the max tokens limit before finishing.'), 'tone' => 'warning', 'icon' => 'M12 9v4M12 17h.01M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z'],
        'tool_use' => ['label' => __('Tool call'), 'detail' => __('Paused to call a tool; send the result to continue.'), 'tone' => 'info', 'icon' => 'M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z'],
        'content_filter' => ['label' => __('Filtered'), 'detail' => __('The output was withheld by the safety filter.'), 'tone' => 'destructive', 'icon' => 'M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10zM9 9l6 6M15 9l-6 6'],
        'refusal' => ['label' => __('Declined'), 'detail' => __('The model declined this request.'), 'tone' => 'destructive', 'icon' => 'M18.36 6.64A9 9 0 1 1 5.64 5.64a9 9 0 0 1 12.72 1zM6 18 18 6'],
        'error' => ['label' => __('Error'), 'detail' => __('The request failed before a reply came back.'), 'tone' => 'destructive', 'icon' => 'M12 8v4M12 16h.01M22 12a10 10 0 1 1-20 0 10 10 0 0 1 20 0z'],
        'cancelled' => ['label' => __('Stopped'), 'detail' => __('Generation was stopped by the user.'), 'tone' => 'muted', 'icon' => 'M7 7h10v10H7z'],
    ];
    $aliases = ['end_turn' => 'stop', 'max_tokens' => 'length', 'tool_calls' => 'tool_use', 'function_call' => 'tool_use', 'safety' => 'content_filter'];
    $key = $aliases[$reason] ?? $reason;
    $current = $map[$key] ?? $map['stop'];
    $detail ??= $current['detail'];

    $tones = [
        'success' => ['dot' => 'bg-success/15 text-success', 'pill' => 'border-success/30 bg-success/10 text-success'],
        'warning' => ['dot' => 'bg-warning/15 text-warning', 'pill' => 'border-warning/30 bg-warning/10 text-warning'],
        'info' => ['dot' => 'bg-info/15 text-info', 'pill' => 'border-info/30 bg-info/10 text-info'],
        'destructive' => ['dot' => 'bg-destructive/15 text-destructive', 'pill' => 'border-destructive/30 bg-destructive/10 text-destructive'],
        'muted' => ['dot' => 'bg-muted text-muted-foreground', 'pill' => 'border-border bg-muted text-muted-foreground'],
    ];
    $tone = $tones[$current['tone']];

    $tokens = $tokens !== null ? max(0, (int) $tokens) : (($inputTokens !== null || $outputTokens !== null) ? (int) $inputTokens + (int) $outputTokens : null);
    $fmtDuration = $duration !== null ? (((float) $duration) < 10 ? number_format((float) $duration, 1).'s' : number_format((float) $duration, 0).'s') : null;
@endphp

{{--
    AI stop reason — why generation finished, as a status line (or an inline
    pill). The glyph and tone carry the outcome, the label names it, the detail
    says what it means, and the numbers on the end (tokens, in/out split,
    duration) line up in tabular figures. role="status" announces the outcome.
--}}
@if ($variant === 'pill')
    <span
        data-slot="ai-stop-reason"
        data-reason="{{ $key }}"
        role="status"
        title="{{ $detail }}"
        {{ $attributes->merge(['class' => 'inline-flex h-7 items-center gap-2 rounded-full border px-2 text-xs font-medium '.$tone['pill']]) }}
    >
        <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="{{ $current['icon'] }}"/></svg>
        {{ $current['label'] }}
        @if ($tokens !== null)
            <span class="tabular-nums opacity-70">{{ number_format($tokens) }}</span>
        @endif
    </span>
@else
    <div
        data-slot="ai-stop-reason"
        data-reason="{{ $key }}"
        role="status"
        {{ $attributes->merge(['class' => 'flex items-center gap-3 rounded-md border border-border bg-card px-3 py-2 text-foreground']) }}
    >
        <span class="flex size-8 shrink-0 items-center justify-center rounded-full {{ $tone['dot'] }}" aria-hidden="true">
            <svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="{{ $current['icon'] }}"/></svg>
        </span>

        <span class="min-w-0 flex-1">
            <span class="block text-sm font-medium leading-tight">{{ $current['label'] }}</span>
            @if ($detail)
                <span class="block truncate text-xs text-muted-foreground">{{ $detail }}</span>
            @endif
        </span>

        @if ($tokens !== null || $fmtDuration !== null)
            <span class="flex shrink-0 flex-col items-end text-xs leading-tight text-muted-foreground">
                @if ($tokens !== null)
                    <span class="tabular-nums"><span class="font-medium text-foreground">{{ number_format($tokens) }}</span> {{ __('tokens') }}</span>
                @endif
                @if ($inputTokens !== null || $outputTokens !== null)
                    <span class="tabular-nums">{{ number_format((int) $inputTokens) }} {{ __('in') }}, {{ number_format((int) $outputTokens) }} {{ __('out') }}</span>
                @elseif ($fmtDuration !== null)
                    <span class="tabular-nums">{{ $fmtDuration }}</span>
                @endif
            </span>
        @endif
    </div>
@endif

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.1.2
Status
Stable
License
open
Accessibility reviewed
No review date recorded
Last breaking change
No date recorded
Deprecation
Not deprecated
Contract
v6
Foundation
≥ 1.0.0