statikbe / laravel-filament-solaris
AI actions for Filament v4 & v5 — auto-detect form fields, compose prompts, and write structured AI responses back. Powered by laravel-ai.
Package info
github.com/statikbe/laravel-filament-solaris
pkg:composer/statikbe/laravel-filament-solaris
Fund package maintenance!
Requires
- php: ^8.3
- filament/filament: ^4.2||^5.0
- illuminate/contracts: ^12.0||^13.0
- laravel/ai: ^0.7
- spatie/laravel-package-tools: ^1.93
Requires (Dev)
- filament/spatie-laravel-media-library-plugin: ^4.2||^5.0
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^11.1.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- spatie/laravel-medialibrary: ^11.0||^12.0
Suggests
- ext-intl: Required for human-readable locale display names in TranslationPreset. Without it, locale codes (e.g. 'nl') are shown instead of names (e.g. 'Dutch') unless a translation entry is published.
This package is auto-updated.
Last update: 2026-05-21 22:09:56 UTC
README
Laravel Filament Solaris
AI actions for Filament v4 & v5 — drop a button on any form to summarize, classify, translate, generate, transcribe, or create images, with the result written straight back into your fields. Powered by laravel/ai.
“There are no answers, only choices.” ― Stanisław Lem, Solaris
What you get
AiAction— read source fields, send them to an AI provider, write a structured response back into one or more target fields. Filament component types are auto-detected (Select, Toggle, RichEditor, …) and the AI is constrained to a matching JSON schema, so aSelectonly ever gets a valid option.ImageGenerationAction— generate (or edit/reskin) images and store them straight into aFileUpload/ Spatie media field.DictationFieldAction— record audio, transcribe it, and drop the text into a field — optionally piped through the AI pipeline first.- Presets for the common jobs (summary, classification, translation, generation) and a prompt API (inline string, Blade view, or custom builder) for everything else.
- Extra input modal and attachments to add extra user input to the prompt.
- Preview & conversational refinement — let users review and chat-refine the result before it touches the form.
- Production-minded — per-action/preset/panel provider config, usage-tracking events, rate-limit handling, a security model for AI output, and full test fakes.
Table of Contents
- How It Works
- Requirements
- Installation
- Quick Start
- Recipes
- Core Concepts
- Security
- Documentation
- Versioning
- License
How It Works
AiAction is a Filament Action that reads values from source fields, sends them to an AI provider via laravel/ai, and writes structured responses back into target fields. The package auto-detects target component types (Select, TextInput, Toggle, etc.) and handles bidirectional data transformation — converting form state to prompt context and AI responses back to valid form state.
flowchart LR
A([Source Fields]) -->|read form values| B[AiAction]
G([UserInput]) -->|extra modal form| B
P([Prompt]) -->|extra prompt| B
N([Config]) -->|provider, model, options| B
T([Attachments]) -->|extra files| B
B -->|compose prompt| C[PromptBuilder]
C -->|structured request| D[SolarisAgent]
D -->|prompt + JSON schema| AI([AI Provider])
AI -->|JSON response| E[ComponentFactory]
E -->|transform & write| F([Target Fields])
E -.->|withPreview| R[Preview]
R -->|accept| F
subgraph Conversational refinement
R -->|chat| O[Conversation]
CT([Attachments]) -->|extra files| O
CP([Prompt]) -->|extra prompt| O
O -->|refine| AI
end
Loading
For the full execution-pipeline and class-hierarchy diagrams, see Architecture.
Requirements
- PHP
^8.3 - Filament
^4.2 || ^5.0 laravel/ai^0.6(configure at least one provider there first)- Laravel
^12.0 || ^13.0
Installation
composer require statikbe/laravel-filament-solaris
Publish the config file:
php artisan vendor:publish --tag="filament-solaris-config"
Optionally publish the views (prompt templates) and translations:
php artisan vendor:publish --tag="filament-solaris-views" php artisan vendor:publish --tag="filament-solaris-translations"
Tailwind CSS (required)
Solaris ships Blade views (preview modal, conversational refinement modal, dictation modal, loading state, etc.) that use Tailwind utility classes. Without telling Tailwind to scan the package's views, those classes are purged and the modals render unstyled.
Add a @source directive to your Filament theme CSS:
/* resources/css/filament/admin/theme.css */ @source "../../vendor/statikbe/laravel-filament-solaris/resources/views";
Then rebuild your Filament theme (npm run build or php artisan filament:assets). This is required for every consumer — not optional and not specific to any single action.
Quick Start
The smallest useful AiAction reads one field, asks the AI to rewrite it, and writes the result straight back into the same field — a one-click "improve writing" button, no preset, no second field:
use Statikbe\FilamentSolaris\Actions\AiAction; Forms\Components\Actions::make([ AiAction::make('improve-writing') ->sourceFields(['description']) ->targetField('description') ->prompt('Rewrite this text: fix grammar and spelling, tighten the phrasing, keep the meaning and tone.'), ]),
That's the whole loop: choose what to read, what to write, and how to instruct the AI. Here it's a one-line ->prompt(); for the common jobs, presets like SummaryPreset, ClassificationPreset, and TranslationPreset collapse the instruction into a tuned one-liner — and the AI is constrained to a schema that matches the target component, so a Select only ever receives a valid option. The next section has a recipe for each.
Recipes
Self-contained snippets for the things you'll actually want to do. Each links to the full reference under Documentation.
Summarize or rewrite text
use Statikbe\FilamentSolaris\Prompts\Presets\SummaryPreset; AiAction::make('summarize') ->sourceFields(['title', 'body']) ->targetField('summary') ->preset(SummaryPreset::make()->maxWords(100)->tone('professional'));
Classify into a Select
The factory constrains the AI to the Select's actual options, so it can only return a valid key — even for relationship-backed selects.
use Statikbe\FilamentSolaris\Prompts\Presets\ClassificationPreset; AiAction::make('classify') ->sourceFields(['title', 'body']) ->targetField('category_id') ->targetScope('category_id', fn ($query) => $query->where('active', true)) ->preset(ClassificationPreset::make()->context('tech blog'));
For Selects with hundreds of options the schema becomes free-text and the answer is matched back to a key — tunable, with a misclassification event. See Option Matching.
Translate a field
use Statikbe\FilamentSolaris\Prompts\Presets\TranslationPreset; AiAction::make('translate') ->sourceFields(['body']) ->targetField('body_nl') ->preset(TranslationPreset::make()->language('nl')->preserveFormatting());
Fill several fields at once
One call writes a summary, picks a category, and extracts a set of tags — three different field types from a single response:
AiAction::make('auto-fill') ->sourceFields(['title', 'body']) ->targetFields(['summary', 'category_id', 'tags']) ->prompt('Analyze the article. Summarize it, pick the best category, and suggest a few relevant tags.');
Use a plain prompt (no preset)
For one-off jobs that don't warrant a preset, write the instruction inline — here, turning an article into a ready-to-post social blurb:
AiAction::make('social-post') ->sourceFields(['title', 'body']) ->targetField('social_post') ->prompt('Write a short, upbeat social media post promoting this article. Max 280 characters, add 2-3 relevant hashtags.');
Ask the user for guidance first
Open a modal before the AI runs and feed the answer into the prompt:
use Statikbe\FilamentSolaris\Support\UserInput; AiAction::make('generate') ->sourceFields(['title']) ->targetField('body') ->preset(GenerationPreset::make()) ->userInput( UserInput::make() ->prompt('What should the AI write about?') ->placeholder('Describe the content you want...') );
Presets that ship their own default modal (e.g. TranslationPreset's language picker) just need ->withDefaultUserInput(). See User Input.
Generate an image
use Statikbe\FilamentSolaris\Actions\ImageGenerationAction; use Statikbe\FilamentSolaris\Enums\ImageSize; ImageGenerationAction::make('generate-cover') ->prompt('Generate a cover image for this article') ->sourceFields(['title', 'body']) ->targetField('cover_image') ->imageSize(ImageSize::Landscape);
Reference images (image-to-image / edits) and storage options are covered in ImageGenerationAction.
Dictate into a field
Attach to any field via ->hintAction(...) (or ->suffixAction(...) on a TextInput). The transcript is written into the host field — no ->targetField() needed.
use Filament\Forms\Components\Textarea; use Statikbe\FilamentSolaris\Actions\DictationFieldAction; Textarea::make('notes') ->hintAction( DictationFieldAction::make()->lang('en')->append() );
Add a ->preset() / ->prompt() to pipe the transcript through the AI first (e.g. dictate rough notes → store a clean summary). See DictationFieldAction.
Preview before applying
Let the user review the result in a modal and accept or cancel:
AiAction::make('summarize') ->sourceFields(['title', 'body']) ->targetField('summary') ->prompt('Summarise the body.') ->withPreview();
Requires the InteractsWithSolarisPreview trait on the Livewire component hosting the form (it fails loud if missing). Full setup in Preview.
Refine conversationally
Turn the preview into a chat so the user can iterate ("shorter", "more formal") before accepting:
AiAction::make('summarize') ->sourceFields(['title', 'body']) ->targetField('summary') ->preset(SummaryPreset::make()) ->conversational(); // implies ->withPreview()
Requires laravel/ai's conversation migrations and an authenticated user — see Conversational Refinement.
Track usage & cost
Every AI call dispatches an event. Listen for it to meter tokens, enforce budgets, or build an audit trail — Solaris persists nothing itself:
use Statikbe\FilamentSolaris\Events\SolarisResponseReceived; class TrackAiUsage { public function handle(SolarisResponseReceived $event): void { AiCall::create([ 'action_name' => $event->actionName, 'user_id' => $event->user?->getAuthIdentifier(), 'model' => $event->model, 'prompt_tokens' => $event->usage->promptTokens, 'completion_tokens' => $event->usage->completionTokens, 'duration_ms' => $event->durationMs, ]); } }
Full payload, the failure event, and rate-limit handling: Usage Tracking.
Putting it together
A complete resource form combining several actions:
use Filament\Forms; use Statikbe\FilamentSolaris\Actions\AiAction; use Statikbe\FilamentSolaris\Actions\DictationFieldAction; use Statikbe\FilamentSolaris\Actions\ImageGenerationAction; use Statikbe\FilamentSolaris\Enums\ImageSize; use Statikbe\FilamentSolaris\Prompts\Presets\ClassificationPreset; use Statikbe\FilamentSolaris\Prompts\Presets\GenerationPreset; use Statikbe\FilamentSolaris\Prompts\Presets\SummaryPreset; use Statikbe\FilamentSolaris\Prompts\Presets\TranslationPreset; public function form(Form $form): Form { return $form->schema([ Forms\Components\TextInput::make('title')->required(), Forms\Components\RichEditor::make('body')->required(), Forms\Components\Textarea::make('summary') ->hintAction( // Voice-to-summary: transcribe audio, run through AI DictationFieldAction::make('voice-summary') ->preset(SummaryPreset::make()->maxWords(100)) ->lang('en'), ), Forms\Components\Select::make('category_id') ->relationship('category', 'name'), Forms\Components\TagsInput::make('tags'), Forms\Components\Actions::make([ AiAction::make('summarize') ->sourceFields(['title', 'body']) ->targetField('summary') ->preset(SummaryPreset::make()->maxWords(100)->tone('professional')), AiAction::make('classify') ->sourceFields(['title', 'body']) ->targetField('category_id') ->preset(ClassificationPreset::make()) ->provider('openai', 'gpt-4o-mini'), // cheaper model for a simple job AiAction::make('auto-fill') ->sourceFields(['title', 'body']) ->targetFields(['summary', 'category_id', 'tags']) ->prompt('Summarize, pick the best category, and suggest a few relevant tags.'), AiAction::make('translate') ->sourceFields(['body']) ->targetField('body_nl') ->preset(TranslationPreset::make()->language('nl')->preserveFormatting()), ImageGenerationAction::make('generate-cover') ->prompt('Generate a cover image for this article') ->sourceFields(['title', 'body']) ->targetField('cover_image') ->imageSize(ImageSize::Landscape), ]), Forms\Components\SpatieMediaLibraryFileUpload::make('cover_image') ->collection('cover')->disk('public')->image(), ]); }
Core Concepts
- Component factories translate between Filament components and AI — each one builds the JSON-schema fragment that constrains the AI's output and transforms the response back into valid form state. They're auto-resolved per target field, and you can register your own. Most form fields are supported (structural data fields like repeater and builder are future work). → Component Factories
- Presets are reusable prompt builders for common jobs (
SummaryPreset,ClassificationPreset,TranslationPreset,GenerationPreset). → Presets Reference - Prompt builders decide how a prompt is composed — inline string, Blade view, or a custom
PromptBuilder. → Prompt Builders - Configuration spans package config, per-preset overrides, and per-panel plugin setters. → Configuration
Security
Solaris connects two untrusted boundaries: user-typed values flow into the prompt, and AI-generated output flows back into form fields. Treat AI output as user-generated content — it's safe on the form itself (Blade escapes, Selects are enum-bounded), but any place you render it as raw HTML ({!! !!}, mail, PDF, CSV, webhooks) must be sanitized at render time. A per-action ->sanitize() / ->sanitizeField() hook is provided, and actions can be gated per-user or per-panel.
Read the full threat model and field-by-field guidance in Security Considerations before shipping a public-facing form.
Documentation
| Topic | What's inside |
|---|---|
| AiAction API | Source/target fields, prompts, user input, locale, provider, timeout, tools, generation options, attachments, preview, conversational refinement |
| Component Factories | Supported components, custom factories, option matching & fuzzy tuning |
| Presets Reference | Summary / Classification / Translation / Generation + custom presets |
| Prompt Builders | Inline, view, and custom prompt builders |
| ImageGenerationAction | Sizes, quality, providers, storage, reference images |
| DictationFieldAction | Transcription, AI chaining, providers, RichEditor support |
| Usage Tracking | Events, metering, rate-limit handling & retry |
| Security Considerations | Threat model, render-layer guidance, sanitization hook |
| Configuration | All config keys + per-panel plugin |
| Architecture | Execution pipeline & class-hierarchy diagrams |
| Testing | Fakes & assertions for all three actions |
Versioning
Solaris ships as 0.x while laravel/ai is pre-1.0. The upstream SDK is itself 0.x and doesn't promise SemVer guarantees, so every laravel/ai minor bump (0.6 → 0.7) is expected to require a Solaris release. Solaris will tag 1.0.0 once laravel/ai reaches 1.0 and its public surface settles.
While on 0.x:
- Minor (
0.1→0.2) — breaking changes are possible. - Patch (
0.1.0→0.1.1) — bugfixes, backwards-compatible additions.
Pin a minor in your composer.json if you want to avoid surprises:
"statikbe/laravel-filament-solaris": "~0.1.0"
Changelog
Please see CHANGELOG for more information on what has changed recently.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.
