AI Tools
AI tool surfaces — text writer, image generator, code generator, an assistant chat, a full-height inbox rail, and a model playground, each with live output panels.
Preview
@props([
'title' => 'AI Inbox',
// Shows a close/collapse icon button in the header that dispatches
// `ai-tools-inbox:close` for the host page to act on (e.g. hide the rail).
'dismissible' => false,
// Conversation turns: [{ role: 'user'|'other'|'system', text: '…' }, …].
// Without any, the panel shows a quiet empty state with suggestion chips.
'messages' => null,
])
@php
$hasMessages = is_array($messages) && ! empty($messages);
$threadMessages = $hasMessages
? collect($messages)->map(fn ($m) => [
'role' => $m['role'] ?? 'user',
'name' => $m['name'] ?? null,
'avatar' => $m['avatar'] ?? null,
'content' => $m['text'] ?? ($m['content'] ?? ''),
])->all()
: [];
@endphp
{{--
AI Tools Inbox — a full-height assistant rail meant to sit beside an app
(a right column), not a self-contained card like ai-tools-chat. A fixed
header and composer bracket a scrollable conversation: an empty state
with suggestion chips by default, or the supplied `messages` rendered
through chat-thread. Picking a suggestion fills the composer and moves
focus into it (suggestion-chips-select bubbles up to the root and writes
straight into the ai-prompt textarea so its own Alpine state stays the
single source of truth).
--}}
<section
data-slot="block-ai-tools-inbox"
x-data="{
fillComposer(text) {
const textarea = this.$el.querySelector('[data-slot=\'ai-prompt\'] textarea');
if (! textarea) return;
textarea.value = text;
textarea.dispatchEvent(new Event('input', { bubbles: true }));
textarea.focus();
},
}"
@suggestion-chips-select="fillComposer($event.detail.value)"
{{ $attributes->merge(['class' => 'flex h-full min-h-[640px] w-full flex-col bg-background text-foreground']) }}
>
{{-- Header --}}
<header class="flex shrink-0 items-center justify-between gap-2 px-4 py-4">
<h2 class="truncate text-lg font-semibold text-foreground">{{ __($title) }}</h2>
@if ($dismissible)
<x-ui.button
variant="ghost"
size="sm"
:icon-label="__('Close')"
@click="$dispatch('ai-tools-inbox:close')"
class="text-muted-foreground"
>
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4"><path d="M18 6 6 18M6 6l12 12" /></svg>
</x-ui.button>
@endif
</header>
{{-- Conversation --}}
<div class="min-h-0 flex-1 overflow-y-auto px-4 py-6" role="region" aria-label="{{ __('Conversation') }}">
@if ($hasMessages)
<x-ui.chat-thread :messages="$threadMessages" />
@else
<div class="flex h-full flex-col items-center justify-center gap-4 text-center">
<span class="ui-ai-spark grid size-10 shrink-0 place-items-center rounded-full" aria-hidden="true">
<svg class="size-5" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l1.9 5.1L19 9l-5.1 1.9L12 16l-1.9-5.1L5 9l5.1-1.9L12 2z" /></svg>
</span>
<p class="max-w-[26ch] text-sm text-muted-foreground">{{ __('Ask about your spending, goals or transactions') }}</p>
<x-ui.suggestion-chips
class="justify-center"
:items="[
__('Where did I overspend this month?'),
__('Am I on track for my goal?'),
__('Explain this transaction'),
]"
/>
</div>
@endif
</div>
{{-- Composer --}}
<footer class="shrink-0 px-4 py-4">
<x-ui.ai.prompt
placeholder="{{ __('Ask me anything') }}"
:attachments="false"
improve
>
<x-slot:actions>
<button
type="button"
class="inline-flex size-8 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring motion-reduce:transition-none"
title="{{ __('Add link') }}"
>
<span class="sr-only">{{ __('Add link') }}</span>
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" class="size-4">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</button>
<button
type="button"
class="inline-flex size-8 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring motion-reduce:transition-none"
title="{{ __('Add image') }}"
>
<span class="sr-only">{{ __('Add image') }}</span>
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" class="size-4">
<rect x="3" y="3" width="18" height="18" rx="2" />
<circle cx="9" cy="9" r="2" />
<path d="m21 15-5-5L5 21" />
</svg>
</button>
</x-slot:actions>
<x-slot:footer>
<span class="inline-flex min-w-0 items-center gap-2 truncate">
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" class="size-4 shrink-0"><rect x="3" y="3" width="7" height="7" rx="1.5" /><rect x="14" y="3" width="7" height="7" rx="1.5" /><rect x="3" y="14" width="7" height="7" rx="1.5" /><rect x="14" y="14" width="7" height="7" rx="1.5" /></svg>
{{ __('Connect apps') }}
</span>
<span class="flex shrink-0 items-center gap-2" aria-hidden="true">
@foreach ([
['label' => __('Calendar'), 'path' => '<rect x="3" y="4" width="18" height="17" rx="2"/><path d="M8 2v4M16 2v4M3 10h18"/>'],
['label' => __('Mail'), 'path' => '<rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7 9 6 9-6"/>'],
['label' => __('Files'), 'path' => '<path d="M4 4h6l2 3h8a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1Z"/>'],
] as $app)
<span
class="grid size-6 place-items-center rounded-sm border border-border bg-muted text-muted-foreground"
title="{{ $app['label'] }}"
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4">{!! $app['path'] !!}</svg>
</span>
@endforeach
</span>
</x-slot:footer>
</x-ui.ai.prompt>
</footer>
</section>
Installation
php artisan ui:add blocks/ai-tools-inbox
Registry contract
Install confidence
php artisan ui:add blocks/ai-tools-inbox
writes only the generated targets below. The CLI validates each file hash before writing and
prompts before replacing local changes unless --force is used.
- Version
- 1.0.1
- License
- open
- Stability
- stable
- Contract
- v1
- Foundation
- ≥ 1.0.0
| Type | Generated target |
|---|---|
| blade | resources/views/blocks/ai-tools-inbox.blade.php |
Registry dependencies
Package dependencies
composer: jml/brok:^0.2
npm: alpinejs
Use with AI
A brief for your coding agent: what the block is, the install command, how to render it, its props and the rules. Copy it, or open a prompt about this block in an assistant.
# Brok UI block: AI Tools Inbox (`ai-tools-inbox`)
A full-height assistant rail for the side of an app: a header, a scrollable conversation that starts with a quiet empty state and suggestion chips, and a composer pinned to the bottom with link/image actions and a "Connect apps" footer strip.
Brok UI is a Laravel Blade registry. `ui:add` copies this block into the app as plain Blade the app owns; it composes installed `<brok:*>` primitives and semantic design tokens.
## Install
```bash
php artisan ui:add blocks/ai-tools-inbox
```
## Render it
```blade
<x-blocks.ai-tools-inbox />
```
## Props
- `title` (string, default `AI Inbox`)
- `dismissible` (bool, default `false`) — Shows a close icon button in the header that dispatches ai-tools-inbox:close for the host page to act on.
- `messages` (array|null, default `null`) — Conversation turns: [{ role: 'user'|'other'|'system', text }]. Without any, the panel shows an empty state with suggestion chips.
## Use when
- Adding a persistent assistant column beside an app's main content — a dashboard, an inbox, a workspace — instead of a modal or a standalone chat page.
- Starting the panel empty with a short prompt and suggestion chips before any conversation exists.
## Avoid when
- Use ai-tools-chat for a self-contained chat panel that reads as its own card (a demo surface, a dedicated chat page) rather than a rail that fills a column.
- Use ai-tools-playground for a settings-plus-conversation layout.
## Rules
- Render the installed block with `<x-blocks.ai-tools-inbox />` and pass data through its props; edit the copied file only for structural changes.
- Keep the semantic design tokens (`bg-background`, `text-muted-foreground`); never swap in raw colour utilities.
- Keep the `data-slot` attributes and the logical (start/end) spacing so the markup still mirrors under `dir="rtl"`.
## Links
- Docs: https://brokui.dev/blocks/ai-tools-inbox
- Registry JSON (files, props, contract): https://brokui.dev/r/open/blocks/ai-tools-inbox.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
Guidance
Use when
- Adding a persistent assistant column beside an app's main content — a dashboard, an inbox, a workspace — instead of a modal or a standalone chat page.
- Starting the panel empty with a short prompt and suggestion chips before any conversation exists.
Avoid when
- Use ai-tools-chat for a self-contained chat panel that reads as its own card (a demo surface, a dedicated chat page) rather than a rail that fills a column.
- Use ai-tools-playground for a settings-plus-conversation layout.
Anti-patterns
- Do not replace semantic props with conflicting utility classes.
- Do not remove labels, focus styles, or state attributes.
- Anatomy
Usage
Render the block as a component, passing data where useful:
<x-blocks.ai-tools-inbox />
Built from primitives
This block composes installed <brok:*> primitives and semantic design
tokens only — it does not reimplement any primitive. Re-theme it (light, dark, admin, customer) by
editing CSS variables; flip the preview to RTL to confirm it mirrors.
Source
The exact, editable file ui:add writes
into your app. The preview above renders this same source — there are no preview-only blocks.
@props([
'title' => 'AI Inbox',
// Shows a close/collapse icon button in the header that dispatches
// `ai-tools-inbox:close` for the host page to act on (e.g. hide the rail).
'dismissible' => false,
// Conversation turns: [{ role: 'user'|'other'|'system', text: '…' }, …].
// Without any, the panel shows a quiet empty state with suggestion chips.
'messages' => null,
])
@php
$hasMessages = is_array($messages) && ! empty($messages);
$threadMessages = $hasMessages
? collect($messages)->map(fn ($m) => [
'role' => $m['role'] ?? 'user',
'name' => $m['name'] ?? null,
'avatar' => $m['avatar'] ?? null,
'content' => $m['text'] ?? ($m['content'] ?? ''),
])->all()
: [];
@endphp
{{--
AI Tools Inbox — a full-height assistant rail meant to sit beside an app
(a right column), not a self-contained card like ai-tools-chat. A fixed
header and composer bracket a scrollable conversation: an empty state
with suggestion chips by default, or the supplied `messages` rendered
through chat-thread. Picking a suggestion fills the composer and moves
focus into it (suggestion-chips-select bubbles up to the root and writes
straight into the ai-prompt textarea so its own Alpine state stays the
single source of truth).
--}}
<section
data-slot="block-ai-tools-inbox"
x-data="{
fillComposer(text) {
const textarea = this.$el.querySelector('[data-slot=\'ai-prompt\'] textarea');
if (! textarea) return;
textarea.value = text;
textarea.dispatchEvent(new Event('input', { bubbles: true }));
textarea.focus();
},
}"
@suggestion-chips-select="fillComposer($event.detail.value)"
{{ $attributes->merge(['class' => 'flex h-full min-h-[640px] w-full flex-col bg-background text-foreground']) }}
>
{{-- Header --}}
<header class="flex shrink-0 items-center justify-between gap-2 px-4 py-4">
<h2 class="truncate text-lg font-semibold text-foreground">{{ __($title) }}</h2>
@if ($dismissible)
<x-ui.button
variant="ghost"
size="sm"
:icon-label="__('Close')"
@click="$dispatch('ai-tools-inbox:close')"
class="text-muted-foreground"
>
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4"><path d="M18 6 6 18M6 6l12 12" /></svg>
</x-ui.button>
@endif
</header>
{{-- Conversation --}}
<div class="min-h-0 flex-1 overflow-y-auto px-4 py-6" role="region" aria-label="{{ __('Conversation') }}">
@if ($hasMessages)
<x-ui.chat-thread :messages="$threadMessages" />
@else
<div class="flex h-full flex-col items-center justify-center gap-4 text-center">
<span class="ui-ai-spark grid size-10 shrink-0 place-items-center rounded-full" aria-hidden="true">
<svg class="size-5" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l1.9 5.1L19 9l-5.1 1.9L12 16l-1.9-5.1L5 9l5.1-1.9L12 2z" /></svg>
</span>
<p class="max-w-[26ch] text-sm text-muted-foreground">{{ __('Ask about your spending, goals or transactions') }}</p>
<x-ui.suggestion-chips
class="justify-center"
:items="[
__('Where did I overspend this month?'),
__('Am I on track for my goal?'),
__('Explain this transaction'),
]"
/>
</div>
@endif
</div>
{{-- Composer --}}
<footer class="shrink-0 px-4 py-4">
<x-ui.ai.prompt
placeholder="{{ __('Ask me anything') }}"
:attachments="false"
improve
>
<x-slot:actions>
<button
type="button"
class="inline-flex size-8 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring motion-reduce:transition-none"
title="{{ __('Add link') }}"
>
<span class="sr-only">{{ __('Add link') }}</span>
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" class="size-4">
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>
</button>
<button
type="button"
class="inline-flex size-8 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-[length:var(--ring-width)] focus-visible:ring-ring motion-reduce:transition-none"
title="{{ __('Add image') }}"
>
<span class="sr-only">{{ __('Add image') }}</span>
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" class="size-4">
<rect x="3" y="3" width="18" height="18" rx="2" />
<circle cx="9" cy="9" r="2" />
<path d="m21 15-5-5L5 21" />
</svg>
</button>
</x-slot:actions>
<x-slot:footer>
<span class="inline-flex min-w-0 items-center gap-2 truncate">
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" class="size-4 shrink-0"><rect x="3" y="3" width="7" height="7" rx="1.5" /><rect x="14" y="3" width="7" height="7" rx="1.5" /><rect x="3" y="14" width="7" height="7" rx="1.5" /><rect x="14" y="14" width="7" height="7" rx="1.5" /></svg>
{{ __('Connect apps') }}
</span>
<span class="flex shrink-0 items-center gap-2" aria-hidden="true">
@foreach ([
['label' => __('Calendar'), 'path' => '<rect x="3" y="4" width="18" height="17" rx="2"/><path d="M8 2v4M16 2v4M3 10h18"/>'],
['label' => __('Mail'), 'path' => '<rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7 9 6 9-6"/>'],
['label' => __('Files'), 'path' => '<path d="M4 4h6l2 3h8a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1Z"/>'],
] as $app)
<span
class="grid size-6 place-items-center rounded-sm border border-border bg-muted text-muted-foreground"
title="{{ $app['label'] }}"
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="size-4">{!! $app['path'] !!}</svg>
</span>
@endforeach
</span>
</x-slot:footer>
</x-ui.ai.prompt>
</footer>
</section>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.0.1 - Status
- Stable
- License
-
open - Deprecation
- Not deprecated
- Contract
-
v1 - Foundation
-
≥ 1.0.0