From d5d5521a904c5d8d919104e3ddb693abf701aaa0 Mon Sep 17 00:00:00 2001 From: Julien Nahum Date: Mon, 27 Mar 2023 19:58:05 +0200 Subject: [PATCH] Queue AI processing to avoid request > 30 sec --- .../Controllers/Forms/AiFormController.php | 25 +++--- app/Jobs/Form/GenerateAiForm.php | 88 +++++++++++++++++++ app/Models/Forms/AI/AiFormCompletion.php | 38 ++++++++ ...53317_create_ai_form_completions_table.php | 35 ++++++++ .../2023_03_27_173507_create_jobs_table.php | 36 ++++++++ .../js/components/common/Notifications.vue | 2 +- .../forms/create/CreateFormBaseModal.vue | 44 ++++++++-- routes/api.php | 1 + vapor.yml | 2 +- 9 files changed, 248 insertions(+), 23 deletions(-) create mode 100644 app/Jobs/Form/GenerateAiForm.php create mode 100644 app/Models/Forms/AI/AiFormCompletion.php create mode 100644 database/migrations/2023_03_27_153317_create_ai_form_completions_table.php create mode 100644 database/migrations/2023_03_27_173507_create_jobs_table.php diff --git a/app/Http/Controllers/Forms/AiFormController.php b/app/Http/Controllers/Forms/AiFormController.php index aa371fb..cc05295 100644 --- a/app/Http/Controllers/Forms/AiFormController.php +++ b/app/Http/Controllers/Forms/AiFormController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers\Forms; use App\Console\Commands\GenerateTemplate; use App\Http\Controllers\Controller; use App\Http\Requests\AiGenerateFormRequest; +use App\Models\Forms\AI\AiFormCompletion; use App\Service\OpenAi\GptCompleter; use Illuminate\Support\Str; @@ -13,26 +14,24 @@ class AiFormController extends Controller public function generateForm(AiGenerateFormRequest $request) { $this->middleware('throttle:4,1'); - $completer = (new GptCompleter(config('services.openai.api_key'))) - ->setSystemMessage('You are a robot helping to generate forms.'); - $completer->completeChat([ - ["role" => "user", "content" => Str::of(GenerateTemplate::FORM_STRUCTURE_PROMPT) - ->replace('[REPLACE]', $request->form_prompt)->toString()] - ], 3000); return $this->success([ - 'message' => 'Form successfully generated!', - 'form' => $this->cleanOutput($completer->getArray()) + 'message' => 'We\'re working on your form, please wait ~1 min.', + 'ai_form_completion_id' => AiFormCompletion::create([ + 'form_prompt' => $request->input('form_prompt'), + 'ip' => $request->ip() + ])->id ]); } - private function cleanOutput($formData) + public function show(AiFormCompletion $aiFormCompletion) { - // Add property uuids - foreach ($formData['properties'] as &$property) { - $property['id'] = Str::uuid()->toString(); + if ($aiFormCompletion->ip != request()->ip()) { + return $this->error('You are not authorized to view this AI completion.', 403); } - return $formData; + return $this->success([ + 'ai_form_completion' => $aiFormCompletion + ]); } } diff --git a/app/Jobs/Form/GenerateAiForm.php b/app/Jobs/Form/GenerateAiForm.php new file mode 100644 index 0000000..04d595c --- /dev/null +++ b/app/Jobs/Form/GenerateAiForm.php @@ -0,0 +1,88 @@ +completion->update([ + 'status' => AiFormCompletion::STATUS_PROCESSING + ]); + + $completer = (new GptCompleter(config('services.openai.api_key'))) + ->setSystemMessage('You are a robot helping to generate forms.'); + + try { + $completer->completeChat([ + ["role" => "user", "content" => Str::of(GenerateTemplate::FORM_STRUCTURE_PROMPT) + ->replace('[REPLACE]', $this->completion->form_prompt)->toString()] + ], 3000); + + $this->completion->update([ + 'status' => AiFormCompletion::STATUS_COMPLETED, + 'result' => $this->cleanOutput($completer->getArray()) + ]); + } catch (\Exception $e) { + $this->completion->update([ + 'status' => AiFormCompletion::STATUS_FAILED, + 'result' => $e->getMessage() + ]); + } + + } + + public function generateForm(AiGenerateFormRequest $request) + { + $completer = (new GptCompleter(config('services.openai.api_key'))) + ->setSystemMessage('You are a robot helping to generate forms.'); + $completer->completeChat([ + ["role" => "user", "content" => Str::of(GenerateTemplate::FORM_STRUCTURE_PROMPT) + ->replace('[REPLACE]', $request->form_prompt)->toString()] + ], 3000); + + return $this->success([ + 'message' => 'Form successfully generated!', + 'form' => $this->cleanOutput($completer->getArray()) + ]); + } + + private function cleanOutput($formData) + { + // Add property uuids + foreach ($formData['properties'] as &$property) { + $property['id'] = Str::uuid()->toString(); + } + + return $formData; + } +} diff --git a/app/Models/Forms/AI/AiFormCompletion.php b/app/Models/Forms/AI/AiFormCompletion.php new file mode 100644 index 0000000..df9cab4 --- /dev/null +++ b/app/Models/Forms/AI/AiFormCompletion.php @@ -0,0 +1,38 @@ + self::STATUS_PENDING + ]; + + protected static function booted() + { + // Dispatch completion job on creation + static::created(function (self $completion) { + GenerateAiForm::dispatch($completion); + }); + } +} diff --git a/database/migrations/2023_03_27_153317_create_ai_form_completions_table.php b/database/migrations/2023_03_27_153317_create_ai_form_completions_table.php new file mode 100644 index 0000000..88de1d3 --- /dev/null +++ b/database/migrations/2023_03_27_153317_create_ai_form_completions_table.php @@ -0,0 +1,35 @@ +id(); + $table->string('form_prompt'); + $table->string('status'); + $table->json('result')->nullable(); + $table->string('ip'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('ai_form_completions'); + } +}; diff --git a/database/migrations/2023_03_27_173507_create_jobs_table.php b/database/migrations/2023_03_27_173507_create_jobs_table.php new file mode 100644 index 0000000..a786a89 --- /dev/null +++ b/database/migrations/2023_03_27_173507_create_jobs_table.php @@ -0,0 +1,36 @@ +bigIncrements('id'); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('jobs'); + } +}; diff --git a/resources/js/components/common/Notifications.vue b/resources/js/components/common/Notifications.vue index 6a0f262..6f12a13 100644 --- a/resources/js/components/common/Notifications.vue +++ b/resources/js/components/common/Notifications.vue @@ -1,5 +1,5 @@