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.

Maintainers

Package info

github.com/statikbe/laravel-filament-solaris

pkg:composer/statikbe/laravel-filament-solaris

Fund package maintenance!

statikbe

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

0.1.0 2026-05-21 22:07 UTC

This package is auto-updated.

Last update: 2026-05-21 22:09:56 UTC


README

Laravel Filament Solaris

Laravel Filament Solaris

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

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 a Select only ever gets a valid option.
  • ImageGenerationAction — generate (or edit/reskin) images and store them straight into a FileUpload / 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

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.60.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.10.2) — breaking changes are possible.
  • Patch (0.1.00.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.