laravel maintained by apartejs
aparté for Laravel
An AI chat for Blade, Livewire and Filament pages, wired to the Laravel AI SDK. No React and no build step: replies stream with Markdown, tool calls show as they run, and a person can approve or refuse them.

It is built on aparté, a chat UI made of web components, and needs PHP 8.3+, Laravel 12 or 13
and laravel/ai 1.3+.
Install
composer require apartejs/laravel
php artisan vendor:publish --provider="Laravel\Ai\AiServiceProvider"
php artisan migrate
The last two are the SDK's own setup: its configuration and the tables that keep conversations.
Use it
An agent that keeps its conversations:
namespace App\Ai\Agents;
use Laravel\Ai\Concerns\RemembersConversations;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\Conversational;
use Laravel\Ai\Promptable;
class Assistant implements Agent, Conversational
{
use Promptable, RemembersConversations;
public function instructions(): string
{
return 'You are a helpful assistant.';
}
}
A route, and the chat in a view. Give it a height:
// routes/web.php
Route::aparte('/chat', App\Ai\Agents\Assistant::class);
<x-aparte::chat endpoint="/chat" style="height: 32rem" />
Inside a Livewire component it works as is: it is excluded from Livewire's re-renders.
Protect the route. It spends your model budget and runs your tools, so give it the middleware your app needs:
Route::aparte('/chat', App\Ai\Agents\Assistant::class)->middleware(['auth', 'throttle:20,1']);
A button in the corner of the page
<x-aparte::launcher endpoint="/chat" label="Help" />
Filament
use Aparte\Laravel\Filament\ApartePlugin;
$panel->plugin(ApartePlugin::make()->agent(App\Ai\Agents\Assistant::class)->label('Assistant'));
Every page of the panel gets the button, for whoever is signed in to it. The route is added for you, behind the panel's authentication, and the chat follows the panel's light or dark mode.
Approving tool calls
A tool that needs a person uses the SDK's approvals:
public function tools(): iterable
{
return [(new SendInvoice)->requireApproval('Sending an invoice.')];
}
The chat asks before the tool runs. The model answers a refusal, and reads what the person typed in its place. A pending question comes back after a reload.
Slots
<x-aparte::chat endpoint="/chat" style="height: 32rem">
<x-slot:empty-state><p>How can I help?</p></x-slot:empty-state>
<x-slot:above-composer><p>Answers may be wrong.</p></x-slot:above-composer>
<x-slot:toolbar><button type="button" style="margin-inline-start: auto">Help</button></x-slot:toolbar>
</x-aparte::chat>
Good to know
- The look is set with aparté's CSS variables. It follows the system's light or dark
mode;
follow-dark-classmakes it follow Tailwind'sdarkclass instead. - What is kept: the SDK keeps the conversation in your database, tied to the signed-in user, and the model's memory comes from there. What is on screen is kept for the tab: closing it starts a new conversation.
- A conversation is identified by an id the browser creates, or by
thread="..."on the component. - Guests can chat if your route allows them. Their conversations belong to nobody, so the id is all that protects one.
- In production, replies stream through nginx and PHP-FPM with no configuration: the SDK tells nginx not to buffer them.
Each reply in progress holds a PHP worker until it ends, so count them in
pm.max_children. - The SDK's AG-UI stream, which this package reads, is not in its documentation yet, so a new
laravel/aiversion could change it.
Develop
composer install
vendor/bin/pest
npm install && npm run build # rebuilds dist/, which is committed
MIT license.