From 232bdc5f1f4d3b844e562fa3d6505d45aa6cb788 Mon Sep 17 00:00:00 2001 From: Manoj Hortulanus Date: Fri, 9 May 2025 14:02:44 +0200 Subject: [PATCH 01/14] wip --- src/AI.php | 58 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/src/AI.php b/src/AI.php index 3961c05..f4e6d3a 100644 --- a/src/AI.php +++ b/src/AI.php @@ -2,14 +2,12 @@ namespace Backstage\AI; +use EchoLabs\Prism\ValueObjects\Messages\SystemMessage as MessagesSystemMessage; +use Prism\Prism\ValueObjects\Messages\SystemMessage; use Filament\Forms\Components\Actions\Action; -use Filament\Forms\Components\Field; -use Filament\Forms\Components\Section; -use Filament\Forms\Components\Select; -use Filament\Forms\Components\Textarea; -use Filament\Forms\Components\TextInput; use Filament\Forms\Get; use Filament\Forms\Set; +use Filament\Forms; use Filament\Notifications\Notification; use Prism\Prism\Exceptions\PrismException; use Prism\Prism\Prism; @@ -18,34 +16,34 @@ class AI { public static function registerMacro(): void { - Field::macro('withAI', function ($prompt = null) { + Forms\Components\Field::macro('withAI', function ($prompt = null) { return $this->hintAction( - function (Set $set, Field $component) use ($prompt) { + function (Set $set, Forms\Components\Field $component) use ($prompt) { return Action::make('ai') ->icon(config('backstage.ai.action.icon')) ->label(config('backstage.ai.action.label')) ->modalHeading(config('backstage.ai.action.modal.heading')) ->modalSubmitActionLabel('Generate') ->form([ - Select::make('model') + Forms\Components\Select::make('model') ->label('Model') ->options( collect(config('backstage.ai.providers')) - ->mapWithKeys(fn ($provider, $model) => [ + ->mapWithKeys(fn($provider, $model) => [ $model => $model . ' (' . $provider->name . ')', ]), ) ->default(key(config('backstage.ai.providers'))), - Textarea::make('prompt') + Forms\Components\Textarea::make('prompt') ->label('Prompt') ->autosize() ->default($prompt), - Section::make('configuration') + Forms\Components\Section::make('configuration') ->heading('Configuration') ->schema([ - TextInput::make('temperature') + Forms\Components\TextInput::make('temperature') ->numeric() ->label('Temperature') ->default(config('backstage.ai.configuration.temperature')) @@ -53,7 +51,8 @@ function (Set $set, Field $component) use ($prompt) { ->maxValue(1) ->minValue(0) ->step('0.1'), - TextInput::make('max_tokens') + + Forms\Components\TextInput::make('max_tokens') ->numeric() ->label('Max tokens') ->default(config('backstage.ai.configuration.max_tokens')) @@ -63,7 +62,7 @@ function (Set $set, Field $component) use ($prompt) { ->suffixAction( Action::make('increase') ->icon('heroicon-o-plus') - ->action(fn (Set $set, Get $get) => $set('max_tokens', $get('max_tokens') + 100)), + ->action(fn(Set $set, Get $get) => $set('max_tokens', $get('max_tokens') + 100)), ), ]) ->columns(2) @@ -71,10 +70,13 @@ function (Set $set, Field $component) use ($prompt) { ->collapsible(), ]) ->action(function ($data) use ($component, $set) { + $systemPrompts = AI::getSystemPrompts($data, $component); + try { $response = Prism::text() ->using(config('backstage.ai.providers.' . $data['model']), $data['model']) ->withPrompt($data['prompt']) + ->withSystemPrompts($systemPrompts) ->asText(); $set($component->getName(), $response->text); @@ -90,4 +92,32 @@ function (Set $set, Field $component) use ($prompt) { ); }); } + + public static function getSystemPrompts($data, Forms\Components\Field $component): array + { + $baseInstructions = [ + new SystemMessage('You are a helpful assistant. That\'s inside a Filament form field. This is the state of the field: ' . json_encode($component->getState())), + new SystemMessage('You must only return the value of the field.'), + new SystemMessage('No yapping, no explanations, no extra text.'), + ]; + + $instructions = [ + new SystemMessage('You must return a string value as output.'), + ]; + + if ($component instanceof Forms\Components\RichEditor) { + $instructions = [ + new SystemMessage('You must return HTML as output.')] + ; + } + + if ($component instanceof Forms\Components\MarkdownEditor) { + $instructions = [ + new SystemMessage('You must return Markdown as output. This is the field that will implement the Markdown (state) that you will return: https://filamentphp.com/docs/3.x/forms/fields/markdown-editor.'), + new SystemMessage("Don\'t return the markdown with markdown syntax like opening the markdown and closing it. For example: ```markdown... ```"), + ]; + } + + return array_merge($baseInstructions, $instructions); + } } From b4043bf36fceb70a6dbc2c41714afcb08f0d2d67 Mon Sep 17 00:00:00 2001 From: arduinomaster22 <91618246+arduinomaster22@users.noreply.github.com> Date: Fri, 9 May 2025 12:03:05 +0000 Subject: [PATCH 02/14] Fix styling --- src/AI.php | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/AI.php b/src/AI.php index f4e6d3a..734f846 100644 --- a/src/AI.php +++ b/src/AI.php @@ -2,15 +2,14 @@ namespace Backstage\AI; -use EchoLabs\Prism\ValueObjects\Messages\SystemMessage as MessagesSystemMessage; -use Prism\Prism\ValueObjects\Messages\SystemMessage; +use Filament\Forms; use Filament\Forms\Components\Actions\Action; use Filament\Forms\Get; use Filament\Forms\Set; -use Filament\Forms; use Filament\Notifications\Notification; use Prism\Prism\Exceptions\PrismException; use Prism\Prism\Prism; +use Prism\Prism\ValueObjects\Messages\SystemMessage; class AI { @@ -29,7 +28,7 @@ function (Set $set, Forms\Components\Field $component) use ($prompt) { ->label('Model') ->options( collect(config('backstage.ai.providers')) - ->mapWithKeys(fn($provider, $model) => [ + ->mapWithKeys(fn ($provider, $model) => [ $model => $model . ' (' . $provider->name . ')', ]), ) @@ -62,7 +61,7 @@ function (Set $set, Forms\Components\Field $component) use ($prompt) { ->suffixAction( Action::make('increase') ->icon('heroicon-o-plus') - ->action(fn(Set $set, Get $get) => $set('max_tokens', $get('max_tokens') + 100)), + ->action(fn (Set $set, Get $get) => $set('max_tokens', $get('max_tokens') + 100)), ), ]) ->columns(2) @@ -107,8 +106,7 @@ public static function getSystemPrompts($data, Forms\Components\Field $component if ($component instanceof Forms\Components\RichEditor) { $instructions = [ - new SystemMessage('You must return HTML as output.')] - ; + new SystemMessage('You must return HTML as output.')]; } if ($component instanceof Forms\Components\MarkdownEditor) { From edc00857acc62228928b97e0aa9cf41c5245c1ec Mon Sep 17 00:00:00 2001 From: Manoj Hortulanus Date: Fri, 9 May 2025 14:16:59 +0200 Subject: [PATCH 03/14] Add extra context --- src/AI.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/AI.php b/src/AI.php index 734f846..dc7e70e 100644 --- a/src/AI.php +++ b/src/AI.php @@ -2,14 +2,15 @@ namespace Backstage\AI; -use Filament\Forms; +use EchoLabs\Prism\ValueObjects\Messages\SystemMessage as MessagesSystemMessage; +use Prism\Prism\ValueObjects\Messages\SystemMessage; use Filament\Forms\Components\Actions\Action; use Filament\Forms\Get; use Filament\Forms\Set; +use Filament\Forms; use Filament\Notifications\Notification; use Prism\Prism\Exceptions\PrismException; use Prism\Prism\Prism; -use Prism\Prism\ValueObjects\Messages\SystemMessage; class AI { @@ -28,7 +29,7 @@ function (Set $set, Forms\Components\Field $component) use ($prompt) { ->label('Model') ->options( collect(config('backstage.ai.providers')) - ->mapWithKeys(fn ($provider, $model) => [ + ->mapWithKeys(fn($provider, $model) => [ $model => $model . ' (' . $provider->name . ')', ]), ) @@ -61,7 +62,7 @@ function (Set $set, Forms\Components\Field $component) use ($prompt) { ->suffixAction( Action::make('increase') ->icon('heroicon-o-plus') - ->action(fn (Set $set, Get $get) => $set('max_tokens', $get('max_tokens') + 100)), + ->action(fn(Set $set, Get $get) => $set('max_tokens', $get('max_tokens') + 100)), ), ]) ->columns(2) @@ -106,7 +107,8 @@ public static function getSystemPrompts($data, Forms\Components\Field $component if ($component instanceof Forms\Components\RichEditor) { $instructions = [ - new SystemMessage('You must return HTML as output.')]; + new SystemMessage('You must return HTML as output.') + ]; } if ($component instanceof Forms\Components\MarkdownEditor) { From 86c7b70e8ef4d483a30369ca9b843eaae3519dec Mon Sep 17 00:00:00 2001 From: Manoj Hortulanus Date: Fri, 9 May 2025 14:17:04 +0200 Subject: [PATCH 04/14] wip --- src/AI.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/AI.php b/src/AI.php index dc7e70e..0f6a7fc 100644 --- a/src/AI.php +++ b/src/AI.php @@ -16,10 +16,11 @@ class AI { public static function registerMacro(): void { - Forms\Components\Field::macro('withAI', function ($prompt = null) { - return $this->hintAction( - function (Set $set, Forms\Components\Field $component) use ($prompt) { + Forms\Components\Field::macro('withAI', function ($prompt = null, $hint = true) { + return $this->{$hint ? 'hintAction' : 'suffixAction'}( + function (Set $set, Forms\Components\Field $component) use ($prompt, $hint) { return Action::make('ai') + ->visible(fn($operation) => $operation !== 'view') ->icon(config('backstage.ai.action.icon')) ->label(config('backstage.ai.action.label')) ->modalHeading(config('backstage.ai.action.modal.heading')) From 4c2d3338227e4a3b149431ac966a6e27ec025fec Mon Sep 17 00:00:00 2001 From: arduinomaster22 <91618246+arduinomaster22@users.noreply.github.com> Date: Fri, 9 May 2025 12:18:12 +0000 Subject: [PATCH 05/14] Fix styling --- src/AI.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/AI.php b/src/AI.php index 0f6a7fc..1b3e609 100644 --- a/src/AI.php +++ b/src/AI.php @@ -2,15 +2,14 @@ namespace Backstage\AI; -use EchoLabs\Prism\ValueObjects\Messages\SystemMessage as MessagesSystemMessage; -use Prism\Prism\ValueObjects\Messages\SystemMessage; +use Filament\Forms; use Filament\Forms\Components\Actions\Action; use Filament\Forms\Get; use Filament\Forms\Set; -use Filament\Forms; use Filament\Notifications\Notification; use Prism\Prism\Exceptions\PrismException; use Prism\Prism\Prism; +use Prism\Prism\ValueObjects\Messages\SystemMessage; class AI { @@ -18,9 +17,9 @@ public static function registerMacro(): void { Forms\Components\Field::macro('withAI', function ($prompt = null, $hint = true) { return $this->{$hint ? 'hintAction' : 'suffixAction'}( - function (Set $set, Forms\Components\Field $component) use ($prompt, $hint) { + function (Set $set, Forms\Components\Field $component) use ($prompt) { return Action::make('ai') - ->visible(fn($operation) => $operation !== 'view') + ->visible(fn ($operation) => $operation !== 'view') ->icon(config('backstage.ai.action.icon')) ->label(config('backstage.ai.action.label')) ->modalHeading(config('backstage.ai.action.modal.heading')) @@ -30,7 +29,7 @@ function (Set $set, Forms\Components\Field $component) use ($prompt, $hint) { ->label('Model') ->options( collect(config('backstage.ai.providers')) - ->mapWithKeys(fn($provider, $model) => [ + ->mapWithKeys(fn ($provider, $model) => [ $model => $model . ' (' . $provider->name . ')', ]), ) @@ -63,7 +62,7 @@ function (Set $set, Forms\Components\Field $component) use ($prompt, $hint) { ->suffixAction( Action::make('increase') ->icon('heroicon-o-plus') - ->action(fn(Set $set, Get $get) => $set('max_tokens', $get('max_tokens') + 100)), + ->action(fn (Set $set, Get $get) => $set('max_tokens', $get('max_tokens') + 100)), ), ]) ->columns(2) @@ -108,7 +107,7 @@ public static function getSystemPrompts($data, Forms\Components\Field $component if ($component instanceof Forms\Components\RichEditor) { $instructions = [ - new SystemMessage('You must return HTML as output.') + new SystemMessage('You must return HTML as output.'), ]; } From a73570736e8900d7274a4cfca559e9060955f188 Mon Sep 17 00:00:00 2001 From: Manoj Hortulanus Date: Fri, 9 May 2025 14:29:12 +0200 Subject: [PATCH 06/14] Refine field-based instructions --- src/AI.php | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/AI.php b/src/AI.php index 0f6a7fc..6b1f5ba 100644 --- a/src/AI.php +++ b/src/AI.php @@ -108,7 +108,9 @@ public static function getSystemPrompts($data, Forms\Components\Field $component if ($component instanceof Forms\Components\RichEditor) { $instructions = [ - new SystemMessage('You must return HTML as output.') + new SystemMessage('You must return pure HTML as output.'), + new SystemMessage('This is the field that will implement the HTML (state) that you will return: https://filamentphp.com/docs/3.x/forms/fields/rich-editor.'), + new SystemMessage('Do not return any

tags.') ]; } @@ -119,6 +121,36 @@ public static function getSystemPrompts($data, Forms\Components\Field $component ]; } + if ($component instanceof Forms\Components\DateTimePicker) { + $format = $component->getFormat(); + + $instructions = [ + new SystemMessage('You must return a date as output.'), + new SystemMessage('The date format is: ' . $format), + ]; + } + + if ($component instanceof Forms\Components\TextInput && $component->isPassword()) { + $instructions = [ + new SystemMessage('You must return a password as output.'), + ]; + } + + if ($component instanceof Forms\Components\TextInput && $component->isEmail()) { + $instructions = [ + new SystemMessage('You must return an email as output.'), + ]; + } + + if ($component instanceof Forms\Components\Select) { + $instructions = [ + new SystemMessage('You must return a value from the select as output.'), + new SystemMessage('The options are: ' . json_encode($component->getOptions())), + new SystemMessage('You must return the key of the option as output.'), + ]; + } + + return array_merge($baseInstructions, $instructions); } } From b680a86cd3b523942a507d2651a2fdfe5d4bebfb Mon Sep 17 00:00:00 2001 From: arduinomaster22 <91618246+arduinomaster22@users.noreply.github.com> Date: Fri, 9 May 2025 12:29:32 +0000 Subject: [PATCH 07/14] Fix styling --- src/AI.php | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/AI.php b/src/AI.php index 6b1f5ba..9076099 100644 --- a/src/AI.php +++ b/src/AI.php @@ -2,15 +2,14 @@ namespace Backstage\AI; -use EchoLabs\Prism\ValueObjects\Messages\SystemMessage as MessagesSystemMessage; -use Prism\Prism\ValueObjects\Messages\SystemMessage; +use Filament\Forms; use Filament\Forms\Components\Actions\Action; use Filament\Forms\Get; use Filament\Forms\Set; -use Filament\Forms; use Filament\Notifications\Notification; use Prism\Prism\Exceptions\PrismException; use Prism\Prism\Prism; +use Prism\Prism\ValueObjects\Messages\SystemMessage; class AI { @@ -18,9 +17,9 @@ public static function registerMacro(): void { Forms\Components\Field::macro('withAI', function ($prompt = null, $hint = true) { return $this->{$hint ? 'hintAction' : 'suffixAction'}( - function (Set $set, Forms\Components\Field $component) use ($prompt, $hint) { + function (Set $set, Forms\Components\Field $component) use ($prompt) { return Action::make('ai') - ->visible(fn($operation) => $operation !== 'view') + ->visible(fn ($operation) => $operation !== 'view') ->icon(config('backstage.ai.action.icon')) ->label(config('backstage.ai.action.label')) ->modalHeading(config('backstage.ai.action.modal.heading')) @@ -30,7 +29,7 @@ function (Set $set, Forms\Components\Field $component) use ($prompt, $hint) { ->label('Model') ->options( collect(config('backstage.ai.providers')) - ->mapWithKeys(fn($provider, $model) => [ + ->mapWithKeys(fn ($provider, $model) => [ $model => $model . ' (' . $provider->name . ')', ]), ) @@ -63,7 +62,7 @@ function (Set $set, Forms\Components\Field $component) use ($prompt, $hint) { ->suffixAction( Action::make('increase') ->icon('heroicon-o-plus') - ->action(fn(Set $set, Get $get) => $set('max_tokens', $get('max_tokens') + 100)), + ->action(fn (Set $set, Get $get) => $set('max_tokens', $get('max_tokens') + 100)), ), ]) ->columns(2) @@ -110,7 +109,7 @@ public static function getSystemPrompts($data, Forms\Components\Field $component $instructions = [ new SystemMessage('You must return pure HTML as output.'), new SystemMessage('This is the field that will implement the HTML (state) that you will return: https://filamentphp.com/docs/3.x/forms/fields/rich-editor.'), - new SystemMessage('Do not return any

tags.') + new SystemMessage('Do not return any

tags.'), ]; } @@ -150,7 +149,6 @@ public static function getSystemPrompts($data, Forms\Components\Field $component ]; } - return array_merge($baseInstructions, $instructions); } } From e46fd18f23ea8cd723b10ccdb9cf0244b65a6a11 Mon Sep 17 00:00:00 2001 From: Manoj Hortulanus Date: Fri, 9 May 2025 14:29:12 +0200 Subject: [PATCH 08/14] Refine field-based instructions --- src/AI.php | 47 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/AI.php b/src/AI.php index 1b3e609..6b1f5ba 100644 --- a/src/AI.php +++ b/src/AI.php @@ -2,14 +2,15 @@ namespace Backstage\AI; -use Filament\Forms; +use EchoLabs\Prism\ValueObjects\Messages\SystemMessage as MessagesSystemMessage; +use Prism\Prism\ValueObjects\Messages\SystemMessage; use Filament\Forms\Components\Actions\Action; use Filament\Forms\Get; use Filament\Forms\Set; +use Filament\Forms; use Filament\Notifications\Notification; use Prism\Prism\Exceptions\PrismException; use Prism\Prism\Prism; -use Prism\Prism\ValueObjects\Messages\SystemMessage; class AI { @@ -17,9 +18,9 @@ public static function registerMacro(): void { Forms\Components\Field::macro('withAI', function ($prompt = null, $hint = true) { return $this->{$hint ? 'hintAction' : 'suffixAction'}( - function (Set $set, Forms\Components\Field $component) use ($prompt) { + function (Set $set, Forms\Components\Field $component) use ($prompt, $hint) { return Action::make('ai') - ->visible(fn ($operation) => $operation !== 'view') + ->visible(fn($operation) => $operation !== 'view') ->icon(config('backstage.ai.action.icon')) ->label(config('backstage.ai.action.label')) ->modalHeading(config('backstage.ai.action.modal.heading')) @@ -29,7 +30,7 @@ function (Set $set, Forms\Components\Field $component) use ($prompt) { ->label('Model') ->options( collect(config('backstage.ai.providers')) - ->mapWithKeys(fn ($provider, $model) => [ + ->mapWithKeys(fn($provider, $model) => [ $model => $model . ' (' . $provider->name . ')', ]), ) @@ -62,7 +63,7 @@ function (Set $set, Forms\Components\Field $component) use ($prompt) { ->suffixAction( Action::make('increase') ->icon('heroicon-o-plus') - ->action(fn (Set $set, Get $get) => $set('max_tokens', $get('max_tokens') + 100)), + ->action(fn(Set $set, Get $get) => $set('max_tokens', $get('max_tokens') + 100)), ), ]) ->columns(2) @@ -107,7 +108,9 @@ public static function getSystemPrompts($data, Forms\Components\Field $component if ($component instanceof Forms\Components\RichEditor) { $instructions = [ - new SystemMessage('You must return HTML as output.'), + new SystemMessage('You must return pure HTML as output.'), + new SystemMessage('This is the field that will implement the HTML (state) that you will return: https://filamentphp.com/docs/3.x/forms/fields/rich-editor.'), + new SystemMessage('Do not return any

tags.') ]; } @@ -118,6 +121,36 @@ public static function getSystemPrompts($data, Forms\Components\Field $component ]; } + if ($component instanceof Forms\Components\DateTimePicker) { + $format = $component->getFormat(); + + $instructions = [ + new SystemMessage('You must return a date as output.'), + new SystemMessage('The date format is: ' . $format), + ]; + } + + if ($component instanceof Forms\Components\TextInput && $component->isPassword()) { + $instructions = [ + new SystemMessage('You must return a password as output.'), + ]; + } + + if ($component instanceof Forms\Components\TextInput && $component->isEmail()) { + $instructions = [ + new SystemMessage('You must return an email as output.'), + ]; + } + + if ($component instanceof Forms\Components\Select) { + $instructions = [ + new SystemMessage('You must return a value from the select as output.'), + new SystemMessage('The options are: ' . json_encode($component->getOptions())), + new SystemMessage('You must return the key of the option as output.'), + ]; + } + + return array_merge($baseInstructions, $instructions); } } From 0e1131a190c0ee37f958e9c2b83d6683d91b6a8a Mon Sep 17 00:00:00 2001 From: Manoj Hortulanus Date: Fri, 9 May 2025 14:53:41 +0200 Subject: [PATCH 09/14] wip --- src/AI.php | 111 ++++++++---------- src/AIServiceProvider.php | 2 +- .../Forms/Components/BaseInstructions.php | 19 +++ .../Forms/Components/DateTimePicker.php | 20 ++++ .../Forms/Components/MarkdownEditor.php | 18 +++ .../Forms/Components/RichEditor.php | 19 +++ .../Forms/Components/Select.php | 19 +++ .../Forms/Components/TextInput.php | 27 +++++ 8 files changed, 170 insertions(+), 65 deletions(-) create mode 100644 src/Prism/SystemMessages/Forms/Components/BaseInstructions.php create mode 100644 src/Prism/SystemMessages/Forms/Components/DateTimePicker.php create mode 100644 src/Prism/SystemMessages/Forms/Components/MarkdownEditor.php create mode 100644 src/Prism/SystemMessages/Forms/Components/RichEditor.php create mode 100644 src/Prism/SystemMessages/Forms/Components/Select.php create mode 100644 src/Prism/SystemMessages/Forms/Components/TextInput.php diff --git a/src/AI.php b/src/AI.php index 6b1f5ba..ebd5b9a 100644 --- a/src/AI.php +++ b/src/AI.php @@ -2,19 +2,23 @@ namespace Backstage\AI; -use EchoLabs\Prism\ValueObjects\Messages\SystemMessage as MessagesSystemMessage; -use Prism\Prism\ValueObjects\Messages\SystemMessage; -use Filament\Forms\Components\Actions\Action; +use Filament\Forms; +use Prism\Prism\Prism; use Filament\Forms\Get; use Filament\Forms\Set; -use Filament\Forms; use Filament\Notifications\Notification; use Prism\Prism\Exceptions\PrismException; -use Prism\Prism\Prism; +use Filament\Forms\Components\Actions\Action; +use Backstage\AI\Prism\SystemMessages\Forms\Components\Select; +use Backstage\AI\Prism\SystemMessages\Forms\Components\TextInput; +use Backstage\AI\Prism\SystemMessages\Forms\Components\RichEditor; +use Backstage\AI\Prism\SystemMessages\Forms\Components\DateTimePicker; +use Backstage\AI\Prism\SystemMessages\Forms\Components\MarkdownEditor; +use Backstage\AI\Prism\SystemMessages\Forms\Components\BaseInstructions; class AI { - public static function registerMacro(): void + public static function registerFormMacro(): void { Forms\Components\Field::macro('withAI', function ($prompt = null, $hint = true) { return $this->{$hint ? 'hintAction' : 'suffixAction'}( @@ -71,7 +75,7 @@ function (Set $set, Forms\Components\Field $component) use ($prompt, $hint) { ->collapsible(), ]) ->action(function ($data) use ($component, $set) { - $systemPrompts = AI::getSystemPrompts($data, $component); + $systemPrompts = AI::getSystemPrompts($component); try { $response = Prism::text() @@ -80,6 +84,19 @@ function (Set $set, Forms\Components\Field $component) use ($prompt, $hint) { ->withSystemPrompts($systemPrompts) ->asText(); + $fieldState = $component->getState(); + + if ($fieldState === $response->text) { + Notification::make() + ->title(__('AI generated text is the same as the current state')) + ->body(__('Please be more specific with your prompt or try again.')) + ->danger() + ->send(); + + return; + } + + $set($component->getName(), $response->text); } catch (PrismException $exception) { Notification::make() @@ -94,63 +111,29 @@ function (Set $set, Forms\Components\Field $component) use ($prompt, $hint) { }); } - public static function getSystemPrompts($data, Forms\Components\Field $component): array + + /** + * Checking the type of the component and returning the specific instructions for each type. + * Allowed types are: + * @var Forms\Components\RichEditor + * @var Forms\Components\MarkdownEditor + * @var Forms\Components\DateTimePicker + * @var Forms\Components\TextInput + * @var Forms\Components\Select + */ + public static function getSystemPrompts(Forms\Components\Field $component): array { - $baseInstructions = [ - new SystemMessage('You are a helpful assistant. That\'s inside a Filament form field. This is the state of the field: ' . json_encode($component->getState())), - new SystemMessage('You must only return the value of the field.'), - new SystemMessage('No yapping, no explanations, no extra text.'), - ]; - - $instructions = [ - new SystemMessage('You must return a string value as output.'), - ]; - - if ($component instanceof Forms\Components\RichEditor) { - $instructions = [ - new SystemMessage('You must return pure HTML as output.'), - new SystemMessage('This is the field that will implement the HTML (state) that you will return: https://filamentphp.com/docs/3.x/forms/fields/rich-editor.'), - new SystemMessage('Do not return any

tags.') - ]; - } - - if ($component instanceof Forms\Components\MarkdownEditor) { - $instructions = [ - new SystemMessage('You must return Markdown as output. This is the field that will implement the Markdown (state) that you will return: https://filamentphp.com/docs/3.x/forms/fields/markdown-editor.'), - new SystemMessage("Don\'t return the markdown with markdown syntax like opening the markdown and closing it. For example: ```markdown... ```"), - ]; - } - - if ($component instanceof Forms\Components\DateTimePicker) { - $format = $component->getFormat(); - - $instructions = [ - new SystemMessage('You must return a date as output.'), - new SystemMessage('The date format is: ' . $format), - ]; - } - - if ($component instanceof Forms\Components\TextInput && $component->isPassword()) { - $instructions = [ - new SystemMessage('You must return a password as output.'), - ]; - } - - if ($component instanceof Forms\Components\TextInput && $component->isEmail()) { - $instructions = [ - new SystemMessage('You must return an email as output.'), - ]; - } - - if ($component instanceof Forms\Components\Select) { - $instructions = [ - new SystemMessage('You must return a value from the select as output.'), - new SystemMessage('The options are: ' . json_encode($component->getOptions())), - new SystemMessage('You must return the key of the option as output.'), - ]; - } - - - return array_merge($baseInstructions, $instructions); + $baseInstructions = BaseInstructions::ask($component); + + $componentInstructions = match (true) { + $component instanceof Forms\Components\RichEditor => RichEditor::ask($component), + $component instanceof Forms\Components\MarkdownEditor => MarkdownEditor::ask($component), + $component instanceof Forms\Components\DateTimePicker => DateTimePicker::ask($component), + $component instanceof Forms\Components\TextInput => TextInput::ask($component), + $component instanceof Forms\Components\Select => Select::ask($component), + default => [], + }; + + return array_merge($baseInstructions, $componentInstructions); } } diff --git a/src/AIServiceProvider.php b/src/AIServiceProvider.php index 0bb244e..9ef864a 100644 --- a/src/AIServiceProvider.php +++ b/src/AIServiceProvider.php @@ -32,7 +32,7 @@ public function configurePackage(Package $package): void public function packageBooted(): void { - AI::registerMacro(); + AI::registerFormMacro(); } /** diff --git a/src/Prism/SystemMessages/Forms/Components/BaseInstructions.php b/src/Prism/SystemMessages/Forms/Components/BaseInstructions.php new file mode 100644 index 0000000..b436dc2 --- /dev/null +++ b/src/Prism/SystemMessages/Forms/Components/BaseInstructions.php @@ -0,0 +1,19 @@ +getState())), + new SystemMessage('You must only return the value of the field.'), + new SystemMessage('No yapping, no explanations, no extra text.'), + ]; + + return $baseInstructions; + } +} diff --git a/src/Prism/SystemMessages/Forms/Components/DateTimePicker.php b/src/Prism/SystemMessages/Forms/Components/DateTimePicker.php new file mode 100644 index 0000000..36a3486 --- /dev/null +++ b/src/Prism/SystemMessages/Forms/Components/DateTimePicker.php @@ -0,0 +1,20 @@ +getFormat(); + + $instructions = [ + new SystemMessage('You must return a date as output.'), + new SystemMessage('The date format is: ' . $format), + ]; + + return $instructions; + } +} diff --git a/src/Prism/SystemMessages/Forms/Components/MarkdownEditor.php b/src/Prism/SystemMessages/Forms/Components/MarkdownEditor.php new file mode 100644 index 0000000..3f82b92 --- /dev/null +++ b/src/Prism/SystemMessages/Forms/Components/MarkdownEditor.php @@ -0,0 +1,18 @@ + tags.') + ]; + + return $instructions; + } +} diff --git a/src/Prism/SystemMessages/Forms/Components/Select.php b/src/Prism/SystemMessages/Forms/Components/Select.php new file mode 100644 index 0000000..a79610b --- /dev/null +++ b/src/Prism/SystemMessages/Forms/Components/Select.php @@ -0,0 +1,19 @@ +getOptions())), + new SystemMessage('You must return the key of the option as output.'), + ]; + + return $instructions; + } +} diff --git a/src/Prism/SystemMessages/Forms/Components/TextInput.php b/src/Prism/SystemMessages/Forms/Components/TextInput.php new file mode 100644 index 0000000..74e0913 --- /dev/null +++ b/src/Prism/SystemMessages/Forms/Components/TextInput.php @@ -0,0 +1,27 @@ +isPassword()) { + $instructions = [ + new SystemMessage('You must return a password as output.'), + ]; + } + + if ($component->isEmail()) { + $instructions = [ + new SystemMessage('You must return an email as output.'), + ]; + } + + return $instructions; + } +} From f01dce06ec096e40344d9355e0d1e9eb3ebad051 Mon Sep 17 00:00:00 2001 From: arduinomaster22 <91618246+arduinomaster22@users.noreply.github.com> Date: Fri, 9 May 2025 12:54:42 +0000 Subject: [PATCH 10/14] Fix styling --- src/AI.php | 27 +++++++++---------- .../Forms/Components/MarkdownEditor.php | 2 +- .../Forms/Components/RichEditor.php | 2 +- .../Forms/Components/Select.php | 2 +- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/AI.php b/src/AI.php index ebd5b9a..1efb6d2 100644 --- a/src/AI.php +++ b/src/AI.php @@ -2,19 +2,19 @@ namespace Backstage\AI; +use Backstage\AI\Prism\SystemMessages\Forms\Components\BaseInstructions; +use Backstage\AI\Prism\SystemMessages\Forms\Components\DateTimePicker; +use Backstage\AI\Prism\SystemMessages\Forms\Components\MarkdownEditor; +use Backstage\AI\Prism\SystemMessages\Forms\Components\RichEditor; +use Backstage\AI\Prism\SystemMessages\Forms\Components\Select; +use Backstage\AI\Prism\SystemMessages\Forms\Components\TextInput; use Filament\Forms; -use Prism\Prism\Prism; +use Filament\Forms\Components\Actions\Action; use Filament\Forms\Get; use Filament\Forms\Set; use Filament\Notifications\Notification; use Prism\Prism\Exceptions\PrismException; -use Filament\Forms\Components\Actions\Action; -use Backstage\AI\Prism\SystemMessages\Forms\Components\Select; -use Backstage\AI\Prism\SystemMessages\Forms\Components\TextInput; -use Backstage\AI\Prism\SystemMessages\Forms\Components\RichEditor; -use Backstage\AI\Prism\SystemMessages\Forms\Components\DateTimePicker; -use Backstage\AI\Prism\SystemMessages\Forms\Components\MarkdownEditor; -use Backstage\AI\Prism\SystemMessages\Forms\Components\BaseInstructions; +use Prism\Prism\Prism; class AI { @@ -22,9 +22,9 @@ public static function registerFormMacro(): void { Forms\Components\Field::macro('withAI', function ($prompt = null, $hint = true) { return $this->{$hint ? 'hintAction' : 'suffixAction'}( - function (Set $set, Forms\Components\Field $component) use ($prompt, $hint) { + function (Set $set, Forms\Components\Field $component) use ($prompt) { return Action::make('ai') - ->visible(fn($operation) => $operation !== 'view') + ->visible(fn ($operation) => $operation !== 'view') ->icon(config('backstage.ai.action.icon')) ->label(config('backstage.ai.action.label')) ->modalHeading(config('backstage.ai.action.modal.heading')) @@ -34,7 +34,7 @@ function (Set $set, Forms\Components\Field $component) use ($prompt, $hint) { ->label('Model') ->options( collect(config('backstage.ai.providers')) - ->mapWithKeys(fn($provider, $model) => [ + ->mapWithKeys(fn ($provider, $model) => [ $model => $model . ' (' . $provider->name . ')', ]), ) @@ -67,7 +67,7 @@ function (Set $set, Forms\Components\Field $component) use ($prompt, $hint) { ->suffixAction( Action::make('increase') ->icon('heroicon-o-plus') - ->action(fn(Set $set, Get $get) => $set('max_tokens', $get('max_tokens') + 100)), + ->action(fn (Set $set, Get $get) => $set('max_tokens', $get('max_tokens') + 100)), ), ]) ->columns(2) @@ -96,7 +96,6 @@ function (Set $set, Forms\Components\Field $component) use ($prompt, $hint) { return; } - $set($component->getName(), $response->text); } catch (PrismException $exception) { Notification::make() @@ -111,10 +110,10 @@ function (Set $set, Forms\Components\Field $component) use ($prompt, $hint) { }); } - /** * Checking the type of the component and returning the specific instructions for each type. * Allowed types are: + * * @var Forms\Components\RichEditor * @var Forms\Components\MarkdownEditor * @var Forms\Components\DateTimePicker diff --git a/src/Prism/SystemMessages/Forms/Components/MarkdownEditor.php b/src/Prism/SystemMessages/Forms/Components/MarkdownEditor.php index 3f82b92..dd652d7 100644 --- a/src/Prism/SystemMessages/Forms/Components/MarkdownEditor.php +++ b/src/Prism/SystemMessages/Forms/Components/MarkdownEditor.php @@ -8,7 +8,7 @@ class MarkdownEditor { public static function ask(\Filament\Forms\Components\MarkdownEditor $component): array { - $instructions = [ + $instructions = [ new SystemMessage('You must return Markdown as output. This is the field that will implement the Markdown (state) that you will return: https://filamentphp.com/docs/3.x/forms/fields/markdown-editor.'), new SystemMessage("Don\'t return the markdown with markdown syntax like opening the markdown and closing it. For example: ```markdown... ```"), ]; diff --git a/src/Prism/SystemMessages/Forms/Components/RichEditor.php b/src/Prism/SystemMessages/Forms/Components/RichEditor.php index 457885a..c0ff61e 100644 --- a/src/Prism/SystemMessages/Forms/Components/RichEditor.php +++ b/src/Prism/SystemMessages/Forms/Components/RichEditor.php @@ -11,7 +11,7 @@ public static function ask(\Filament\Forms\Components\RichEditor $component): ar $instructions = [ new SystemMessage('You must return pure HTML as output.'), new SystemMessage('This is the field that will implement the HTML (state) that you will return: https://filamentphp.com/docs/3.x/forms/fields/rich-editor.'), - new SystemMessage('Do not return any

tags.') + new SystemMessage('Do not return any

tags.'), ]; return $instructions; diff --git a/src/Prism/SystemMessages/Forms/Components/Select.php b/src/Prism/SystemMessages/Forms/Components/Select.php index a79610b..22c0f55 100644 --- a/src/Prism/SystemMessages/Forms/Components/Select.php +++ b/src/Prism/SystemMessages/Forms/Components/Select.php @@ -13,7 +13,7 @@ public static function ask(\Filament\Forms\Components\Select $component): array new SystemMessage('The options are: ' . json_encode($component->getOptions())), new SystemMessage('You must return the key of the option as output.'), ]; - + return $instructions; } } From 6e462fd9c4d932d1f44c382bdcd5195f2eb22511 Mon Sep 17 00:00:00 2001 From: Manoj Hortulanus Date: Fri, 9 May 2025 15:12:35 +0200 Subject: [PATCH 11/14] wip --- src/AI.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/AI.php b/src/AI.php index 1efb6d2..d6abefe 100644 --- a/src/AI.php +++ b/src/AI.php @@ -28,10 +28,10 @@ function (Set $set, Forms\Components\Field $component) use ($prompt) { ->icon(config('backstage.ai.action.icon')) ->label(config('backstage.ai.action.label')) ->modalHeading(config('backstage.ai.action.modal.heading')) - ->modalSubmitActionLabel('Generate') + ->modalSubmitActionLabel(__('Generate')) ->form([ Forms\Components\Select::make('model') - ->label('Model') + ->label(__('AI Model')) ->options( collect(config('backstage.ai.providers')) ->mapWithKeys(fn ($provider, $model) => [ @@ -41,16 +41,16 @@ function (Set $set, Forms\Components\Field $component) use ($prompt) { ->default(key(config('backstage.ai.providers'))), Forms\Components\Textarea::make('prompt') - ->label('Prompt') + ->label(__('Instructions')) ->autosize() ->default($prompt), Forms\Components\Section::make('configuration') - ->heading('Configuration') + ->heading(__('Configuration')) ->schema([ Forms\Components\TextInput::make('temperature') ->numeric() - ->label('Temperature') + ->label(__('AI Temperature')) ->default(config('backstage.ai.configuration.temperature')) ->helperText('The higher the temperature, the more creative the text') ->maxValue(1) @@ -59,7 +59,7 @@ function (Set $set, Forms\Components\Field $component) use ($prompt) { Forms\Components\TextInput::make('max_tokens') ->numeric() - ->label('Max tokens') + ->label(__('Max Tokens')) ->default(config('backstage.ai.configuration.max_tokens')) ->helperText('The maximum number of tokens to generate') ->step('10') From 37704ded6f1f7ad7d7d3f2985242dbdcd36255e4 Mon Sep 17 00:00:00 2001 From: arduinomaster22 <91618246+arduinomaster22@users.noreply.github.com> Date: Fri, 9 May 2025 13:52:25 +0000 Subject: [PATCH 12/14] Fix styling --- src/AI.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/AI.php b/src/AI.php index e8acdb2..d6abefe 100644 --- a/src/AI.php +++ b/src/AI.php @@ -15,7 +15,6 @@ use Filament\Notifications\Notification; use Prism\Prism\Exceptions\PrismException; use Prism\Prism\Prism; -use Prism\Prism\ValueObjects\Messages\SystemMessage; class AI { @@ -134,7 +133,6 @@ public static function getSystemPrompts(Forms\Components\Field $component): arra default => [], }; - return array_merge($baseInstructions, $componentInstructions); } } From ec92d47939f41f0d8c6fa79090e37f5dd4478307 Mon Sep 17 00:00:00 2001 From: Manoj Hortulanus Date: Fri, 9 May 2025 16:07:51 +0200 Subject: [PATCH 13/14] wip --- src/AI.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AI.php b/src/AI.php index d6abefe..90390a4 100644 --- a/src/AI.php +++ b/src/AI.php @@ -13,6 +13,7 @@ use Filament\Forms\Get; use Filament\Forms\Set; use Filament\Notifications\Notification; +use Prism\Prism\Enums\Provider; use Prism\Prism\Exceptions\PrismException; use Prism\Prism\Prism; @@ -34,7 +35,7 @@ function (Set $set, Forms\Components\Field $component) use ($prompt) { ->label(__('AI Model')) ->options( collect(config('backstage.ai.providers')) - ->mapWithKeys(fn ($provider, $model) => [ + ->mapWithKeys(fn (Provider $provider, $model) => [ $model => $model . ' (' . $provider->name . ')', ]), ) From 4a4a135d9e4416e06a39ab4e6174da1076493d8c Mon Sep 17 00:00:00 2001 From: Manoj Hortulanus Date: Fri, 23 May 2025 11:14:43 +0200 Subject: [PATCH 14/14] Add focus --- src/AI.php | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/AI.php b/src/AI.php index 90390a4..06a1c95 100644 --- a/src/AI.php +++ b/src/AI.php @@ -23,19 +23,23 @@ public static function registerFormMacro(): void { Forms\Components\Field::macro('withAI', function ($prompt = null, $hint = true) { return $this->{$hint ? 'hintAction' : 'suffixAction'}( - function (Set $set, Forms\Components\Field $component) use ($prompt) { + function (Set $set, Forms\Components\Field $component) use ($prompt, $hint) { return Action::make('ai') - ->visible(fn ($operation) => $operation !== 'view') + ->visible(fn($operation) => $operation !== 'view') ->icon(config('backstage.ai.action.icon')) ->label(config('backstage.ai.action.label')) ->modalHeading(config('backstage.ai.action.modal.heading')) ->modalSubmitActionLabel(__('Generate')) + ->extraAttributes($hint ? [ + 'x-show' => 'focused || hover', + 'x-cloak' => '', + ] : []) ->form([ Forms\Components\Select::make('model') ->label(__('AI Model')) ->options( collect(config('backstage.ai.providers')) - ->mapWithKeys(fn (Provider $provider, $model) => [ + ->mapWithKeys(fn(Provider $provider, $model) => [ $model => $model . ' (' . $provider->name . ')', ]), ) @@ -68,7 +72,7 @@ function (Set $set, Forms\Components\Field $component) use ($prompt) { ->suffixAction( Action::make('increase') ->icon('heroicon-o-plus') - ->action(fn (Set $set, Get $get) => $set('max_tokens', $get('max_tokens') + 100)), + ->action(fn(Set $set, Get $get) => $set('max_tokens', $get('max_tokens') + 100)), ), ]) ->columns(2) @@ -107,7 +111,15 @@ function (Set $set, Forms\Components\Field $component) use ($prompt) { } }); } - ); + ) + ->extraFieldWrapperAttributes([ + 'x-data' => '{focused: false, hover: false}', + 'x-on:mouseover' => 'hover = true', + 'x-on:mouseout' => 'hover = false', + ])->extraInputAttributes([ + 'x-on:focus' => 'focused = true', + 'x-on:blur' => 'focused = false', + ]); }); }