Skip to content

Commit

Permalink
#10 - regulations for escooters (#194)
Browse files Browse the repository at this point in the history
* navbar cleanup

* Added route and controller

* Add RulesController index method, Rules page, nav href

* Getting rules from GPT API

* Fix Nav component import, fix guest route to rules, still 500 on /rules

* Fix controller

* Update RulesController and update route

* Create migrations

* Fix importer

* Update query

* Nav cleanup - delete rules tab

* Update translations and add regulations section at city view

* change to job

* fix routes

* Update app.css and Index.vue

* added import rules button, changes in city view for fetching data, rules route

* Refactor regulations fetching in City Index.vue

* Translations for rules import button, route fix

* lint

* small fixes

* regulations padding

* translation rules

* small fixes

* translation fix

* Refactor regulations display logic

* rules are working

* Lintf and remove unused code

* avoid unnecessary api calls

* Update import rules endpoint

* added info about loading

* fixed html displaying in rules

* route fix

* Lintf, replaced v-html with v-text

* instead of v-html used v-if & v-else

* fixed rules, now they are correctly displaying new line char

* linter

* added visual hins for loading

* change border-[1px] to border

Co-authored-by: Ewelina Skrzypacz <[email protected]>

* Update lang/pl.json

Co-authored-by: Ewelina Skrzypacz <[email protected]>

* Update lang/pl.json

Co-authored-by: Ewelina Skrzypacz <[email protected]>

* Update app/Http/Controllers/RulesController.php

Co-authored-by: Kamil Piech <[email protected]>

* composer csf

* comma fix

* Changed ENG to EN where it was possible

* Change ENG to EN

* lint

* Add type declarations and fix constructor

* Used string interpolation, add error toast, polish translation.

* lintf

* small fixes

* fetchRegulations fix

* replace v-html with v-text

* lintf

* Add exception, fix variable naming

* changes in exception handling

* fix displaying import errors

* typing

* small fixes

* fix api.php

* small fixes

* csf

* Fix variable names

* Update dependencies in composer.json

* Update lang/pl.json

Co-authored-by: Jakub Wójcik <[email protected]>

* small changes

* Modify functions in OpenAIService.php

* small fixes

* fix translation

* change to config

---------

Co-authored-by: JakubKermes <[email protected]>
Co-authored-by: zmigroo <[email protected]>
Co-authored-by: Patryk Żmigrodzki <[email protected]>
Co-authored-by: Ewelina Skrzypacz <[email protected]>
Co-authored-by: Kamil Piech <[email protected]>
Co-authored-by: Jakub Kermes <[email protected]>
Co-authored-by: Jakub Wójcik <[email protected]>
Co-authored-by: Aleksandra Kozubal <[email protected]>
Co-authored-by: Aleksandra Kozubal <[email protected]>
  • Loading branch information
10 people authored May 14, 2024
1 parent 293e2b6 commit 4916bec
Show file tree
Hide file tree
Showing 22 changed files with 792 additions and 42 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,5 @@ GOOGLE_REDIRECT_URI=http://escooters.blumilk.localhost/login/google/redirect
FACEBOOK_CLIENT_ID=
FACEBOOK_CLIENT_SECRET=
FACEBOOK_REDIRECT_URI=http://escooters.blumilk.localhost/login/facebook/redirect

OPENAI_API_KEY=
4 changes: 4 additions & 0 deletions app/Exceptions/ExceptionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ public function render($request, Throwable $exception)
return back()->withErrors($exception->errors());
}

if ($exception instanceof OpenAiException) {
return response()->json(["error" => $exception->getMessage()], $exception->getCode());
}

$response = parent::render($request, $exception);
$statusCode = $response->status();

Expand Down
14 changes: 14 additions & 0 deletions app/Exceptions/OpenAiException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace App\Exceptions;

use Exception;
use Symfony\Component\HttpFoundation\Response;

