diff --git a/.env.example b/.env.example index ae0e0c3..654deba 100644 --- a/.env.example +++ b/.env.example @@ -75,3 +75,6 @@ PADDLE_PRICE_PRO_YEARLY= VITE_PADDLE_PRICE_PRO_MONTHLY="${PADDLE_PRICE_PRO_MONTHLY}" VITE_PADDLE_PRICE_PRO_YEARLY="${PADDLE_PRICE_PRO_YEARLY}" + +OPENROUTER_API_KEY= +OPENROUTER_MODEL=nvidia/nemotron-3-super-120b-a12b:free diff --git a/app/Observers/ThemeObserver.php b/app/Observers/ThemeObserver.php index 43fba32..cc60282 100644 --- a/app/Observers/ThemeObserver.php +++ b/app/Observers/ThemeObserver.php @@ -3,6 +3,7 @@ namespace App\Observers; use App\Models\Theme; +use App\Services\AiService; use Illuminate\Support\Str; class ThemeObserver @@ -12,5 +13,13 @@ public function creating(Theme $theme): void if (! $theme->title) { $theme->title = Str::headline($theme->name); } + + if (! $theme->description) { + $ai = app(AiService::class); + $theme->description = $ai->generateThemeDescription( + $theme->name, + $theme->vars_light ?? [] + ); + } } } diff --git a/app/Services/AiService.php b/app/Services/AiService.php new file mode 100644 index 0000000..7d5cb16 --- /dev/null +++ b/app/Services/AiService.php @@ -0,0 +1,45 @@ +map(fn ($value, $key) => "{$key}: {$value}") + ->implode(', '); + + $prompt = "Generate a short, engaging description (max 2 sentences) for a UI theme named \"{$name}\" that uses these colors: {$colorList}. The description should highlight the mood or style of the theme."; + + $response = Http::withHeaders([ + 'Authorization' => 'Bearer '.$apiKey, + 'HTTP-Referer' => config('app.url'), + 'X-Title' => config('app.name'), + ])->post('https://openrouter.ai/api/v1/chat/completions', [ + 'model' => config('services.openrouter.model'), + 'messages' => [ + [ + 'role' => 'user', + 'content' => $prompt, + ], + ], + ]); + + if ($response->failed()) { + return null; + } + + $data = $response->json(); + + return $data['choices'][0]['message']['content'] ?? null; + } +} diff --git a/config/services.php b/config/services.php index b8ced15..5c87866 100644 --- a/config/services.php +++ b/config/services.php @@ -46,4 +46,9 @@ 'redirect' => env('GOOGLE_REDIRECT_URI'), ], + 'openrouter' => [ + 'key' => env('OPENROUTER_API_KEY'), + 'model' => env('OPENROUTER_MODEL', 'nvidia/nemotron-3-super-120b-a12b:free'), + ], + ]; diff --git a/tests/Feature/AiDescriptionTest.php b/tests/Feature/AiDescriptionTest.php new file mode 100644 index 0000000..5615d9c --- /dev/null +++ b/tests/Feature/AiDescriptionTest.php @@ -0,0 +1,51 @@ + Http::response([ + 'choices' => [ + [ + 'message' => [ + 'content' => 'A beautiful dark theme with neon accents.' + ] + ] + ] + ], 200), + 'https://example.com/theme.json' => Http::response([ + 'name' => 'neon-dark', + 'cssVars' => [ + 'light' => [ + 'background' => '0 0% 100%', + 'foreground' => '222.2 84% 4.9%', + ], + ], + ], 200), + ]); + + config(['services.openrouter.key' => 'test-key']); + + $user = User::factory()->create(); + + $response = $this->actingAs($user)->post(route('themes.store'), [ + 'url' => 'https://example.com/theme.json', + ]); + + $this->assertDatabaseHas('themes', [ + 'name' => 'neon-dark', + 'description' => 'A beautiful dark theme with neon accents.', + ]); + } +}