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([
'model' => 'GPT-4o',
])
<x-ui.section space="xl" {{ $attributes }}>
<div class="mx-auto max-w-5xl px-4">
<x-ui.card
x-data="{
prompt: '',
tone: 'professional',
length: 'medium',
output: '',
loading: false,
timer: null,
generate() {
if (!this.prompt || this.loading) return;
this.loading = true;
this.output = '';
const toneMap = {
professional: 'clear and authoritative',
friendly: 'warm and conversational',
creative: 'imaginative and expressive',
formal: 'precise and structured',
};
const lengthMap = {
short: 'one concise sentence',
medium: 'two to three sentences',
long: 'a full paragraph',
};
const p = this.prompt;
const t = toneMap[this.tone] || 'clear';
const l = lengthMap[this.length] || 'two to three sentences';
this.timer = setTimeout(() => {
this.output = 'Here is ' + l + ' about “' + p + '” written in a ' + t + ' style. The content has been crafted to engage your audience effectively, balancing clarity with impact. Feel free to refine it further to match your exact needs.';
this.loading = false;
}, 900);
},
copy() {
if (this.output) navigator.clipboard.writeText(this.output);
},
reset() {
clearTimeout(this.timer);
this.prompt = '';
this.output = '';
this.loading = false;
},
destroy() {
clearTimeout(this.timer);
},
}"
>
<x-ui.card.header class="flex flex-row items-center justify-between gap-4 border-b border-border pb-4">
<div class="flex items-center gap-2">
<svg class="size-5 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M12 20h9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="text-base font-semibold text-foreground">{{ __('AI Writer') }}</span>
</div>
<x-ui.badge variant="secondary">{{ $model }}</x-ui.badge>
</x-ui.card.header>
<x-ui.card.content class="pt-6">
<div class="grid gap-8 lg:grid-cols-2">
{{-- LEFT: controls --}}
<div class="flex flex-col gap-6">
<div class="flex flex-col gap-2">
<label for="tg-prompt" class="text-sm font-medium text-foreground">{{ __('Your prompt') }}</label>
<x-ui.textarea
id="tg-prompt"
name="prompt"
rows="5"
placeholder="{{ __('Describe what you want to write…') }}"
x-model="prompt"
required
/>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="flex flex-col gap-2">
<label for="tg-tone" class="text-sm font-medium text-foreground">{{ __('Tone') }}</label>
<x-ui.select id="tg-tone" name="tone" x-model="tone">
<option value="professional">{{ __('Professional') }}</option>
<option value="friendly">{{ __('Friendly') }}</option>
<option value="creative">{{ __('Creative') }}</option>
<option value="formal">{{ __('Formal') }}</option>
</x-ui.select>
</div>
<div class="flex flex-col gap-2">
<label for="tg-length" class="text-sm font-medium text-foreground">{{ __('Length') }}</label>
<x-ui.select id="tg-length" name="length" x-model="length">
<option value="short">{{ __('Short') }}</option>
<option value="medium">{{ __('Medium') }}</option>
<option value="long">{{ __('Long') }}</option>
</x-ui.select>
</div>
</div>
<x-ui.button
x-on:click="generate()"
x-bind:disabled="!prompt || loading"
x-bind:aria-busy="loading"
class="w-full"
>
<span x-show="loading" class="me-2 inline-flex" aria-hidden="true">
<svg class="size-4 animate-spin motion-reduce:animate-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 12a9 9 0 1 1-6.219-8.56" stroke-linecap="round"/>
</svg>
</span>
<span x-show="loading">{{ __('Generating…') }}</span>
<span x-show="!loading">{{ __('Generate') }}</span>
</x-ui.button>
</div>
{{-- RIGHT: output --}}
<div class="flex flex-col gap-4">
<div
class="relative min-h-[12rem] flex-1 rounded-lg border border-border bg-muted/40 p-6"
aria-live="polite"
aria-atomic="true"
aria-label="{{ __('Generated text output') }}"
x-bind:aria-busy="loading"
>
{{-- Shimmer overlay while loading --}}
<div
x-show="loading"
class="absolute inset-0 flex flex-col gap-4 rounded-lg p-6"
aria-hidden="true"
>
<x-ui.skeleton class="h-3 w-full" />
<x-ui.skeleton class="h-3 w-5/6" />
<x-ui.skeleton class="h-3 w-4/6" />
<x-ui.skeleton class="h-3 w-full" />
<x-ui.skeleton class="h-3 w-3/6" />
</div>
{{-- Empty state --}}
<div
x-show="!output && !loading"
class="flex h-full min-h-[10rem] flex-col items-center justify-center gap-2 text-center"
>
<svg class="size-8 text-muted-foreground/40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5.586a1 1 0 0 1 .707.293l5.414 5.414a1 1 0 0 1 .293.707V19a2 2 0 0 1-2 2z" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<p class="text-sm text-muted-foreground">{{ __('Your generated text will appear here.') }}</p>
</div>
{{-- Output text --}}
<p
x-show="output && !loading"
x-text="output"
class="text-sm leading-relaxed text-foreground"
></p>
</div>
{{-- Output actions --}}
<div class="flex items-center gap-2" x-show="output && !loading">
<x-ui.button variant="outline" size="sm" x-on:click="copy()" class="gap-2">
<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
{{ __('Copy') }}
</x-ui.button>
<x-ui.button variant="ghost" size="sm" x-on:click="generate()" class="gap-2">
<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M1 4v6h6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.51 15a9 9 0 1 0 .49-3.95" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
{{ __('Regenerate') }}
</x-ui.button>
<x-ui.button variant="ghost" size="sm" x-on:click="reset()" class="ms-auto gap-2 text-muted-foreground">
<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M18 6 6 18M6 6l12 12" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
{{ __('Clear') }}
</x-ui.button>
</div>
</div>
</div>
</x-ui.card.content>
</x-ui.card>
</div>
</x-ui.section>
Installation
php artisan ui:add blocks/ai-tools-text
Registry contract
Install confidence
php artisan ui:add blocks/ai-tools-text
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.1.5
- License
- open
- Stability
- stable
- Contract
- v1
- Foundation
- ≥ 1.0.0
| Type | Generated target |
|---|---|
| blade | resources/views/blocks/ai-tools-text.blade.php |
Registry dependencies
Package dependencies
composer: jml/brok:^0.2
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: Text Generator (`ai-tools-text`)
Interactive AI writing tool with tone and length controls, a live output panel, and copy/regenerate actions.
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-text
```
## Render it
```blade
<x-blocks.ai-tools-text />
```
## Props
- `model` (string, default `GPT-4o`)
## Use when
- Demoing an AI writing tool where a visitor types a prompt, picks a tone and length, and reads generated copy in a live output panel.
- Showing copy and regenerate actions on AI-produced text inside a single card.
## Avoid when
- Use ai-tools-image for an image-generation demo or ai-tools-code for a code-generation demo; ai-tools-text is text-only.
## Rules
- Render the installed block with `<x-blocks.ai-tools-text />` 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-text
- Registry JSON (files, props, contract): https://brokui.dev/r/open/blocks/ai-tools-text.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
- Demoing an AI writing tool where a visitor types a prompt, picks a tone and length, and reads generated copy in a live output panel.
- Showing copy and regenerate actions on AI-produced text inside a single card.
Avoid when
- Use ai-tools-image for an image-generation demo or ai-tools-code for a code-generation demo; ai-tools-text is text-only.
Anti-patterns
- Do not replace semantic props with conflicting utility classes.
- Do not remove labels, focus styles, or state attributes.
Usage
Render the block as a component, passing data where useful:
<x-blocks.ai-tools-text />
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([
'model' => 'GPT-4o',
])
<x-ui.section space="xl" {{ $attributes }}>
<div class="mx-auto max-w-5xl px-4">
<x-ui.card
x-data="{
prompt: '',
tone: 'professional',
length: 'medium',
output: '',
loading: false,
timer: null,
generate() {
if (!this.prompt || this.loading) return;
this.loading = true;
this.output = '';
const toneMap = {
professional: 'clear and authoritative',
friendly: 'warm and conversational',
creative: 'imaginative and expressive',
formal: 'precise and structured',
};
const lengthMap = {
short: 'one concise sentence',
medium: 'two to three sentences',
long: 'a full paragraph',
};
const p = this.prompt;
const t = toneMap[this.tone] || 'clear';
const l = lengthMap[this.length] || 'two to three sentences';
this.timer = setTimeout(() => {
this.output = 'Here is ' + l + ' about “' + p + '” written in a ' + t + ' style. The content has been crafted to engage your audience effectively, balancing clarity with impact. Feel free to refine it further to match your exact needs.';
this.loading = false;
}, 900);
},
copy() {
if (this.output) navigator.clipboard.writeText(this.output);
},
reset() {
clearTimeout(this.timer);
this.prompt = '';
this.output = '';
this.loading = false;
},
destroy() {
clearTimeout(this.timer);
},
}"
>
<x-ui.card.header class="flex flex-row items-center justify-between gap-4 border-b border-border pb-4">
<div class="flex items-center gap-2">
<svg class="size-5 text-primary" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M12 20h9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="text-base font-semibold text-foreground">{{ __('AI Writer') }}</span>
</div>
<x-ui.badge variant="secondary">{{ $model }}</x-ui.badge>
</x-ui.card.header>
<x-ui.card.content class="pt-6">
<div class="grid gap-8 lg:grid-cols-2">
{{-- LEFT: controls --}}
<div class="flex flex-col gap-6">
<div class="flex flex-col gap-2">
<label for="tg-prompt" class="text-sm font-medium text-foreground">{{ __('Your prompt') }}</label>
<x-ui.textarea
id="tg-prompt"
name="prompt"
rows="5"
placeholder="{{ __('Describe what you want to write…') }}"
x-model="prompt"
required
/>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="flex flex-col gap-2">
<label for="tg-tone" class="text-sm font-medium text-foreground">{{ __('Tone') }}</label>
<x-ui.select id="tg-tone" name="tone" x-model="tone">
<option value="professional">{{ __('Professional') }}</option>
<option value="friendly">{{ __('Friendly') }}</option>
<option value="creative">{{ __('Creative') }}</option>
<option value="formal">{{ __('Formal') }}</option>
</x-ui.select>
</div>
<div class="flex flex-col gap-2">
<label for="tg-length" class="text-sm font-medium text-foreground">{{ __('Length') }}</label>
<x-ui.select id="tg-length" name="length" x-model="length">
<option value="short">{{ __('Short') }}</option>
<option value="medium">{{ __('Medium') }}</option>
<option value="long">{{ __('Long') }}</option>
</x-ui.select>
</div>
</div>
<x-ui.button
x-on:click="generate()"
x-bind:disabled="!prompt || loading"
x-bind:aria-busy="loading"
class="w-full"
>
<span x-show="loading" class="me-2 inline-flex" aria-hidden="true">
<svg class="size-4 animate-spin motion-reduce:animate-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 12a9 9 0 1 1-6.219-8.56" stroke-linecap="round"/>
</svg>
</span>
<span x-show="loading">{{ __('Generating…') }}</span>
<span x-show="!loading">{{ __('Generate') }}</span>
</x-ui.button>
</div>
{{-- RIGHT: output --}}
<div class="flex flex-col gap-4">
<div
class="relative min-h-[12rem] flex-1 rounded-lg border border-border bg-muted/40 p-6"
aria-live="polite"
aria-atomic="true"
aria-label="{{ __('Generated text output') }}"
x-bind:aria-busy="loading"
>
{{-- Shimmer overlay while loading --}}
<div
x-show="loading"
class="absolute inset-0 flex flex-col gap-4 rounded-lg p-6"
aria-hidden="true"
>
<x-ui.skeleton class="h-3 w-full" />
<x-ui.skeleton class="h-3 w-5/6" />
<x-ui.skeleton class="h-3 w-4/6" />
<x-ui.skeleton class="h-3 w-full" />
<x-ui.skeleton class="h-3 w-3/6" />
</div>
{{-- Empty state --}}
<div
x-show="!output && !loading"
class="flex h-full min-h-[10rem] flex-col items-center justify-center gap-2 text-center"
>
<svg class="size-8 text-muted-foreground/40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5.586a1 1 0 0 1 .707.293l5.414 5.414a1 1 0 0 1 .293.707V19a2 2 0 0 1-2 2z" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<p class="text-sm text-muted-foreground">{{ __('Your generated text will appear here.') }}</p>
</div>
{{-- Output text --}}
<p
x-show="output && !loading"
x-text="output"
class="text-sm leading-relaxed text-foreground"
></p>
</div>
{{-- Output actions --}}
<div class="flex items-center gap-2" x-show="output && !loading">
<x-ui.button variant="outline" size="sm" x-on:click="copy()" class="gap-2">
<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
{{ __('Copy') }}
</x-ui.button>
<x-ui.button variant="ghost" size="sm" x-on:click="generate()" class="gap-2">
<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M1 4v6h6" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3.51 15a9 9 0 1 0 .49-3.95" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
{{ __('Regenerate') }}
</x-ui.button>
<x-ui.button variant="ghost" size="sm" x-on:click="reset()" class="ms-auto gap-2 text-muted-foreground">
<svg class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
<path d="M18 6 6 18M6 6l12 12" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
{{ __('Clear') }}
</x-ui.button>
</div>
</div>
</div>
</x-ui.card.content>
</x-ui.card>
</div>
</x-ui.section>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.1.5 - Status
- Stable
- License
-
open - Deprecation
- Not deprecated
- Contract
-
v1 - Foundation
-
≥ 1.0.0