class OpenAiException extends Exception
{
protected $message = "OpenAI API connection error";
protected $code = Response::HTTP_INTERNAL_SERVER_ERROR;
}
50 changes: 50 additions & 0 deletions app/Http/Controllers/Api/RulesController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers\Api;

use App\Models\City;
use App\Models\Country;
use App\Models\Rules;
use App\Services\OpenAIService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class RulesController
{
public function getRules(Country $country, City $city): JsonResponse
{
$rules = Rules::query()
->where("city_id", $city->id)
->first();

if (!$rules || $rules->rules_en === null || $rules->rules_pl === null) {
$cityData = [
"cityId" => $city->id,
"countryId" => $country->id,
"cityName" => $city->name,
"countryName" => $country->name,
];
$importer = new OpenAIService();
$data = $importer->importRulesForCity($cityData, true);
} else {
$data = [
"country" => $country->name,
"city" => $city->name,
"rules_en" => $rules->rules_en,
"rules_pl" => $rules->rules_pl,
];
}

return response()->json($data);
}

public function importRules(Request $request, OpenAIService $importer): JsonResponse
{
$force = $request->input("force", false);
$importer->importRulesForAllCities($force);

return response()->json(["message" => "Rules import started"]);
}
}
47 changes: 47 additions & 0 deletions app/Http/Controllers/RulesController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Models\City;
use App\Models\Country;
use App\Models\Rules;
use App\Services\OpenAIService;
use Illuminate\Http\Request;

class RulesController
{
public function getRules(Country $country, City $city): array
{
$rules = Rules::query()
->where("city_id", $city->id)
->first();

if (!$rules || $rules->rules_en === null || $rules->rules_pl === null) {
$cityData = [
"cityId" => $city->id,
"countryId" => $country->id,
"cityName" => $city->name,
"countryName" => $country->name,
];
$importer = new OpenAIService();
$data = $importer->importRulesForCity($cityData, true);
} else {
$data = [
"country" => $country->name,
"city" => $city->name,
"rules_en" => $rules->rules_en,
"rules_pl" => $rules->rules_pl,
];
}

return $data;
}

public function importRules(Request $request, OpenAIService $importer): void
{
$force = $request->input("force", false);
$importer->importRulesForAllCities($force);
}
}
30 changes: 30 additions & 0 deletions app/Jobs/ImportCityRulesJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace App\Jobs;

use App\Services\OpenAIService;
use Illuminate\Bus\Batchable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class ImportCityRulesJob implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use SerializesModels;
use Batchable;

public function __construct(
private array $cityData,
private bool $force,
) {}

public function handle(OpenAIService $openAIService): array
{
return $openAIService->importRulesForCity($this->cityData, $this->force);
}
}
29 changes: 29 additions & 0 deletions app/Models/Rules.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Rules extends Model
{
protected $table = "rules_for_cities";
protected $fillable = [
"city_id",
"country_id",
"rules_en",
"rules_pl",
];

public function city(): BelongsTo
{
return $this->belongsTo(City::class);
}

public function country(): BelongsTo
{
return $this->belongsTo(Country::class);
}
}
130 changes: 130 additions & 0 deletions app/Services/OpenAIService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

declare(strict_types=1);

namespace App\Services;

use App\Exceptions\OpenAiException;
use App\Jobs\ImportCityRulesJob;
use App\Models\City;
use App\Models\ImportInfo;
use App\Models\ImportInfoDetail;
use App\Models\Rules;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Storage;
use OpenAI;
use Throwable;

class OpenAIService implements ShouldQueue
{
private OpenAI\Client $client;
private array $countriesKnownToHaveUniformRules;

public function __construct()
{
try {
$this->client = OpenAI::client(config("openai.token"));
} catch (Throwable $e) {
throw new OpenAiException();
}

$this->countriesKnownToHaveUniformRules = Storage::disk("public")->json("citiesKnownToHaveUniformRules.json");
}

public function importRulesForAllCities(bool $force): void
{
$cities = City::query()->whereHas("cityProviders")->orderBy("country_id")->get();

$importInfo = ImportInfo::query()->create([
"who_runs_it" => "admin",
"status" => "running",
]);
$jobs = [];

foreach ($cities as $city) {
$cityData = [
"cityId" => $city->id,
"countryId" => $city->country_id,
"cityName" => $city->name,
"countryName" => $city->country->name,
];
$jobs[] = new ImportCityRulesJob($cityData, $force);
ImportCityRulesJob::dispatch($cityData, $force);
}

Bus::batch($jobs)
->catch(fn() => ImportInfoDetail::query()->updateOrCreate([
"import_info_id" => $importInfo->id,
"provider_name" => "OpenAI",
"code" => 400,
]))
->finally(fn() => ImportInfo::query()->where("id", $importInfo->id)->update([
"status" => "finished",
]))
->dispatch();
}

public function importRulesForCity(array $cityData, bool $force): array
{
$cityId = $cityData["cityId"];
$countryId = $cityData["countryId"];
$cityName = $cityData["cityName"];
$countryName = $cityData["countryName"];

$promptEn = "Act as a helpful assistant. Explain what are the legal limitations for riding electric scooters in $cityName, $countryName? Contain information about: max speed, helmet requirements, allowed ABV, passengers, other relevant details. Be formal, speak English. Don't include city name in your response. If you don't have information answering the question, write 'null'.";
$promptPl = "Translate to polish: ";
$currentRulesInCountry = Rules::query()->where("country_id", $countryId)->first();

if ($this->checkIfRulesExist($countryName, $currentRulesInCountry) && !$force) {
$rulesEn = $currentRulesInCountry->rules_en;
$rulesPl = $currentRulesInCountry->rules_pl;
} else {
$rulesEn = $this->askGPT($promptEn);
$rulesPl = $this->askGPT($promptPl . $rulesEn);
}

if (strlen($rulesEn) < 700 || strlen($rulesPl) < 700) {
return [
"city" => $cityName,
"country" => $countryName,
"rules_en" => null,
"rules_pl" => null,
];
}

Rules::query()->updateOrCreate([
"city_id" => $cityId,
"country_id" => $countryId,
"rules_en" => $rulesEn,
"rules_pl" => $rulesPl,
]);

return [
"city" => $cityName,
"country" => $countryName,
"rules_en" => $rulesEn,
"rules_pl" => $rulesPl,
];
}

private function askGPT(string $prompt): string
{
$response = $this->client->chat()->create([
"model" => "gpt-3.5-turbo",
"messages" => [
[
"role" => "user",
"content" => $prompt,
],
],
]);

return $response["choices"][0]["message"]["content"];
}

private function checkIfRulesExist(string $countryName, ?Rules $currentRulesInCountry): bool
{
return in_array($countryName, $this->countriesKnownToHaveUniformRules, true) && $currentRulesInCountry !== null && $currentRulesInCountry->rules_en !== null && $currentRulesInCountry->rules_pl !== null;
}
}
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
"ext-gd": "*",
"ext-pdo": "*",
"guzzlehttp/guzzle": "^7.7",
"inertiajs/inertia-laravel": "^0.6.9",
"dedoc/scramble": "^0.9.0",
"inertiajs/inertia-laravel": "^1.0.0",
"intervention/image": "2.*",

"laravel/framework": "^10.13.0",
"laravel/sanctum": "^3.2.5",
"laravel/socialite": "^5.10",
"laravel/tinker": "^2.8.1",
"openai-php/client": "^0.8.1",
"sentry/sentry-laravel": "^3.7",
"spatie/laravel-permission": "^6.1",
"stichoza/google-translate-php": "^5.1",
Expand Down
Loading

0 comments on commit 4916bec

Please sign in to comment.