First Commit
This commit is contained in:
Executable
+98
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\AiModel;
|
||||
use App\Models\AiProvider;
|
||||
use App\Models\PromptProject;
|
||||
use App\Services\Ai\AiServiceFactory;
|
||||
use App\Services\AiAgentService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AiAgentController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AiAgentService $aiAgentService
|
||||
) {}
|
||||
|
||||
public function index(): View
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
$projectsQuery = PromptProject::with(['provider', 'model'])->latest();
|
||||
|
||||
if (!$user || $user->role !== 'superadmin') {
|
||||
$projectsQuery->where('user_id', $user->id);
|
||||
}
|
||||
|
||||
$projects = $projectsQuery->get();
|
||||
|
||||
$providers = AiProvider::where('is_active', true)
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
$defaultProviderId = 1;
|
||||
$defaultModelId = 11;
|
||||
|
||||
return view('ai-agent.index', compact('projects', 'providers', 'defaultProviderId', 'defaultModelId'));
|
||||
}
|
||||
|
||||
public function execute(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'project_id' => 'nullable|exists:prompt_projects,id',
|
||||
'prompt_text' => 'required|string',
|
||||
'provider_id' => 'required|exists:ai_providers,id',
|
||||
'model_id' => 'required|exists:ai_models,id',
|
||||
'temperature' => 'nullable|numeric|min:0|max:1',
|
||||
'max_tokens' => 'nullable|integer|min:1|max:128000',
|
||||
]);
|
||||
|
||||
$provider = AiProvider::where('id', $request->provider_id)
|
||||
->where('is_active', true)
|
||||
->firstOrFail();
|
||||
|
||||
$model = AiModel::where('id', $request->model_id)
|
||||
->where('provider_id', $provider->id)
|
||||
->where('is_active', true)
|
||||
->firstOrFail();
|
||||
|
||||
$result = $this->aiAgentService->execute(
|
||||
prompt: $request->prompt_text,
|
||||
provider: $provider,
|
||||
model: $model,
|
||||
temperature: (float) ($request->temperature ?? 0.7),
|
||||
maxTokens: (int) ($request->max_tokens ?? 1024),
|
||||
);
|
||||
|
||||
return redirect()->route('ai-agent.index')
|
||||
->with('success', 'Execução concluída com sucesso.')
|
||||
->with('agent_result', $result)
|
||||
->withInput([
|
||||
'project_id' => $request->project_id,
|
||||
'prompt_text' => $request->prompt_text,
|
||||
'provider_id' => $request->provider_id,
|
||||
'model_id' => $request->model_id,
|
||||
'temperature' => $request->temperature,
|
||||
'max_tokens' => $request->max_tokens,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getModels(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'provider_id' => 'required|exists:ai_providers,id',
|
||||
]);
|
||||
|
||||
$models = AiModel::where('provider_id', $request->provider_id)
|
||||
->where('is_active', true)
|
||||
->orderBy('name')
|
||||
->get(['id', 'name', 'slug']);
|
||||
|
||||
return response()->json($models);
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\AiProvider;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AiProvider\StoreAiProviderRequest;
|
||||
use App\Http\Requests\AiProvider\UpdateAiProviderRequest;
|
||||
use App\Services\AiProviderService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AiProviderController extends Controller
|
||||
{
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly AiProviderService $aiProviderService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
$this->authorize('viewAny', \App\Models\AiProvider::class);
|
||||
|
||||
$result = $this->aiProviderService->index();
|
||||
$providers = $result['data'] ?? [];
|
||||
|
||||
return view('ai-providers.index', compact('providers'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(\App\Models\AiProvider $aiProvider): View
|
||||
{
|
||||
$this->authorize('view', $aiProvider);
|
||||
|
||||
return view('ai-providers.show', compact('aiProvider'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
$this->authorize('create', \App\Models\AiProvider::class);
|
||||
|
||||
return view('ai-providers.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(StoreAiProviderRequest $request): RedirectResponse
|
||||
{
|
||||
$this->authorize('create', \App\Models\AiProvider::class);
|
||||
|
||||
$this->aiProviderService->store($request->validated());
|
||||
|
||||
return redirect()->route('ai-providers.index')
|
||||
->with('success', 'Provider created successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(\App\Models\AiProvider $aiProvider): View
|
||||
{
|
||||
$this->authorize('update', $aiProvider);
|
||||
|
||||
return view('ai-providers.edit', compact('aiProvider'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(UpdateAiProviderRequest $request, \App\Models\AiProvider $aiProvider): RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $aiProvider);
|
||||
|
||||
$this->aiProviderService->update($aiProvider, $request->validated());
|
||||
|
||||
return redirect()->route('ai-providers.index')
|
||||
->with('success', 'Provider updated successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(\App\Models\AiProvider $aiProvider): RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', $aiProvider);
|
||||
|
||||
$this->aiProviderService->destroy($aiProvider);
|
||||
|
||||
return redirect()->route('ai-providers.index')
|
||||
->with('success', 'Provider deleted successfully.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Auth\LoginRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AuthenticatedSessionController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the login view.
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
return view('auth.login');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming authentication request.
|
||||
*/
|
||||
public function store(LoginRequest $request): RedirectResponse
|
||||
{
|
||||
$request->authenticate();
|
||||
|
||||
$request->session()->regenerate();
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy an authenticated session.
|
||||
*/
|
||||
public function destroy(Request $request): RedirectResponse
|
||||
{
|
||||
Auth::guard('web')->logout();
|
||||
|
||||
$request->session()->invalidate();
|
||||
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
||||
class CaptchaController extends Controller
|
||||
{
|
||||
/**
|
||||
* Generate a new arithmetic captcha.
|
||||
*/
|
||||
public function generate(Request $request): \Illuminate\Http\JsonResponse
|
||||
{
|
||||
$a = random_int(1, 20);
|
||||
$b = random_int(1, 20);
|
||||
$operations = ['+', '-', '*'];
|
||||
$operation = $operations[array_rand($operations)];
|
||||
|
||||
if ($operation === '+') {
|
||||
$answer = $a + $b;
|
||||
} elseif ($operation === '-') {
|
||||
$answer = $a - $b;
|
||||
} else {
|
||||
$answer = $a * $b;
|
||||
}
|
||||
|
||||
$expression = "{$a} {$operation} {$b} = ?";
|
||||
|
||||
Session::put('captcha_answer', (string) $answer);
|
||||
|
||||
return response()->json([
|
||||
'expression' => $expression,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class ConfirmablePasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the confirm password view.
|
||||
*/
|
||||
public function show(): View
|
||||
{
|
||||
return view('auth.confirm-password');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm the user's password.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if (! Auth::guard('web')->validate([
|
||||
'email' => $request->user()->email,
|
||||
'password' => $request->password,
|
||||
])) {
|
||||
throw ValidationException::withMessages([
|
||||
'password' => __('auth.password'),
|
||||
]);
|
||||
}
|
||||
|
||||
$request->session()->put('auth.password_confirmed_at', time());
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EmailVerificationNotificationController extends Controller
|
||||
{
|
||||
/**
|
||||
* Send a new email verification notification.
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
if ($request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->intended(route('dashboard', absolute: false));
|
||||
}
|
||||
|
||||
$request->user()->sendEmailVerificationNotification();
|
||||
|
||||
return back()->with('status', 'verification-link-sent');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class EmailVerificationPromptController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the email verification prompt.
|
||||
*/
|
||||
public function __invoke(Request $request): RedirectResponse|View
|
||||
{
|
||||
return $request->user()->hasVerifiedEmail()
|
||||
? redirect()->intended(route('dashboard', absolute: false))
|
||||
: view('auth.verify-email');
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Events\PasswordReset;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class NewPasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the password reset view.
|
||||
*/
|
||||
public function create(Request $request): View
|
||||
{
|
||||
return view('auth.reset-password', ['request' => $request]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming new password request.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'token' => ['required'],
|
||||
'email' => ['required', 'email'],
|
||||
'password' => ['required', 'confirmed', Rules\Password::defaults()],
|
||||
]);
|
||||
|
||||
// Here we will attempt to reset the user's password. If it is successful we
|
||||
// will update the password on an actual user model and persist it to the
|
||||
// database. Otherwise we will parse the error and return the response.
|
||||
$status = Password::reset(
|
||||
$request->only('email', 'password', 'password_confirmation', 'token'),
|
||||
function (User $user) use ($request) {
|
||||
$user->forceFill([
|
||||
'password' => Hash::make($request->password),
|
||||
'remember_token' => Str::random(60),
|
||||
])->save();
|
||||
|
||||
event(new PasswordReset($user));
|
||||
}
|
||||
);
|
||||
|
||||
// If the password was successfully reset, we will redirect the user back to
|
||||
// the application's home authenticated view. If there is an error we can
|
||||
// redirect them back to where they came from with their error message.
|
||||
return $status == Password::PASSWORD_RESET
|
||||
? redirect()->route('login')->with('status', __($status))
|
||||
: back()->withInput($request->only('email'))
|
||||
->withErrors(['email' => __($status)]);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class PasswordController extends Controller
|
||||
{
|
||||
/**
|
||||
* Update the user's password.
|
||||
*/
|
||||
public function update(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validateWithBag('updatePassword', [
|
||||
'password' => ['required', 'confirmed', Password::defaults()],
|
||||
]);
|
||||
|
||||
$request->user()->update([
|
||||
'password' => Hash::make($validated['password']),
|
||||
]);
|
||||
|
||||
return back()->with('status', 'password-updated');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class PasswordResetLinkController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the password reset link request view.
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
return view('auth.forgot-password');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming password reset link request.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'email' => ['required', 'email'],
|
||||
]);
|
||||
|
||||
// We will send the password reset link to this user. Once we have attempted
|
||||
// to send the link, we will examine the response then see the message we
|
||||
// need to show to the user. Finally, we'll send out a proper response.
|
||||
$status = Password::sendResetLink(
|
||||
$request->only('email')
|
||||
);
|
||||
|
||||
return $status == Password::RESET_LINK_SENT
|
||||
? back()->with('status', __($status))
|
||||
: back()->withInput($request->only('email'))
|
||||
->withErrors(['email' => __($status)]);
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Auth\RegisterRequest;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class RegisteredUserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the registration view.
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
return view('auth.register');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming registration request.
|
||||
*
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function store(RegisterRequest $request): RedirectResponse
|
||||
{
|
||||
$user = User::create([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'password' => Hash::make($request->password),
|
||||
'role' => 'pending',
|
||||
'is_approved' => false,
|
||||
]);
|
||||
|
||||
event(new Registered($user));
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
$this->recordDocumentAcceptances($user, $request);
|
||||
|
||||
return redirect(route('dashboard'));
|
||||
}
|
||||
|
||||
private function recordDocumentAcceptances(User $user, RegisterRequest $request): void
|
||||
{
|
||||
$documents = \App\Models\LegalDocument::all()->groupBy('type');
|
||||
|
||||
foreach ($documents as $type => $docs) {
|
||||
$latest = $docs->sortByDesc('version')->first();
|
||||
|
||||
if (!$latest) {
|
||||
continue;
|
||||
}
|
||||
|
||||
\App\Models\UserDocumentAcceptance::create([
|
||||
'user_id' => $user->id,
|
||||
'legal_document_id' => $latest->id,
|
||||
'document_version' => $latest->version,
|
||||
'ip_address' => $request->ip(),
|
||||
'user_agent' => $request->userAgent(),
|
||||
'accepted_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Auth\Events\Verified;
|
||||
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
||||
class VerifyEmailController extends Controller
|
||||
{
|
||||
/**
|
||||
* Mark the authenticated user's email address as verified.
|
||||
*/
|
||||
public function __invoke(EmailVerificationRequest $request): RedirectResponse
|
||||
{
|
||||
if ($request->user()->hasVerifiedEmail()) {
|
||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
|
||||
if ($request->user()->markEmailAsVerified()) {
|
||||
event(new Verified($request->user()));
|
||||
}
|
||||
|
||||
return redirect()->intended(route('dashboard', absolute: false).'?verified=1');
|
||||
}
|
||||
}
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\AiModel;
|
||||
use App\Models\AiProvider;
|
||||
use App\Services\ChatService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class ChatController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ChatService $chatService
|
||||
) {}
|
||||
|
||||
public function index(): View
|
||||
{
|
||||
$providers = AiProvider::where('is_active', true)
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
$history = $this->chatService->getHistory();
|
||||
|
||||
$defaultProviderId = 1;
|
||||
$defaultModelId = 2;
|
||||
|
||||
return view('chat.index', compact('providers', 'history', 'defaultProviderId', 'defaultModelId'));
|
||||
}
|
||||
|
||||
public function send(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'message' => 'required|string',
|
||||
'provider_id' => 'required|exists:ai_providers,id',
|
||||
'model_id' => 'required|exists:ai_models,id',
|
||||
'temperature' => 'nullable|numeric|min:0|max:1',
|
||||
'max_tokens' => 'nullable|integer|min:1|max:128000',
|
||||
]);
|
||||
|
||||
$provider = AiProvider::where('id', $request->provider_id)
|
||||
->where('is_active', true)
|
||||
->firstOrFail();
|
||||
|
||||
$model = AiModel::where('id', $request->model_id)
|
||||
->where('provider_id', $provider->id)
|
||||
->where('is_active', true)
|
||||
->firstOrFail();
|
||||
|
||||
$result = $this->chatService->sendMessage(
|
||||
userMessage: $request->message,
|
||||
provider: $provider,
|
||||
model: $model,
|
||||
temperature: (float) ($request->temperature ?? 0.7),
|
||||
maxTokens: (int) ($request->max_tokens ?? 8192),
|
||||
);
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
|
||||
public function reset(): RedirectResponse
|
||||
{
|
||||
$this->chatService->reset();
|
||||
|
||||
return redirect()->route('chat.index')
|
||||
->with('success', 'Conversa reiniciada.');
|
||||
}
|
||||
|
||||
public function getModels(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'provider_id' => 'required|exists:ai_providers,id',
|
||||
]);
|
||||
|
||||
$models = AiModel::where('provider_id', $request->provider_id)
|
||||
->where('is_active', true)
|
||||
->orderBy('name')
|
||||
->get(['id', 'name', 'slug']);
|
||||
|
||||
return response()->json($models);
|
||||
}
|
||||
}
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
abstract class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, ValidatesRequests;
|
||||
}
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\AiProvider;
|
||||
use App\Services\PromptGenerationService;
|
||||
use App\Services\PromptProjectService;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PromptProjectService $promptProjectService,
|
||||
private readonly PromptGenerationService $promptGenerationService
|
||||
) {}
|
||||
|
||||
public function index(): View
|
||||
{
|
||||
$userId = auth()->id();
|
||||
$user = auth()->user();
|
||||
|
||||
$stats = [
|
||||
'projects' => $this->promptProjectService->index()['total'] ?? 0,
|
||||
'generations' => $this->promptGenerationService->index()->total() ?? 0,
|
||||
'providers' => AiProvider::count(),
|
||||
'tokens' => $user && $user->role === 'superadmin'
|
||||
? \App\Models\PromptGeneration::sum('tokens_used') ?? 0
|
||||
: \App\Models\PromptGeneration::where('user_id', $userId)->sum('tokens_used') ?? 0,
|
||||
];
|
||||
|
||||
$recentGenerationsQuery = \App\Models\PromptGeneration::with(['project', 'model'])
|
||||
->latest()
|
||||
->limit(5);
|
||||
|
||||
if (!$user || $user->role !== 'superadmin') {
|
||||
$recentGenerationsQuery->where('user_id', $userId);
|
||||
}
|
||||
|
||||
$recentGenerations = $recentGenerationsQuery->get();
|
||||
|
||||
return view('dashboard', compact('stats', 'recentGenerations'));
|
||||
}
|
||||
}
|
||||
Executable
+19
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the welcome page.
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
return view('welcome');
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Legal;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\CookieConsent;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class CookieConsentController extends Controller
|
||||
{
|
||||
/**
|
||||
* Store cookie consent preferences.
|
||||
*/
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'consents' => ['required', 'array'],
|
||||
'consents.necessary' => ['required', 'boolean'],
|
||||
'consents.statistics' => ['nullable', 'boolean'],
|
||||
'consents.marketing' => ['nullable', 'boolean'],
|
||||
'consents.preferences' => ['nullable', 'boolean'],
|
||||
]);
|
||||
|
||||
$consent = CookieConsent::updateOrCreate(
|
||||
[
|
||||
'user_id' => Auth::id(),
|
||||
'session_id' => $request->session()->getId(),
|
||||
],
|
||||
[
|
||||
'ip_address' => $request->ip(),
|
||||
'user_agent' => $request->userAgent(),
|
||||
'consents' => $validated['consents'],
|
||||
'accepted_at' => now(),
|
||||
]
|
||||
);
|
||||
|
||||
return response()->json(['success' => true, 'consent_id' => $consent->id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current cookie consent preferences.
|
||||
*/
|
||||
public function show(Request $request)
|
||||
{
|
||||
$consent = CookieConsent::where('user_id', Auth::id())
|
||||
->orWhere('session_id', $request->session()->getId())
|
||||
->latest()
|
||||
->first();
|
||||
|
||||
return response()->json($consent ? $consent->consents : null);
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Legal;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\LegalDocument;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class LegalDocumentController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of legal documents.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->authorize('viewAny', LegalDocument::class);
|
||||
|
||||
$documents = LegalDocument::orderBy('type')->orderByDesc('version')->paginate(20);
|
||||
|
||||
return view('legal.documents.index', compact('documents'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing a legal document.
|
||||
*/
|
||||
public function edit(string $type)
|
||||
{
|
||||
$document = LegalDocument::where('type', $type)->latest('version')->first();
|
||||
|
||||
if (!$document) {
|
||||
$document = new LegalDocument(['type' => $type]);
|
||||
}
|
||||
|
||||
$this->authorize('update', $document);
|
||||
|
||||
return view('legal.documents.edit', compact('document'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update or create a legal document.
|
||||
*/
|
||||
public function update(Request $request, string $type)
|
||||
{
|
||||
$this->authorize('create', LegalDocument::class);
|
||||
|
||||
$validated = $request->validate([
|
||||
'title' => ['required', 'string', 'max:255'],
|
||||
'content' => ['required', 'string'],
|
||||
'version' => ['required', 'string', 'max:50'],
|
||||
'published_at' => ['nullable', 'date'],
|
||||
]);
|
||||
|
||||
$document = LegalDocument::where('type', $type)->latest('version')->first();
|
||||
|
||||
if ($document && $document->version === $validated['version']) {
|
||||
$document->update([
|
||||
'title' => $validated['title'],
|
||||
'content' => $validated['content'],
|
||||
'published_at' => $validated['published_at'] ? now() : null,
|
||||
]);
|
||||
} else {
|
||||
$document = LegalDocument::create(array_merge($validated, [
|
||||
'type' => $type,
|
||||
'published_by' => Auth::id(),
|
||||
'published_at' => $validated['published_at'] ?? now(),
|
||||
]));
|
||||
}
|
||||
|
||||
return redirect()->route('legal.documents.index')->with('success', 'Documento atualizado com sucesso.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the legal document page.
|
||||
*/
|
||||
public function show(string $type)
|
||||
{
|
||||
$document = LegalDocument::where('type', $type)->latest('version')->firstOrFail();
|
||||
|
||||
return view('legal.documents.show', compact('document'));
|
||||
}
|
||||
}
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\PromptGeneration;
|
||||
use App\Models\PromptProject;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
$stats = [
|
||||
'projects' => PromptProject::where('user_id', $user->id)->count(),
|
||||
'generations' => PromptGeneration::where('user_id', $user->id)->count(),
|
||||
'tokens' => PromptGeneration::where('user_id', $user->id)->sum('tokens_used') ?? 0,
|
||||
];
|
||||
|
||||
return view('profile.index', compact('user', 'stats'));
|
||||
}
|
||||
|
||||
public function edit(): View
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
return view('profile.edit', compact('user'));
|
||||
}
|
||||
|
||||
public function update(Request $request): RedirectResponse
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|max:255|unique:users,email,' . $user->id,
|
||||
'avatar' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
|
||||
]);
|
||||
|
||||
if ($request->hasFile('avatar')) {
|
||||
$path = $request->file('avatar')->store('avatars', 'public');
|
||||
$user->avatar = $path;
|
||||
}
|
||||
|
||||
$user->name = $validated['name'];
|
||||
$user->email = $validated['email'];
|
||||
|
||||
$user->save();
|
||||
|
||||
return redirect()->route('profile.index')
|
||||
->with('success', 'Perfil atualizado com sucesso.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\PromptGeneration;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\PromptGeneration\GeneratePromptRequest;
|
||||
use App\Models\PromptGeneration;
|
||||
use App\Services\PromptGenerationService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class PromptGenerationController extends Controller
|
||||
{
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly PromptGenerationService $promptGenerationService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Display a listing of prompt generations.
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
$generations = $this->promptGenerationService->index();
|
||||
|
||||
return view('prompt-generations.index', compact('generations'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new prompt.
|
||||
*/
|
||||
public function generate(GeneratePromptRequest $request): RedirectResponse
|
||||
{
|
||||
$result = $this->promptGenerationService->generate($request->validated());
|
||||
|
||||
return redirect()->route('prompt-generations.index')
|
||||
->with('success', 'Prompt generated successfully.')
|
||||
->with('generation', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified prompt generation.
|
||||
*/
|
||||
public function destroy(Request $request, PromptGeneration $generation): RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', $generation);
|
||||
|
||||
$this->promptGenerationService->destroy($generation);
|
||||
|
||||
return redirect()->back()->with('success', 'Prompt generation deleted successfully.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\PromptProject;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\PromptProject\StorePromptProjectRequest;
|
||||
use App\Http\Requests\PromptProject\UpdatePromptProjectRequest;
|
||||
use App\Models\AiProvider;
|
||||
use App\Services\PromptProjectService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class PromptProjectController extends Controller
|
||||
{
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly PromptProjectService $promptProjectService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
$result = $this->promptProjectService->index();
|
||||
$projects = $result['data'] ?? [];
|
||||
|
||||
return view('prompt-projects.index', compact('projects'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
$this->authorize('create', \App\Models\PromptProject::class);
|
||||
|
||||
$providers = AiProvider::where('is_active', true)->get();
|
||||
|
||||
return view('prompt-projects.create', compact('providers'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(StorePromptProjectRequest $request): RedirectResponse
|
||||
{
|
||||
$this->authorize('create', \App\Models\PromptProject::class);
|
||||
|
||||
$this->promptProjectService->store($request->validated());
|
||||
|
||||
return redirect()->route('prompt-projects.index')
|
||||
->with('success', 'Project created successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(\App\Models\PromptProject $promptProject): View
|
||||
{
|
||||
$this->authorize('view', $promptProject);
|
||||
|
||||
$providerModels = \App\Models\AiModel::where('provider_id', $promptProject->provider_id)
|
||||
->where('is_active', true)
|
||||
->get();
|
||||
|
||||
$models = $providerModels->isNotEmpty()
|
||||
? $providerModels
|
||||
: \App\Models\AiModel::where('is_active', true)->get();
|
||||
|
||||
$generations = \App\Models\PromptGeneration::where('project_id', $promptProject->id)
|
||||
->latest()
|
||||
->take(5)
|
||||
->get();
|
||||
|
||||
return view('prompt-projects.show', compact('promptProject', 'models', 'generations'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(\App\Models\PromptProject $promptProject): View
|
||||
{
|
||||
$this->authorize('update', $promptProject);
|
||||
|
||||
$providers = AiProvider::where('is_active', true)->get();
|
||||
|
||||
return view('prompt-projects.edit', compact('promptProject', 'providers'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(UpdatePromptProjectRequest $request, \App\Models\PromptProject $promptProject): RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $promptProject);
|
||||
|
||||
$this->promptProjectService->update($promptProject, $request->validated());
|
||||
|
||||
return redirect()->route('prompt-projects.show', $promptProject)
|
||||
->with('success', 'Project updated successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(\App\Models\PromptProject $promptProject): RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', $promptProject);
|
||||
|
||||
$this->promptProjectService->destroy($promptProject);
|
||||
|
||||
return redirect()->route('prompt-projects.index')
|
||||
->with('success', 'Project deleted successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate prompt for the specified project.
|
||||
*/
|
||||
public function generate(\App\Models\PromptProject $promptProject, Request $request): RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $promptProject);
|
||||
|
||||
$model = null;
|
||||
if ($request->filled('model')) {
|
||||
$modelSlug = $request->input('model');
|
||||
|
||||
if ($modelSlug === 'outros') {
|
||||
$customModel = $request->input('custom_model');
|
||||
if ($customModel) {
|
||||
$model = $customModel;
|
||||
}
|
||||
} else {
|
||||
$model = \App\Models\AiModel::where('slug', $modelSlug)
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
}
|
||||
}
|
||||
|
||||
$this->promptProjectService->generate($promptProject, $model, Auth::id());
|
||||
|
||||
return redirect()->route('prompt-projects.show', $promptProject)
|
||||
->with('success', 'Prompt generated successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get built prompt for the specified project.
|
||||
*/
|
||||
public function getPrompt(\App\Models\PromptProject $promptProject): \Illuminate\Http\JsonResponse
|
||||
{
|
||||
$this->authorize('view', $promptProject);
|
||||
|
||||
$prompt = $this->promptProjectService->buildPrompt($promptProject);
|
||||
|
||||
return response()->json(['prompt' => $prompt]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\PromptTemplate;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\PromptTemplate\StorePromptTemplateRequest;
|
||||
use App\Http\Requests\PromptTemplate\UpdatePromptTemplateRequest;
|
||||
use App\Models\PromptTemplate;
|
||||
use App\Services\PromptTemplateService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class PromptTemplateController extends Controller
|
||||
{
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly PromptTemplateService $promptTemplateService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(): View
|
||||
{
|
||||
$this->authorize('viewAny', \App\Models\PromptTemplate::class);
|
||||
|
||||
$result = $this->promptTemplateService->index();
|
||||
$templates = $result['data'] ?? [];
|
||||
|
||||
return view('prompt-templates.index', compact('templates'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
$this->authorize('create', \App\Models\PromptTemplate::class);
|
||||
|
||||
return view('prompt-templates.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(StorePromptTemplateRequest $request): RedirectResponse
|
||||
{
|
||||
$this->authorize('create', \App\Models\PromptTemplate::class);
|
||||
|
||||
$data = $request->validated();
|
||||
|
||||
if (isset($data['template_data']) && is_string($data['template_data'])) {
|
||||
$data['template_data'] = json_decode($data['template_data'], true);
|
||||
}
|
||||
|
||||
$this->promptTemplateService->store($data);
|
||||
|
||||
return redirect()->route('prompt-templates.index')
|
||||
->with('success', 'Template created successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(\App\Models\PromptTemplate $promptTemplate): View
|
||||
{
|
||||
$this->authorize('update', $promptTemplate);
|
||||
|
||||
return view('prompt-templates.edit', compact('promptTemplate'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(UpdatePromptTemplateRequest $request, \App\Models\PromptTemplate $promptTemplate): RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $promptTemplate);
|
||||
|
||||
$data = $request->validated();
|
||||
|
||||
if (isset($data['template_data']) && is_string($data['template_data'])) {
|
||||
$data['template_data'] = json_decode($data['template_data'], true);
|
||||
}
|
||||
|
||||
$this->promptTemplateService->update($promptTemplate, $data);
|
||||
|
||||
return redirect()->route('prompt-templates.index')
|
||||
->with('success', 'Template updated successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(\App\Models\PromptTemplate $promptTemplate): RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', $promptTemplate);
|
||||
|
||||
$this->promptTemplateService->destroy($promptTemplate);
|
||||
|
||||
return redirect()->route('prompt-templates.index')
|
||||
->with('success', 'Template deleted successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all templates as JSON for the dropdown.
|
||||
*/
|
||||
public function api(): JsonResponse
|
||||
{
|
||||
$templates = PromptTemplate::where('is_public', true)
|
||||
->orWhere('user_id', auth()->id())
|
||||
->get(['name', 'template_data']);
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($templates as $template) {
|
||||
$data = $template->template_data;
|
||||
$result[$template->name] = [
|
||||
'title' => $data['title'] ?? $template->name,
|
||||
'objective' => $data['objective'] ?? '',
|
||||
'context' => $data['context'] ?? '',
|
||||
'persona' => $data['persona'] ?? '',
|
||||
'tone' => $data['tone'] ?? '',
|
||||
'constraints' => $data['constraints'] ?? '',
|
||||
'output_format' => $data['output_format'] ?? '',
|
||||
'examples' => $data['examples'] ?? '',
|
||||
'variables' => $data['variables'] ?? '',
|
||||
'temperature' => $data['temperature'] ?? 0.7,
|
||||
'max_tokens' => $data['max_tokens'] ?? 1024,
|
||||
];
|
||||
}
|
||||
|
||||
return response()->json($result);
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Tutorial;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\TutorialLesson;
|
||||
use App\Models\TutorialQuestion;
|
||||
use App\Models\TutorialUserProgress;
|
||||
use App\Services\TutorialService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\View\View;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
|
||||
class TutorialController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly TutorialService $tutorialService
|
||||
) {}
|
||||
|
||||
public function index(): View
|
||||
{
|
||||
$lessons = TutorialLesson::orderBy('order')->get();
|
||||
$progress = TutorialUserProgress::where('user_id', Auth::id())->first();
|
||||
|
||||
return view('tutorial.index', compact('lessons', 'progress'));
|
||||
}
|
||||
|
||||
public function lesson(int $id): View
|
||||
{
|
||||
$lesson = TutorialLesson::findOrFail($id);
|
||||
$lessons = TutorialLesson::orderBy('order')->get();
|
||||
$progress = TutorialUserProgress::where('user_id', Auth::id())->first();
|
||||
|
||||
$completedLessons = $progress?->completed_lessons ?? [];
|
||||
if (!in_array($id, $completedLessons)) {
|
||||
$completedLessons[] = $id;
|
||||
if (!$progress) {
|
||||
$progress = TutorialUserProgress::create(['user_id' => Auth::id(), 'completed_lessons' => $completedLessons]);
|
||||
} else {
|
||||
$progress->update(['completed_lessons' => $completedLessons]);
|
||||
}
|
||||
}
|
||||
|
||||
return view('tutorial.lesson', compact('lesson', 'lessons', 'progress'));
|
||||
}
|
||||
|
||||
public function quiz(): View
|
||||
{
|
||||
$questions = TutorialQuestion::orderBy('order')->get();
|
||||
$progress = TutorialUserProgress::where('user_id', Auth::id())->first();
|
||||
|
||||
return view('tutorial.quiz', compact('questions', 'progress'));
|
||||
}
|
||||
|
||||
public function submitQuiz(Request $request): View
|
||||
{
|
||||
$request->validate([
|
||||
'answers' => 'required|array',
|
||||
'answers.*' => 'required|string',
|
||||
]);
|
||||
|
||||
$questions = TutorialQuestion::orderBy('order')->get();
|
||||
$score = 0;
|
||||
$answers = $request->input('answers', []);
|
||||
$results = [];
|
||||
|
||||
foreach ($questions as $question) {
|
||||
$isCorrect = isset($answers[$question->id]) && $answers[$question->id] === $question->correct_answer;
|
||||
if ($isCorrect) {
|
||||
$score++;
|
||||
}
|
||||
$results[] = [
|
||||
'question' => $question->question,
|
||||
'your_answer' => $answers[$question->id] ?? null,
|
||||
'correct_answer' => $question->correct_answer,
|
||||
'is_correct' => $isCorrect,
|
||||
'explanation' => $question->explanation,
|
||||
];
|
||||
}
|
||||
|
||||
$passed = $score >= 6;
|
||||
|
||||
$progress = TutorialUserProgress::where('user_id', Auth::id())->first();
|
||||
if (!$progress) {
|
||||
$progress = TutorialUserProgress::create(['user_id' => Auth::id()]);
|
||||
}
|
||||
|
||||
$progress->update([
|
||||
'quiz_answers' => $answers,
|
||||
'quiz_score' => $score,
|
||||
'quiz_passed' => $passed,
|
||||
'quiz_completed_at' => now(),
|
||||
]);
|
||||
|
||||
return view('tutorial.result', compact('score', 'results', 'passed', 'progress'));
|
||||
}
|
||||
|
||||
public function certificate(): \Illuminate\Http\Response
|
||||
{
|
||||
$user = Auth::user();
|
||||
$progress = TutorialUserProgress::where('user_id', $user->id)->firstOrFail();
|
||||
|
||||
if (!$progress->quiz_passed) {
|
||||
return redirect()->route('tutorial.quiz')->with('error', 'Você precisa passar no quiz para gerar o certificado.');
|
||||
}
|
||||
|
||||
$progress->update(['certificate_generated_at' => now()]);
|
||||
|
||||
$pdf = Pdf::loadView('tutorial.certificate', compact('user', 'progress'));
|
||||
$pdf->setPaper('a4', 'landscape');
|
||||
|
||||
return $pdf->download('certificado-engenharia-prompt-gprompt.pdf');
|
||||
}
|
||||
}
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware(['auth', 'verified', 'user.approved']);
|
||||
}
|
||||
|
||||
public function index(): View
|
||||
{
|
||||
$this->authorize('viewAny', User::class);
|
||||
|
||||
$users = User::query()
|
||||
->select(['id', 'name', 'email', 'role', 'is_approved', 'created_at'])
|
||||
->orderByDesc('created_at')
|
||||
->paginate(15);
|
||||
|
||||
$users->each(function ($user) {
|
||||
$user->tokens_used = $user->generations()
|
||||
->where('status', 'completed')
|
||||
->sum('tokens_used') ?? 0;
|
||||
|
||||
$user->generations_count = $user->generations()->count();
|
||||
});
|
||||
|
||||
return view('users.index', compact('users'));
|
||||
}
|
||||
|
||||
public function edit(User $user): View
|
||||
{
|
||||
$this->authorize('update', $user);
|
||||
|
||||
return view('users.edit', compact('user'));
|
||||
}
|
||||
|
||||
public function update(Request $request, User $user): RedirectResponse
|
||||
{
|
||||
$this->authorize('update', $user);
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'email' => 'required|email|max:255|unique:users,email,' . $user->id,
|
||||
'role' => 'required|in:superadmin,user',
|
||||
'is_approved' => 'required|boolean',
|
||||
'password' => 'nullable|string|min:6|confirmed',
|
||||
]);
|
||||
|
||||
if (empty($validated['password'])) {
|
||||
unset($validated['password']);
|
||||
}
|
||||
|
||||
$user->update($validated);
|
||||
|
||||
return redirect()->route('users.index')
|
||||
->with('success', 'Usuário atualizado com sucesso.');
|
||||
}
|
||||
|
||||
public function destroy(User $user): RedirectResponse
|
||||
{
|
||||
$this->authorize('delete', $user);
|
||||
|
||||
if ($user->id === auth()->id()) {
|
||||
return redirect()->route('users.index')
|
||||
->with('error', 'Você não pode excluir a si mesmo.');
|
||||
}
|
||||
|
||||
$user->delete();
|
||||
|
||||
return redirect()->route('users.index')
|
||||
->with('success', 'Usuário excluído com sucesso.');
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\LegalDocument;
|
||||
use App\Models\UserDocumentAcceptance;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class EnsureDocumentsAccepted
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
if (Auth::check() && Auth::user()->role !== 'superadmin') {
|
||||
$user = Auth::user();
|
||||
|
||||
$documents = LegalDocument::all()->groupBy('type');
|
||||
|
||||
foreach ($documents as $type => $docs) {
|
||||
$latest = $docs->sortByDesc('version')->first();
|
||||
|
||||
if (!$latest) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$accepted = UserDocumentAcceptance::where('user_id', $user->id)
|
||||
->where('legal_document_id', $latest->id)
|
||||
->exists();
|
||||
|
||||
if (!$accepted) {
|
||||
return redirect()->route('legal.document.show', $latest->type)
|
||||
->with('warning', 'Você precisa aceitar a versão atual dos documentos legais para continuar.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class EnsureUserIsApproved
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Closure(Request): Response $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (Auth::check() && !Auth::user()->is_approved && Auth::user()->role !== 'superadmin') {
|
||||
Auth::logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
|
||||
return redirect()->route('login')
|
||||
->with('error', 'Sua conta está aguardando aprovação do SuperAdmin.');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\AiProvider;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreAiProviderRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255', Rule::unique('ai_providers', 'name')->whereNull('deleted_at')],
|
||||
'slug' => ['required', 'string', 'max:255', Rule::unique('ai_providers', 'slug')->whereNull('deleted_at')],
|
||||
'description' => ['nullable', 'string'],
|
||||
'api_key' => ['required', 'string', 'max:255'],
|
||||
'base_url' => ['required', 'url', 'max:255'],
|
||||
'is_active' => ['boolean'],
|
||||
'config' => ['nullable', 'array'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\AiProvider;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateAiProviderRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['sometimes', 'string', 'max:255', Rule::unique('ai_providers', 'name')->ignore($this->route('ai_provider'))],
|
||||
'slug' => ['sometimes', 'string', 'max:255', Rule::unique('ai_providers', 'slug')->ignore($this->route('ai_provider'))],
|
||||
'description' => ['nullable', 'string'],
|
||||
'api_key' => ['sometimes', 'string', 'max:255'],
|
||||
'base_url' => ['sometimes', 'url', 'max:255'],
|
||||
'is_active' => ['boolean'],
|
||||
'config' => ['nullable', 'array'],
|
||||
];
|
||||
}
|
||||
}
|
||||
Executable
+126
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Auth;
|
||||
|
||||
use App\Rules\Captcha;
|
||||
use Illuminate\Auth\Events\Lockout;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
/**
|
||||
* Form request for user login with arithmetic captcha validation.
|
||||
*/
|
||||
class LoginRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'email' => ['required', 'string', 'email'],
|
||||
'password' => ['required', 'string'],
|
||||
'captcha' => ['required', 'string', 'max:4', new Captcha()],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'email.required' => 'O e-mail é obrigatório.',
|
||||
'email.email' => 'Informe um e-mail válido.',
|
||||
'password.required' => 'A senha é obrigatória.',
|
||||
'captcha.required' => 'O captcha é obrigatório.',
|
||||
'captcha.size' => 'O captcha deve ter :size caracteres.',
|
||||
'captcha' => 'O captcha informado está incorreto.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'email' => 'e-mail',
|
||||
'password' => 'senha',
|
||||
'captcha' => 'captcha',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to authenticate the request's credentials.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function authenticate(): void
|
||||
{
|
||||
$this->ensureIsNotRateLimited();
|
||||
|
||||
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
|
||||
RateLimiter::hit($this->throttleKey());
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => trans('auth.failed'),
|
||||
]);
|
||||
}
|
||||
|
||||
RateLimiter::clear($this->throttleKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the login request is not rate limited.
|
||||
*
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function ensureIsNotRateLimited(): void
|
||||
{
|
||||
if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) {
|
||||
return;
|
||||
}
|
||||
|
||||
event(new Lockout($this));
|
||||
|
||||
$seconds = RateLimiter::availableIn($this->throttleKey());
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'email' => trans('auth.throttle', [
|
||||
'seconds' => $seconds,
|
||||
'minutes' => ceil($seconds / 60),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the rate limiting throttle key for the request.
|
||||
*/
|
||||
public function throttleKey(): string
|
||||
{
|
||||
return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip());
|
||||
}
|
||||
}
|
||||
Executable
+84
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Auth;
|
||||
|
||||
use App\Rules\Captcha;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
/**
|
||||
* Form request for user registration with arithmetic captcha validation.
|
||||
*/
|
||||
class RegisterRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:users,email'],
|
||||
'password' => ['required', 'confirmed', Password::defaults()],
|
||||
'captcha' => ['required', 'string', 'max:4', new Captcha()],
|
||||
'terms_of_service' => ['required', 'accepted'],
|
||||
'privacy_policy' => ['required', 'accepted'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.required' => 'O nome é obrigatório.',
|
||||
'name.max' => 'O nome não pode ultrapassar 255 caracteres.',
|
||||
'email.required' => 'O e-mail é obrigatório.',
|
||||
'email.email' => 'Informe um e-mail válido.',
|
||||
'email.unique' => 'Este e-mail já está cadastrado.',
|
||||
'password.required' => 'A senha é obrigatória.',
|
||||
'password.confirmed' => 'A confirmação de senha não confere.',
|
||||
'captcha.required' => 'O captcha é obrigatório.',
|
||||
'captcha.size' => 'O captcha deve ter :size caracteres.',
|
||||
'captcha' => 'O captcha informado está incorreto.',
|
||||
'terms_of_service.accepted' => 'Você deve aceitar os Termos de Uso para continuar.',
|
||||
'privacy_policy.accepted' => 'Você deve aceitar a Política de Privacidade para continuar.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'nome',
|
||||
'email' => 'e-mail',
|
||||
'password' => 'senha',
|
||||
'password_confirmation' => 'confirmação de senha',
|
||||
'captcha' => 'captcha',
|
||||
'terms_of_service' => 'Termos de Uso',
|
||||
'privacy_policy' => 'Política de Privacidade',
|
||||
];
|
||||
}
|
||||
}
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ProfileUpdateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
'lowercase',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique(User::class)->ignore($this->user()->id),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\PromptGeneration;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class GeneratePromptRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'project_id' => ['required', 'exists:prompt_projects,id'],
|
||||
'provider_id' => ['required', 'exists:ai_providers,id'],
|
||||
'model_id' => ['required', 'exists:ai_models,id'],
|
||||
'temperature' => ['nullable', 'numeric', 'min:0', 'max:1'],
|
||||
'max_tokens' => ['nullable', 'integer', 'min:1'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\PromptProject;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StorePromptProjectRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'title' => ['required', 'string', 'max:255'],
|
||||
'company_name' => ['nullable', 'string', 'max:255'],
|
||||
'company_address' => ['nullable', 'string'],
|
||||
'company_whatsapp' => ['nullable', 'string', 'max:255'],
|
||||
'company_website' => ['nullable', 'url', 'max:255'],
|
||||
'company_social_media' => ['nullable', 'string'],
|
||||
'company_business_activities' => ['nullable', 'string'],
|
||||
'company_services_products' => ['nullable', 'string'],
|
||||
'objective' => ['required', 'string'],
|
||||
'context' => ['nullable', 'string'],
|
||||
'persona' => ['nullable', 'string', 'max:255'],
|
||||
'tone' => ['nullable', 'string', 'max:255'],
|
||||
'constraints' => ['nullable', 'string'],
|
||||
'output_format' => ['nullable', 'string'],
|
||||
'examples' => ['nullable', 'string'],
|
||||
'variables' => ['nullable', 'string'],
|
||||
'temperature' => ['nullable', 'numeric', 'min:0', 'max:1'],
|
||||
'max_tokens' => ['nullable', 'integer', 'min:1'],
|
||||
'provider_id' => ['nullable', 'exists:ai_providers,id'],
|
||||
'model_id' => ['nullable', 'exists:ai_models,id'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\PromptProject;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdatePromptProjectRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'title' => ['sometimes', 'string', 'max:255'],
|
||||
'company_name' => ['nullable', 'string', 'max:255'],
|
||||
'company_address' => ['nullable', 'string'],
|
||||
'company_whatsapp' => ['nullable', 'string', 'max:255'],
|
||||
'company_website' => ['nullable', 'url', 'max:255'],
|
||||
'company_social_media' => ['nullable', 'string'],
|
||||
'company_business_activities' => ['nullable', 'string'],
|
||||
'company_services_products' => ['nullable', 'string'],
|
||||
'objective' => ['sometimes', 'string'],
|
||||
'context' => ['nullable', 'string'],
|
||||
'persona' => ['nullable', 'string', 'max:255'],
|
||||
'tone' => ['nullable', 'string', 'max:255'],
|
||||
'constraints' => ['nullable', 'string'],
|
||||
'output_format' => ['nullable', 'string'],
|
||||
'examples' => ['nullable', 'string'],
|
||||
'variables' => ['nullable', 'string'],
|
||||
'temperature' => ['nullable', 'numeric', 'min:0', 'max:1'],
|
||||
'max_tokens' => ['nullable', 'integer', 'min:1'],
|
||||
'provider_id' => ['nullable', 'exists:ai_providers,id'],
|
||||
'model_id' => ['nullable', 'exists:ai_models,id'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\PromptTemplate;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StorePromptTemplateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'description' => ['nullable', 'string', 'max:1000'],
|
||||
'category' => ['nullable', 'string', 'max:255'],
|
||||
'template_data' => ['required', 'string'],
|
||||
'is_public' => ['boolean'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\PromptTemplate;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdatePromptTemplateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'description' => ['nullable', 'string', 'max:1000'],
|
||||
'category' => ['nullable', 'string', 'max:255'],
|
||||
'template_data' => ['required', 'string'],
|
||||
'is_public' => ['boolean'],
|
||||
];
|
||||
}
|
||||
}
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class AiModel extends Model
|
||||
{
|
||||
use \Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'provider_id',
|
||||
'name',
|
||||
'slug',
|
||||
'max_tokens',
|
||||
'temperature_support',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'temperature_support' => 'boolean',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the AI provider that owns the model.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\AiProvider, \App\Models\AiModel>
|
||||
*/
|
||||
public function provider(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(AiProvider::class);
|
||||
}
|
||||
}
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class AiProvider extends Model
|
||||
{
|
||||
use \Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
'api_key',
|
||||
'base_url',
|
||||
'is_active',
|
||||
'config',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'is_active' => 'boolean',
|
||||
'config' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the models for the AI provider.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany<\App\Models\AiModel>
|
||||
*/
|
||||
public function models(): HasMany
|
||||
{
|
||||
return $this->hasMany(AiModel::class, 'provider_id');
|
||||
}
|
||||
}
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class CookieConsent extends Model
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'session_id',
|
||||
'ip_address',
|
||||
'user_agent',
|
||||
'consents',
|
||||
'accepted_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'consents' => 'array',
|
||||
'accepted_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user that owns the cookie consent.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\User, \App\Models\CookieConsent>
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class LegalDocument extends Model
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'type',
|
||||
'title',
|
||||
'content',
|
||||
'version',
|
||||
'published_at',
|
||||
'published_by',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'published_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user that published the document.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\User, \App\Models\LegalDocument>
|
||||
*/
|
||||
public function publishedBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'published_by');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user document acceptances for the legal document.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany<\App\Models\UserDocumentAcceptance, \App\Models\LegalDocument>
|
||||
*/
|
||||
public function acceptances(): HasMany
|
||||
{
|
||||
return $this->hasMany(UserDocumentAcceptance::class);
|
||||
}
|
||||
}
|
||||
Executable
+87
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class PromptGeneration extends Model
|
||||
{
|
||||
use \Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'project_id',
|
||||
'user_id',
|
||||
'provider_id',
|
||||
'model_id',
|
||||
'generated_prompt',
|
||||
'input_data',
|
||||
'output_data',
|
||||
'tokens_used',
|
||||
'generation_time',
|
||||
'status',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'input_data' => 'array',
|
||||
'output_data' => 'array',
|
||||
'tokens_used' => 'integer',
|
||||
'generation_time' => 'decimal:3',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the prompt project that owns the generation.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\PromptProject, \App\Models\PromptGeneration>
|
||||
*/
|
||||
public function project(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PromptProject::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user that owns the generation.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\User, \App\Models\PromptGeneration>
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the AI provider for the generation.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\AiProvider, \App\Models\PromptGeneration>
|
||||
*/
|
||||
public function provider(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(AiProvider::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the AI model for the generation.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\AiModel, \App\Models\PromptGeneration>
|
||||
*/
|
||||
public function model(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(AiModel::class);
|
||||
}
|
||||
}
|
||||
Executable
+100
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class PromptProject extends Model
|
||||
{
|
||||
use \Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'title',
|
||||
'company_name',
|
||||
'company_address',
|
||||
'company_whatsapp',
|
||||
'company_website',
|
||||
'company_social_media',
|
||||
'company_business_activities',
|
||||
'company_services_products',
|
||||
'objective',
|
||||
'context',
|
||||
'persona',
|
||||
'tone',
|
||||
'constraints',
|
||||
'output_format',
|
||||
'examples',
|
||||
'variables',
|
||||
'temperature',
|
||||
'max_tokens',
|
||||
'provider_id',
|
||||
'model_id',
|
||||
'generated_prompt',
|
||||
'status',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'examples' => 'array',
|
||||
'variables' => 'array',
|
||||
'temperature' => 'decimal:2',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user that owns the prompt project.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\User, \App\Models\PromptProject>
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the AI provider for the prompt project.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\AiProvider, \App\Models\PromptProject>
|
||||
*/
|
||||
public function provider(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(AiProvider::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the AI model for the prompt project.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\AiModel, \App\Models\PromptProject>
|
||||
*/
|
||||
public function model(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(AiModel::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the generations for the prompt project.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany<\App\Models\PromptGeneration>
|
||||
*/
|
||||
public function generations(): HasMany
|
||||
{
|
||||
return $this->hasMany(PromptGeneration::class);
|
||||
}
|
||||
}
|
||||
Executable
+51
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class PromptTemplate extends Model
|
||||
{
|
||||
use \Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'description',
|
||||
'category',
|
||||
'template_data',
|
||||
'is_public',
|
||||
'user_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'is_public' => 'boolean',
|
||||
'template_data' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user that owns the prompt template.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\User, \App\Models\PromptTemplate>
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class TutorialLesson extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'content',
|
||||
'example_bad',
|
||||
'example_good',
|
||||
'order',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'order' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
public function questions(): HasMany
|
||||
{
|
||||
return $this->hasMany(TutorialQuestion::class);
|
||||
}
|
||||
}
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class TutorialQuestion extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'question',
|
||||
'options',
|
||||
'correct_answer',
|
||||
'explanation',
|
||||
'order',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'options' => 'array',
|
||||
'order' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
public function lesson(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(TutorialLesson::class);
|
||||
}
|
||||
}
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class TutorialUserProgress extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'completed_lessons',
|
||||
'quiz_answers',
|
||||
'quiz_score',
|
||||
'quiz_passed',
|
||||
'quiz_completed_at',
|
||||
'certificate_generated_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'completed_lessons' => 'array',
|
||||
'quiz_answers' => 'array',
|
||||
'quiz_score' => 'integer',
|
||||
'quiz_passed' => 'boolean',
|
||||
'quiz_completed_at' => 'datetime',
|
||||
'certificate_generated_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
#[Fillable(['name', 'email', 'password', 'role', 'is_approved', 'avatar'])]
|
||||
#[Hidden(['password', 'remember_token'])]
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'is_approved' => 'boolean',
|
||||
'role' => 'string',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the prompt projects for the user.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany<\App\Models\PromptProject>
|
||||
*/
|
||||
public function projects(): HasMany
|
||||
{
|
||||
return $this->hasMany(PromptProject::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the prompt generations for the user.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany<\App\Models\PromptGeneration>
|
||||
*/
|
||||
public function generations(): HasMany
|
||||
{
|
||||
return $this->hasMany(PromptGeneration::class);
|
||||
}
|
||||
}
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class UserDocumentAcceptance extends Model
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'legal_document_id',
|
||||
'document_version',
|
||||
'ip_address',
|
||||
'user_agent',
|
||||
'accepted_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'accepted_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user that accepted the document.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\User, \App\Models\UserDocumentAcceptance>
|
||||
*/
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the legal document that was accepted.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\LegalDocument, \App\Models\UserDocumentAcceptance>
|
||||
*/
|
||||
public function document(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(LegalDocument::class, 'legal_document_id');
|
||||
}
|
||||
}
|
||||
Executable
+82
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\AiProvider;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
|
||||
class AiProviderPolicy
|
||||
{
|
||||
/**
|
||||
* Determine whether the user can view any AI providers.
|
||||
*/
|
||||
public function viewAny(User $user): Response
|
||||
{
|
||||
return $user->role === 'superadmin'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to view AI providers.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the AI provider.
|
||||
*/
|
||||
public function view(User $user, AiProvider $aiProvider): Response
|
||||
{
|
||||
return $user->role === 'superadmin'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to view this AI provider.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create AI providers.
|
||||
*/
|
||||
public function create(User $user): Response
|
||||
{
|
||||
return $user->role === 'superadmin'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to create AI providers.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the AI provider.
|
||||
*/
|
||||
public function update(User $user, AiProvider $aiProvider): Response
|
||||
{
|
||||
return $user->role === 'superadmin'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to update this AI provider.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the AI provider.
|
||||
*/
|
||||
public function delete(User $user, AiProvider $aiProvider): Response
|
||||
{
|
||||
return $user->role === 'superadmin'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to delete this AI provider.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the AI provider.
|
||||
*/
|
||||
public function restore(User $user, AiProvider $aiProvider): Response
|
||||
{
|
||||
return $user->role === 'superadmin'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to restore this AI provider.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the AI provider.
|
||||
*/
|
||||
public function forceDelete(User $user, AiProvider $aiProvider): Response
|
||||
{
|
||||
return $user->role === 'superadmin'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to permanently delete this AI provider.');
|
||||
}
|
||||
}
|
||||
Executable
+69
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\LegalDocument;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
|
||||
class LegalDocumentPolicy
|
||||
{
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*/
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return $user->role === 'superadmin';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the model.
|
||||
*/
|
||||
public function view(User $user, LegalDocument $document): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*/
|
||||
public function create(User $user): bool
|
||||
{
|
||||
return $user->role === 'superadmin';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*/
|
||||
public function update(User $user, LegalDocument $document): bool
|
||||
{
|
||||
return $user->role === 'superadmin';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*/
|
||||
public function delete(User $user, LegalDocument $document): bool
|
||||
{
|
||||
return $user->role === 'superadmin';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the model.
|
||||
*/
|
||||
public function restore(User $user, LegalDocument $document): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the model.
|
||||
*/
|
||||
public function forceDelete(User $user, LegalDocument $document): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\PromptGeneration;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
|
||||
class PromptGenerationPolicy
|
||||
{
|
||||
/**
|
||||
* Determine whether the user can view any prompt generations.
|
||||
*/
|
||||
public function viewAny(User $user): Response
|
||||
{
|
||||
return in_array($user->role, ['superadmin', 'user'], true)
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to view prompt generations.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the prompt generation.
|
||||
*/
|
||||
public function view(User $user, PromptGeneration $promptGeneration): Response
|
||||
{
|
||||
return $user->role === 'superadmin' || $promptGeneration->user_id === $user->id
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to view this prompt generation.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create prompt generations.
|
||||
*/
|
||||
public function create(User $user): Response
|
||||
{
|
||||
return $user->role === 'superadmin' || $user->role === 'user'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to create prompt generations.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the prompt generation.
|
||||
*/
|
||||
public function delete(User $user, PromptGeneration $promptGeneration): Response
|
||||
{
|
||||
return $user->role === 'superadmin' || $promptGeneration->user_id === $user->id
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to delete this prompt generation.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the prompt generation.
|
||||
*/
|
||||
public function restore(User $user, PromptGeneration $promptGeneration): Response
|
||||
{
|
||||
return $user->role === 'superadmin' || $promptGeneration->user_id === $user->id
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to restore this prompt generation.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the prompt generation.
|
||||
*/
|
||||
public function forceDelete(User $user, PromptGeneration $promptGeneration): Response
|
||||
{
|
||||
return $user->role === 'superadmin'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to permanently delete this prompt generation.');
|
||||
}
|
||||
}
|
||||
Executable
+82
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\PromptProject;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
|
||||
class PromptProjectPolicy
|
||||
{
|
||||
/**
|
||||
* Determine whether the user can view any prompt projects.
|
||||
*/
|
||||
public function viewAny(User $user): Response
|
||||
{
|
||||
return in_array($user->role, ['superadmin', 'user'], true)
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to view prompt projects.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the prompt project.
|
||||
*/
|
||||
public function view(User $user, PromptProject $promptProject): Response
|
||||
{
|
||||
return $user->role === 'superadmin' || $promptProject->user_id === $user->id
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to view this prompt project.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create prompt projects.
|
||||
*/
|
||||
public function create(User $user): Response
|
||||
{
|
||||
return $user->role === 'superadmin' || $user->role === 'user'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to create prompt projects.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the prompt project.
|
||||
*/
|
||||
public function update(User $user, PromptProject $promptProject): Response
|
||||
{
|
||||
return $user->role === 'superadmin' || $promptProject->user_id === $user->id
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to update this prompt project.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the prompt project.
|
||||
*/
|
||||
public function delete(User $user, PromptProject $promptProject): Response
|
||||
{
|
||||
return $user->role === 'superadmin' || $promptProject->user_id === $user->id
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to delete this prompt project.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the prompt project.
|
||||
*/
|
||||
public function restore(User $user, PromptProject $promptProject): Response
|
||||
{
|
||||
return $user->role === 'superadmin' || $promptProject->user_id === $user->id
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to restore this prompt project.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the prompt project.
|
||||
*/
|
||||
public function forceDelete(User $user, PromptProject $promptProject): Response
|
||||
{
|
||||
return $user->role === 'superadmin'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to permanently delete this prompt project.');
|
||||
}
|
||||
}
|
||||
Executable
+82
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\PromptTemplate;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
|
||||
class PromptTemplatePolicy
|
||||
{
|
||||
/**
|
||||
* Determine whether the user can view any prompt templates.
|
||||
*/
|
||||
public function viewAny(User $user): Response
|
||||
{
|
||||
return $user->role === 'superadmin'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to view prompt templates.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the prompt template.
|
||||
*/
|
||||
public function view(User $user, PromptTemplate $promptTemplate): Response
|
||||
{
|
||||
return $user->role === 'superadmin' || $promptTemplate->user_id === $user->id
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to view this prompt template.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create prompt templates.
|
||||
*/
|
||||
public function create(User $user): Response
|
||||
{
|
||||
return $user->role === 'superadmin' || $user->role === 'user'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to create prompt templates.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the prompt template.
|
||||
*/
|
||||
public function update(User $user, PromptTemplate $promptTemplate): Response
|
||||
{
|
||||
return $user->role === 'superadmin' || $promptTemplate->user_id === $user->id
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to update this prompt template.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the prompt template.
|
||||
*/
|
||||
public function delete(User $user, PromptTemplate $promptTemplate): Response
|
||||
{
|
||||
return $user->role === 'superadmin' || $promptTemplate->user_id === $user->id
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to delete this prompt template.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the prompt template.
|
||||
*/
|
||||
public function restore(User $user, PromptTemplate $promptTemplate): Response
|
||||
{
|
||||
return $user->role === 'superadmin' || $promptTemplate->user_id === $user->id
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to restore this prompt template.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the prompt template.
|
||||
*/
|
||||
public function forceDelete(User $user, PromptTemplate $promptTemplate): Response
|
||||
{
|
||||
return $user->role === 'superadmin'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to permanently delete this prompt template.');
|
||||
}
|
||||
}
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
|
||||
class UserPolicy
|
||||
{
|
||||
public function viewAny(User $user): Response
|
||||
{
|
||||
return $user->role === 'superadmin'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to view users.');
|
||||
}
|
||||
|
||||
public function view(User $user, User $target): Response
|
||||
{
|
||||
return $user->role === 'superadmin'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to view this user.');
|
||||
}
|
||||
|
||||
public function create(User $user): Response
|
||||
{
|
||||
return $user->role === 'superadmin'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to create users.');
|
||||
}
|
||||
|
||||
public function update(User $user, User $target): Response
|
||||
{
|
||||
return $user->role === 'superadmin'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to update this user.');
|
||||
}
|
||||
|
||||
public function delete(User $user, User $target): Response
|
||||
{
|
||||
return $user->role === 'superadmin'
|
||||
? Response::allow()
|
||||
: Response::deny('You do not have permission to delete this user.');
|
||||
}
|
||||
}
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\AiProvider;
|
||||
use App\Models\LegalDocument;
|
||||
use App\Models\PromptGeneration;
|
||||
use App\Models\PromptProject;
|
||||
use App\Models\PromptTemplate;
|
||||
use App\Models\User;
|
||||
use App\Policies\AiProviderPolicy;
|
||||
use App\Policies\LegalDocumentPolicy;
|
||||
use App\Policies\PromptGenerationPolicy;
|
||||
use App\Policies\PromptProjectPolicy;
|
||||
use App\Policies\PromptTemplatePolicy;
|
||||
use App\Policies\UserPolicy;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any authentication / authorization services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$this->registerPolicies();
|
||||
|
||||
Gate::define('superadmin', function (User $user) {
|
||||
return $user->role === 'superadmin';
|
||||
});
|
||||
|
||||
Gate::before(function (User $user, string $ability) {
|
||||
if ($user->role === 'superadmin') {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
Gate::resource(PromptProject::class, PromptProjectPolicy::class);
|
||||
Gate::resource(PromptGeneration::class, PromptGenerationPolicy::class);
|
||||
Gate::resource(AiProvider::class, AiProviderPolicy::class);
|
||||
Gate::resource(User::class, UserPolicy::class);
|
||||
Gate::resource(PromptTemplate::class, PromptTemplatePolicy::class);
|
||||
Gate::resource(LegalDocument::class, LegalDocumentPolicy::class);
|
||||
}
|
||||
}
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Contracts\Validation\ValidatorAwareRule;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
/**
|
||||
* Validation rule for arithmetic captcha.
|
||||
*
|
||||
* Validates against session-stored answer (preferred) or input-hidden fallback.
|
||||
* Expected input field: captcha (4 characters from arithmetic expression).
|
||||
*/
|
||||
class Captcha implements ValidationRule, ValidatorAwareRule
|
||||
{
|
||||
/**
|
||||
* Run the validation rule.
|
||||
*
|
||||
* @param Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
$expected = session('captcha_answer');
|
||||
|
||||
if ($expected === null) {
|
||||
$fail('O captcha expirou. Recarregue a página.');
|
||||
return;
|
||||
}
|
||||
|
||||
if ((string) $value !== (string) $expected) {
|
||||
$fail('O captcha informado está incorreto.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the validator instance.
|
||||
*/
|
||||
public function setValidator(Validator $validator): void
|
||||
{
|
||||
// No-op: rule does not require validator instance.
|
||||
}
|
||||
}
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Ai;
|
||||
|
||||
use App\Models\AiProvider;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class AiServiceFactory
|
||||
{
|
||||
/**
|
||||
* Create an AI service instance based on the provider.
|
||||
*
|
||||
* @param AiProvider|string $provider
|
||||
* @return AiServiceInterface
|
||||
*/
|
||||
public static function make(AiProvider|string $provider): AiServiceInterface
|
||||
{
|
||||
if (is_string($provider)) {
|
||||
$provider = AiProvider::withTrashed()->where('slug', $provider)
|
||||
->firstOrFail();
|
||||
}
|
||||
|
||||
if (!$provider->is_active) {
|
||||
throw new InvalidArgumentException('AI provider is not active.');
|
||||
}
|
||||
|
||||
return match ($provider->slug) {
|
||||
'openrouter', 'openrouter-test' => new OpenRouterService($provider),
|
||||
'stepfun' => new StepFunService($provider),
|
||||
'llamacpp' => new LlamaCppService($provider),
|
||||
default => throw new InvalidArgumentException("Unsupported AI provider: {$provider->slug}"),
|
||||
};
|
||||
}
|
||||
}
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Ai;
|
||||
|
||||
interface AiServiceInterface
|
||||
{
|
||||
/**
|
||||
* Generate a prompt using the AI provider.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function generatePrompt(array $data): array;
|
||||
|
||||
/**
|
||||
* Send a chat conversation to the AI provider.
|
||||
*
|
||||
* @param array<int, array{role: string, content: string}> $messages
|
||||
* @param array<string, mixed> $data
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function chat(array $messages, array $data = []): array;
|
||||
}
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Ai;
|
||||
|
||||
use App\Models\AiProvider;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class LlamaCppService implements AiServiceInterface
|
||||
{
|
||||
/**
|
||||
* @param AiProvider $provider
|
||||
*/
|
||||
public function __construct(private AiProvider $provider)
|
||||
{
|
||||
$this->client = new Client([
|
||||
'base_uri' => rtrim((string) $provider->base_url, '/') . '/',
|
||||
'timeout' => 120,
|
||||
]);
|
||||
}
|
||||
|
||||
public function chat(array $messages, array $data = []): array
|
||||
{
|
||||
$baseUrl = rtrim((string) $this->provider->base_url, '/');
|
||||
|
||||
if (empty($baseUrl)) {
|
||||
throw new \InvalidArgumentException('Llama.cpp base URL is not configured.');
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->client->post('v1/chat/completions', [
|
||||
'headers' => [
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
'json' => [
|
||||
'model' => (string) ($data['model'] ?? ''),
|
||||
'messages' => $messages,
|
||||
'temperature' => isset($data['temperature']) ? (float) $data['temperature'] : null,
|
||||
'max_tokens' => isset($data['max_tokens']) ? (int) $data['max_tokens'] : null,
|
||||
],
|
||||
]);
|
||||
|
||||
$body = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
if (!isset($body['choices'][0]['message']['content'])) {
|
||||
throw new \RuntimeException('Invalid response structure from Llama.cpp server.');
|
||||
}
|
||||
|
||||
return [
|
||||
'content' => $body['choices'][0]['message']['content'],
|
||||
'tokens_used' => $body['usage']['total_tokens'] ?? null,
|
||||
'model' => $body['model'] ?? (string) ($data['model'] ?? ''),
|
||||
];
|
||||
} catch (GuzzleException $e) {
|
||||
Log::error('Llama.cpp API error: ' . $e->getMessage());
|
||||
throw new \RuntimeException('Failed to generate prompt with Llama.cpp: ' . $e->getMessage(), (int) $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generatePrompt(array $data): array
|
||||
{
|
||||
return $this->chat(
|
||||
[['role' => 'user', 'content' => (string) ($data['prompt'] ?? '')]],
|
||||
$data
|
||||
);
|
||||
}
|
||||
}
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Ai;
|
||||
|
||||
use App\Models\AiProvider;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class OpenRouterService implements AiServiceInterface
|
||||
{
|
||||
/**
|
||||
* @param AiProvider $provider
|
||||
*/
|
||||
public function __construct(private AiProvider $provider)
|
||||
{
|
||||
$this->client = new Client([
|
||||
'base_uri' => rtrim((string) $provider->base_url, '/') . '/',
|
||||
'timeout' => 120,
|
||||
]);
|
||||
}
|
||||
|
||||
public function chat(array $messages, array $data = []): array
|
||||
{
|
||||
$apiKey = $data['api_key'] ?? $this->provider->api_key;
|
||||
|
||||
if (empty($apiKey)) {
|
||||
throw new \InvalidArgumentException('OpenRouter API key is not configured.');
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->client->post('chat/completions', [
|
||||
'headers' => [
|
||||
'Authorization' => 'Bearer ' . $apiKey,
|
||||
'Content-Type' => 'application/json',
|
||||
'HTTP-Referer' => $data['http_referer'] ?? config('app.url'),
|
||||
'X-Title' => $data['x_title'] ?? config('app.name'),
|
||||
],
|
||||
'json' => [
|
||||
'model' => (string) ($data['model'] ?? ''),
|
||||
'messages' => $messages,
|
||||
'temperature' => isset($data['temperature']) ? (float) $data['temperature'] : null,
|
||||
'max_tokens' => isset($data['max_tokens']) ? (int) $data['max_tokens'] : null,
|
||||
],
|
||||
]);
|
||||
|
||||
$body = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
if (!isset($body['choices'][0]['message']['content'])) {
|
||||
throw new \RuntimeException('Invalid response structure from OpenRouter.');
|
||||
}
|
||||
|
||||
return [
|
||||
'content' => $body['choices'][0]['message']['content'],
|
||||
'tokens_used' => $body['usage']['total_tokens'] ?? null,
|
||||
'model' => $body['model'] ?? (string) ($data['model'] ?? ''),
|
||||
];
|
||||
} catch (GuzzleException $e) {
|
||||
Log::error('OpenRouter API error: ' . $e->getMessage());
|
||||
throw new \RuntimeException('Failed to generate prompt with OpenRouter: ' . $e->getMessage(), (int) $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generatePrompt(array $data): array
|
||||
{
|
||||
return $this->chat(
|
||||
[['role' => 'user', 'content' => (string) ($data['prompt'] ?? '')]],
|
||||
$data
|
||||
);
|
||||
}
|
||||
}
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Ai;
|
||||
|
||||
use App\Models\AiProvider;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class StepFunService implements AiServiceInterface
|
||||
{
|
||||
/**
|
||||
* @param AiProvider $provider
|
||||
*/
|
||||
public function __construct(private AiProvider $provider)
|
||||
{
|
||||
$this->client = new Client([
|
||||
'base_uri' => rtrim((string) $provider->base_url, '/') . '/',
|
||||
'timeout' => 120,
|
||||
]);
|
||||
}
|
||||
|
||||
public function chat(array $messages, array $data = []): array
|
||||
{
|
||||
$apiKey = $data['api_key'] ?? $this->provider->api_key;
|
||||
|
||||
if (empty($apiKey)) {
|
||||
throw new \InvalidArgumentException('StepFun API key is not configured.');
|
||||
}
|
||||
|
||||
try {
|
||||
$payload = [
|
||||
'model' => (string) ($data['model'] ?? ''),
|
||||
'messages' => $messages,
|
||||
];
|
||||
|
||||
if (isset($data['temperature'])) {
|
||||
$payload['temperature'] = (float) $data['temperature'];
|
||||
}
|
||||
|
||||
if (isset($data['max_tokens'])) {
|
||||
$payload['max_tokens'] = (int) $data['max_tokens'];
|
||||
}
|
||||
|
||||
if (!empty($data['reasoning_effort'])) {
|
||||
$payload['reasoning_effort'] = (string) $data['reasoning_effort'];
|
||||
}
|
||||
|
||||
$response = $this->client->post('chat/completions', [
|
||||
'headers' => [
|
||||
'Authorization' => 'Bearer ' . $apiKey,
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
'json' => $payload,
|
||||
]);
|
||||
|
||||
$body = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
if (!isset($body['choices'][0]['message']['content'])) {
|
||||
throw new \RuntimeException('Invalid response structure from StepFun.');
|
||||
}
|
||||
|
||||
return [
|
||||
'content' => $body['choices'][0]['message']['content'],
|
||||
'tokens_used' => $body['usage']['total_tokens'] ?? null,
|
||||
'model' => $body['model'] ?? (string) ($data['model'] ?? ''),
|
||||
];
|
||||
} catch (GuzzleException $e) {
|
||||
Log::error('StepFun API error: ' . $e->getMessage());
|
||||
throw new \RuntimeException('Failed to generate prompt with StepFun: ' . $e->getMessage(), (int) $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generatePrompt(array $data): array
|
||||
{
|
||||
return $this->chat(
|
||||
[['role' => 'user', 'content' => (string) ($data['prompt'] ?? '')]],
|
||||
$data
|
||||
);
|
||||
}
|
||||
}
|
||||
Executable
+69
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\AiModel;
|
||||
use App\Models\AiProvider;
|
||||
use App\Services\Ai\AiServiceFactory;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use RuntimeException;
|
||||
|
||||
class AiAgentService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AiServiceFactory $aiServiceFactory
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Execute a prompt using the specified provider and model.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function execute(
|
||||
string $prompt,
|
||||
AiProvider $provider,
|
||||
AiModel $model,
|
||||
float $temperature = 0.7,
|
||||
int $maxTokens = 8192
|
||||
): array {
|
||||
$startTime = microtime(true);
|
||||
|
||||
$aiData = [
|
||||
'prompt' => $prompt,
|
||||
'temperature' => $temperature,
|
||||
'max_tokens' => $maxTokens,
|
||||
'model' => $model->slug,
|
||||
'api_key' => $provider->api_key,
|
||||
'base_url' => $provider->base_url,
|
||||
];
|
||||
|
||||
try {
|
||||
$service = $this->aiServiceFactory->make($provider);
|
||||
$result = $service->generatePrompt($aiData);
|
||||
} catch (ConnectionException|RuntimeException $e) {
|
||||
Log::error('AI Agent execution failed: ' . $e->getMessage());
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'content' => '',
|
||||
'tokens_used' => null,
|
||||
'generation_time' => round(microtime(true) - $startTime, 3),
|
||||
'model' => $model->slug,
|
||||
'provider' => $provider->name,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'content' => $result['content'] ?? '',
|
||||
'tokens_used' => $result['tokens_used'] ?? null,
|
||||
'generation_time' => round(microtime(true) - $startTime, 3),
|
||||
'model' => $model->slug,
|
||||
'provider' => $provider->name,
|
||||
];
|
||||
}
|
||||
}
|
||||
Executable
+71
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\AiProvider;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use RuntimeException;
|
||||
|
||||
class AiProviderService
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function index(): array
|
||||
{
|
||||
$paginator = AiProvider::with('models')
|
||||
->latest()
|
||||
->paginate(15);
|
||||
|
||||
return [
|
||||
'data' => $paginator->items(),
|
||||
'total' => $paginator->total(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
'from' => $paginator->firstItem(),
|
||||
'to' => $paginator->lastItem(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(array $data): AiProvider
|
||||
{
|
||||
return AiProvider::withTrashed()->firstOrCreate(
|
||||
['slug' => $data['slug']],
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(AiProvider $provider, array $data): AiProvider
|
||||
{
|
||||
$provider->update($data);
|
||||
|
||||
return $provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(AiProvider $provider): bool
|
||||
{
|
||||
try {
|
||||
$provider->delete();
|
||||
|
||||
return true;
|
||||
} catch (RuntimeException $e) {
|
||||
Log::error('Failed to delete AI provider: ' . $e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\AiModel;
|
||||
use App\Models\AiProvider;
|
||||
use App\Services\Ai\AiServiceFactory;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use RuntimeException;
|
||||
|
||||
class ChatService
|
||||
{
|
||||
private const SESSION_KEY = 'chat_conversation';
|
||||
|
||||
public function __construct(
|
||||
private readonly AiServiceFactory $aiServiceFactory
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get conversation history from session.
|
||||
*
|
||||
* @return array<int, array{role: string, content: string}>
|
||||
*/
|
||||
public function getHistory(): array
|
||||
{
|
||||
return session()->get(self::SESSION_KEY, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset conversation history.
|
||||
*/
|
||||
public function reset(): void
|
||||
{
|
||||
session()->forget(self::SESSION_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message and get AI response.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function sendMessage(
|
||||
string $userMessage,
|
||||
AiProvider $provider,
|
||||
AiModel $model,
|
||||
float $temperature = 0.7,
|
||||
int $maxTokens = 8192
|
||||
): array {
|
||||
$history = $this->getHistory();
|
||||
|
||||
$history[] = ['role' => 'user', 'content' => $userMessage];
|
||||
|
||||
$startTime = microtime(true);
|
||||
|
||||
try {
|
||||
$service = $this->aiServiceFactory->make($provider);
|
||||
$result = $service->chat($history, [
|
||||
'temperature' => $temperature,
|
||||
'max_tokens' => $maxTokens,
|
||||
'model' => $model->slug,
|
||||
'api_key' => $provider->api_key,
|
||||
'base_url' => $provider->base_url,
|
||||
]);
|
||||
} catch (ConnectionException|RuntimeException $e) {
|
||||
Log::error('Chat API error: ' . $e->getMessage());
|
||||
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $e->getMessage(),
|
||||
'content' => '',
|
||||
'role' => 'assistant',
|
||||
'tokens_used' => null,
|
||||
'generation_time' => round(microtime(true) - $startTime, 3),
|
||||
];
|
||||
}
|
||||
|
||||
$assistantMessage = [
|
||||
'role' => 'assistant',
|
||||
'content' => $result['content'] ?? '',
|
||||
];
|
||||
|
||||
$history[] = $assistantMessage;
|
||||
|
||||
session()->put(self::SESSION_KEY, $history);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'content' => $result['content'] ?? '',
|
||||
'role' => 'assistant',
|
||||
'tokens_used' => $result['tokens_used'] ?? null,
|
||||
'generation_time' => round(microtime(true) - $startTime, 3),
|
||||
];
|
||||
}
|
||||
}
|
||||
Executable
+210
@@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\AiModel;
|
||||
use App\Models\AiProvider;
|
||||
use App\Models\PromptGeneration;
|
||||
use App\Models\PromptProject;
|
||||
use App\Models\User;
|
||||
use App\Services\Ai\AiServiceFactory;
|
||||
use App\Services\Ai\AiServiceInterface;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use RuntimeException;
|
||||
|
||||
class PromptGenerationService
|
||||
{
|
||||
/**
|
||||
* @param AiServiceFactory $factory
|
||||
*/
|
||||
public function __construct(private AiServiceFactory $factory)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a prompt based on a prompt project.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
* @return PromptGeneration
|
||||
*/
|
||||
public function generate(array $data): PromptGeneration
|
||||
{
|
||||
$project = $data['project'] ?? null;
|
||||
$user = $data['user'] ?? null;
|
||||
|
||||
if (!$project instanceof PromptProject) {
|
||||
throw new InvalidArgumentException('Prompt project is required.');
|
||||
}
|
||||
|
||||
if (!$user instanceof User) {
|
||||
$user = auth()->user();
|
||||
}
|
||||
|
||||
if (!$user instanceof User) {
|
||||
throw new InvalidArgumentException('User is required.');
|
||||
}
|
||||
|
||||
$provider = $data['provider_id'] ?? $project->provider_id;
|
||||
$model = $data['model_id'] ?? $project->model_id;
|
||||
|
||||
if (!$provider instanceof AiProvider) {
|
||||
$provider = AiProvider::findOrFail((int) $provider);
|
||||
}
|
||||
|
||||
if (!$model instanceof AiModel) {
|
||||
$model = AiModel::findOrFail((int) $model);
|
||||
}
|
||||
|
||||
$promptText = $this->buildPrompt($project);
|
||||
|
||||
$aiData = [
|
||||
'prompt' => $promptText,
|
||||
'temperature' => (float) ($data['temperature'] ?? $project->temperature ?? 0.7),
|
||||
'max_tokens' => (int) ($data['max_tokens'] ?? $project->max_tokens ?? 1024),
|
||||
'model' => $model->slug,
|
||||
'api_key' => $provider->api_key,
|
||||
'base_url' => $provider->base_url,
|
||||
];
|
||||
|
||||
$startTime = microtime(true);
|
||||
|
||||
try {
|
||||
$service = $this->factory->make($provider);
|
||||
$result = $service->generatePrompt($aiData);
|
||||
} catch (GuzzleException|RuntimeException $e) {
|
||||
Log::error('Prompt generation failed: ' . $e->getMessage());
|
||||
|
||||
return PromptGeneration::create([
|
||||
'project_id' => $project->id,
|
||||
'user_id' => $user->id,
|
||||
'provider_id' => $provider->id,
|
||||
'model_id' => $model->id,
|
||||
'generated_prompt' => $promptText,
|
||||
'input_data' => $aiData,
|
||||
'output_data' => ['error' => $e->getMessage()],
|
||||
'tokens_used' => null,
|
||||
'generation_time' => round(microtime(true) - $startTime, 3),
|
||||
'status' => 'failed',
|
||||
]);
|
||||
}
|
||||
|
||||
$generation = PromptGeneration::create([
|
||||
'project_id' => $project->id,
|
||||
'user_id' => $user->id,
|
||||
'provider_id' => $provider->id,
|
||||
'model_id' => $model->id,
|
||||
'generated_prompt' => $promptText,
|
||||
'input_data' => $aiData,
|
||||
'output_data' => $result,
|
||||
'tokens_used' => $result['tokens_used'] ?? null,
|
||||
'generation_time' => round(microtime(true) - $startTime, 3),
|
||||
'status' => 'completed',
|
||||
]);
|
||||
|
||||
return $generation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the prompt text from project data.
|
||||
*
|
||||
* @param PromptProject $project
|
||||
* @return string
|
||||
*/
|
||||
private function buildPrompt(PromptProject $project): string
|
||||
{
|
||||
$parts = [];
|
||||
|
||||
if ($project->company_name) {
|
||||
$parts[] = "Company Name: " . $project->company_name;
|
||||
}
|
||||
|
||||
if ($project->company_address) {
|
||||
$parts[] = "Company Address: " . $project->company_address;
|
||||
}
|
||||
|
||||
if ($project->company_whatsapp) {
|
||||
$parts[] = "Company WhatsApp: " . $project->company_whatsapp;
|
||||
}
|
||||
|
||||
if ($project->company_website) {
|
||||
$parts[] = "Company Website: " . $project->company_website;
|
||||
}
|
||||
|
||||
if ($project->company_social_media) {
|
||||
$parts[] = "Company Social Media: " . $project->company_social_media;
|
||||
}
|
||||
|
||||
if ($project->company_business_activities) {
|
||||
$parts[] = "Company Business Activities: " . $project->company_business_activities;
|
||||
}
|
||||
|
||||
if ($project->company_services_products) {
|
||||
$parts[] = "Company Services/Products: " . $project->company_services_products;
|
||||
}
|
||||
|
||||
if ($project->objective) {
|
||||
$parts[] = "Objective: " . $project->objective;
|
||||
}
|
||||
|
||||
if ($project->context) {
|
||||
$parts[] = "Context: " . $project->context;
|
||||
}
|
||||
|
||||
if ($project->persona) {
|
||||
$parts[] = "Persona/Role: " . $project->persona;
|
||||
}
|
||||
|
||||
if ($project->tone) {
|
||||
$parts[] = "Tone/Style: " . $project->tone;
|
||||
}
|
||||
|
||||
if ($project->constraints) {
|
||||
$parts[] = "Constraints: " . $project->constraints;
|
||||
}
|
||||
|
||||
if ($project->output_format) {
|
||||
$parts[] = "Output Format: " . $project->output_format;
|
||||
}
|
||||
|
||||
if (!empty($project->examples)) {
|
||||
$parts[] = "Examples:\n" . json_encode($project->examples, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
if (!empty($project->variables)) {
|
||||
$parts[] = "Variables: " . json_encode($project->variables, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
return implode("\n\n", $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of prompt generations.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Pagination\Paginator
|
||||
*/
|
||||
public function index(): \Illuminate\Contracts\Pagination\Paginator
|
||||
{
|
||||
$user = auth()->user();
|
||||
|
||||
$query = PromptGeneration::query()
|
||||
->with(['project', 'user', 'provider', 'model'])
|
||||
->latest();
|
||||
|
||||
if (!$user || $user->role !== 'superadmin') {
|
||||
$query->where('user_id', auth()->id());
|
||||
}
|
||||
|
||||
return $query->paginate(15);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the specified prompt generation.
|
||||
*/
|
||||
public function destroy(PromptGeneration $generation): void
|
||||
{
|
||||
$generation->delete();
|
||||
}
|
||||
}
|
||||
Executable
+271
@@ -0,0 +1,271 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\AiModel;
|
||||
use App\Models\AiProvider;
|
||||
use App\Models\PromptGeneration;
|
||||
use App\Models\PromptProject;
|
||||
use App\Services\Ai\AiServiceFactory;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
class PromptProjectService
|
||||
{
|
||||
/**
|
||||
* Create a new service instance.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly AiServiceFactory $aiServiceFactory,
|
||||
private readonly PromptGeneration $generationModel
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function index(): array
|
||||
{
|
||||
$userId = auth()->id();
|
||||
$user = auth()->user();
|
||||
|
||||
$query = PromptProject::with(['user', 'provider', 'model'])
|
||||
->latest();
|
||||
|
||||
if (!$user || $user->role !== 'superadmin') {
|
||||
$query->where('user_id', $userId);
|
||||
}
|
||||
|
||||
$paginator = $query->paginate(15);
|
||||
|
||||
return [
|
||||
'data' => $paginator->items(),
|
||||
'total' => $paginator->total(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
'from' => $paginator->firstItem(),
|
||||
'to' => $paginator->lastItem(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(array $data): PromptProject
|
||||
{
|
||||
$data['examples'] = $this->convertToArray($data['examples'] ?? null);
|
||||
$data['variables'] = $this->convertToArray($data['variables'] ?? null);
|
||||
|
||||
return PromptProject::create($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(PromptProject $project, array $data): PromptProject
|
||||
{
|
||||
$data['examples'] = $this->convertToArray($data['examples'] ?? null);
|
||||
$data['variables'] = $this->convertToArray($data['variables'] ?? null);
|
||||
|
||||
$project->update($data);
|
||||
|
||||
return $project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert string to array.
|
||||
*/
|
||||
private function convertToArray(mixed $value): ?array
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
$value = trim($value);
|
||||
|
||||
if ($value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array_map('trim', explode(',', $value));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(PromptProject $project): bool
|
||||
{
|
||||
try {
|
||||
$project->delete();
|
||||
|
||||
return true;
|
||||
} catch (RuntimeException $e) {
|
||||
Log::error('Failed to delete prompt project: ' . $e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate prompt for the specified project.
|
||||
*/
|
||||
public function generate(PromptProject $project, mixed $model = null, ?int $userId = null): PromptGeneration
|
||||
{
|
||||
$provider = $project->provider;
|
||||
|
||||
if (!$provider instanceof AiProvider) {
|
||||
throw new InvalidArgumentException('AI provider is required for generation.');
|
||||
}
|
||||
|
||||
if (!$model) {
|
||||
$model = $project->model;
|
||||
}
|
||||
|
||||
if (!$model) {
|
||||
throw new InvalidArgumentException('AI model is required for generation.');
|
||||
}
|
||||
|
||||
if (is_string($model)) {
|
||||
$model = AiModel::firstOrCreate(
|
||||
['provider_id' => $provider->id, 'slug' => $model],
|
||||
['name' => $model, 'slug' => $model, 'is_active' => true]
|
||||
);
|
||||
}
|
||||
|
||||
$modelSlug = $model->slug;
|
||||
$modelId = $model->id;
|
||||
|
||||
$userId ??= auth()->id();
|
||||
|
||||
$promptText = $this->buildPrompt($project);
|
||||
|
||||
$aiData = [
|
||||
'prompt' => $promptText,
|
||||
'temperature' => (float) ($project->temperature ?? 0.7),
|
||||
'max_tokens' => (int) ($project->max_tokens ?? 1024),
|
||||
'model' => $modelSlug,
|
||||
'api_key' => $provider->api_key,
|
||||
'base_url' => $provider->base_url,
|
||||
];
|
||||
|
||||
$startTime = microtime(true);
|
||||
|
||||
try {
|
||||
$service = $this->aiServiceFactory->make($provider);
|
||||
$result = $service->generatePrompt($aiData);
|
||||
} catch (ConnectionException|RuntimeException $e) {
|
||||
Log::error('Prompt generation failed: ' . $e->getMessage());
|
||||
|
||||
return $this->generationModel::create([
|
||||
'project_id' => $project->id,
|
||||
'user_id' => $userId,
|
||||
'provider_id' => $provider->id,
|
||||
'model_id' => $modelId,
|
||||
'generated_prompt' => $promptText,
|
||||
'input_data' => $aiData,
|
||||
'output_data' => ['error' => $e->getMessage()],
|
||||
'tokens_used' => null,
|
||||
'generation_time' => round(microtime(true) - $startTime, 3),
|
||||
'status' => 'failed',
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->generationModel::create([
|
||||
'project_id' => $project->id,
|
||||
'user_id' => $userId,
|
||||
'provider_id' => $provider->id,
|
||||
'model_id' => $modelId,
|
||||
'generated_prompt' => $promptText,
|
||||
'input_data' => $aiData,
|
||||
'output_data' => $result,
|
||||
'tokens_used' => $result['tokens_used'] ?? null,
|
||||
'generation_time' => round(microtime(true) - $startTime, 3),
|
||||
'status' => 'completed',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the prompt text from project data.
|
||||
*/
|
||||
public function buildPrompt(PromptProject $project): string
|
||||
{
|
||||
$parts = [];
|
||||
|
||||
$parts[] = "You are an expert prompt engineering assistant. Your task is to generate a high-quality, optimized prompt based on the specifications provided below.";
|
||||
|
||||
if ($project->company_name) {
|
||||
$parts[] = "Company Name: " . $project->company_name;
|
||||
}
|
||||
|
||||
if ($project->company_address) {
|
||||
$parts[] = "Company Address: " . $project->company_address;
|
||||
}
|
||||
|
||||
if ($project->company_whatsapp) {
|
||||
$parts[] = "Company WhatsApp: " . $project->company_whatsapp;
|
||||
}
|
||||
|
||||
if ($project->company_website) {
|
||||
$parts[] = "Company Website: " . $project->company_website;
|
||||
}
|
||||
|
||||
if ($project->company_social_media) {
|
||||
$parts[] = "Company Social Media: " . $project->company_social_media;
|
||||
}
|
||||
|
||||
if ($project->company_business_activities) {
|
||||
$parts[] = "Company Business Activities: " . $project->company_business_activities;
|
||||
}
|
||||
|
||||
if ($project->company_services_products) {
|
||||
$parts[] = "Company Services/Products: " . $project->company_services_products;
|
||||
}
|
||||
|
||||
if ($project->objective) {
|
||||
$parts[] = "Objective: " . $project->objective;
|
||||
}
|
||||
|
||||
if ($project->context) {
|
||||
$parts[] = "Context: " . $project->context;
|
||||
}
|
||||
|
||||
if ($project->persona) {
|
||||
$parts[] = "Persona/Role: " . $project->persona;
|
||||
}
|
||||
|
||||
if ($project->tone) {
|
||||
$parts[] = "Tone/Style: " . $project->tone;
|
||||
}
|
||||
|
||||
if ($project->constraints) {
|
||||
$parts[] = "Constraints: " . $project->constraints;
|
||||
}
|
||||
|
||||
if ($project->output_format) {
|
||||
$parts[] = "Output Format: " . $project->output_format;
|
||||
}
|
||||
|
||||
if (!empty($project->examples)) {
|
||||
$parts[] = "Examples:\n" . json_encode($project->examples, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
if (!empty($project->variables)) {
|
||||
$parts[] = "Variables: " . json_encode($project->variables, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
$parts[] = "Generate the optimized prompt now. Return ONLY the prompt, without explanations, introductions, or additional text.";
|
||||
|
||||
return implode("\n\n", $parts);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\PromptTemplate;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use RuntimeException;
|
||||
|
||||
class PromptTemplateService
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function index(): array
|
||||
{
|
||||
$paginator = PromptTemplate::with('user')
|
||||
->latest()
|
||||
->paginate(15);
|
||||
|
||||
return [
|
||||
'data' => $paginator->items(),
|
||||
'total' => $paginator->total(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
'from' => $paginator->firstItem(),
|
||||
'to' => $paginator->lastItem(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(array $data): PromptTemplate
|
||||
{
|
||||
$data['user_id'] = auth()->id();
|
||||
|
||||
return PromptTemplate::create($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(PromptTemplate $template, array $data): PromptTemplate
|
||||
{
|
||||
$template->update($data);
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(PromptTemplate $template): bool
|
||||
{
|
||||
try {
|
||||
$template->delete();
|
||||
|
||||
return true;
|
||||
} catch (RuntimeException $e) {
|
||||
Log::error('Failed to delete prompt template: ' . $e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+141
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
class TutorialService
|
||||
{
|
||||
/**
|
||||
* Get tutorial content.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getContent(): array
|
||||
{
|
||||
return [
|
||||
'title' => 'Prompt Engineering Tutorial',
|
||||
'description' => 'Learn how to create effective prompts for AI models.',
|
||||
'sections' => [
|
||||
[
|
||||
'id' => 'introduction',
|
||||
'title' => 'Introduction',
|
||||
'content' => 'Prompt engineering is the process of designing and optimizing prompts to effectively communicate with AI language models. A well-crafted prompt can significantly improve the quality and relevance of AI-generated responses.',
|
||||
],
|
||||
[
|
||||
'id' => 'objective',
|
||||
'title' => 'Objective',
|
||||
'content' => 'Clearly define what you want the AI to achieve. Be specific about the goal and desired outcome. A clear objective helps the AI understand the task and produce more relevant results.',
|
||||
'tips' => [
|
||||
'Start with action verbs (e.g., "Write", "Analyze", "Create")',
|
||||
'Specify the desired outcome explicitly',
|
||||
'Keep it concise but complete',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'context',
|
||||
'title' => 'Context',
|
||||
'content' => 'Provide background information that helps the AI understand the situation. Context includes relevant details, constraints, and situational awareness that influence the response.',
|
||||
'tips' => [
|
||||
'Include relevant background information',
|
||||
'Mention any constraints or limitations',
|
||||
'Provide examples of the desired scenario',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'persona',
|
||||
'title' => 'Persona / Role',
|
||||
'content' => 'Define who the AI should be. Assigning a specific role or persona helps the AI adopt the appropriate tone, expertise level, and perspective for the task.',
|
||||
'tips' => [
|
||||
'Use professional roles (e.g., "Senior Developer", "Marketing Expert")',
|
||||
'Specify expertise level (e.g., "Expert", "Beginner-friendly")',
|
||||
'Combine multiple roles if needed',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'tone',
|
||||
'title' => 'Tone / Style',
|
||||
'content' => 'Specify the communication style and tone. This ensures the AI\'s response matches your expectations for formality, friendliness, and overall style.',
|
||||
'tips' => [
|
||||
'Use descriptive adjectives (e.g., "Professional", "Casual", "Technical")',
|
||||
'Specify formality level',
|
||||
'Mention any stylistic preferences',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'constraints',
|
||||
'title' => 'Constraints',
|
||||
'content' => 'Define limitations and rules the AI must follow. Constraints help prevent unwanted content and ensure the response meets specific requirements.',
|
||||
'tips' => [
|
||||
'Set word or character limits',
|
||||
'Exclude certain topics or formats',
|
||||
'Specify mandatory elements',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'output_format',
|
||||
'title' => 'Output Format',
|
||||
'content' => 'Specify the expected structure of the response. A clear format helps the AI organize the information in a way that is most useful for you.',
|
||||
'tips' => [
|
||||
'Use markdown, JSON, or plain text',
|
||||
'Specify sections and headings',
|
||||
'Include examples of the desired format',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'examples',
|
||||
'title' => 'Examples (Few-Shot)',
|
||||
'content' => 'Provide example inputs and outputs. Few-shot learning helps the AI understand the pattern and quality expected in the response.',
|
||||
'tips' => [
|
||||
'Include 2-3 high-quality examples',
|
||||
'Show both input and desired output',
|
||||
'Ensure examples cover edge cases',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'variables',
|
||||
'title' => 'Variables',
|
||||
'content' => 'Use dynamic placeholders for reusable prompts. Variables make your prompts flexible and reusable across different scenarios.',
|
||||
'tips' => [
|
||||
'Use clear variable names (e.g., {{topic}}, {{audience}})',
|
||||
'Document expected variable values',
|
||||
'Keep variable count manageable',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'temperature',
|
||||
'title' => 'Temperature',
|
||||
'content' => 'Control the randomness of the AI response. Lower values produce more deterministic and focused outputs, while higher values increase creativity and diversity.',
|
||||
'tips' => [
|
||||
'0.0 - 0.3: Factual, deterministic, focused',
|
||||
'0.4 - 0.7: Balanced, natural, versatile',
|
||||
'0.8 - 1.0: Creative, diverse, exploratory',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'max_tokens',
|
||||
'title' => 'Max Tokens',
|
||||
'content' => 'Limit the length of the AI response. Setting an appropriate token limit helps control response length and cost.',
|
||||
'tips' => [
|
||||
'Short responses: 100-300 tokens',
|
||||
'Medium responses: 500-1000 tokens',
|
||||
'Long responses: 1500+ tokens',
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => 'best_practices',
|
||||
'title' => 'Best Practices',
|
||||
'content' => 'Follow these guidelines to create effective prompts:',
|
||||
'tips' => [
|
||||
'Be specific and clear about your requirements',
|
||||
'Provide context and examples',
|
||||
'Iterate and refine your prompts',
|
||||
'Test with different models and settings',
|
||||
'Keep prompts concise but complete',
|
||||
'Use structured formats for complex tasks',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AppLayout extends Component
|
||||
{
|
||||
/**
|
||||
* Get the view / contents that represents the component.
|
||||
*/
|
||||
public function render(): View
|
||||
{
|
||||
return view('layouts.app');
|
||||
}
|
||||
}
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class GuestLayout extends Component
|
||||
{
|
||||
/**
|
||||
* Get the view / contents that represents the component.
|
||||
*/
|
||||
public function render(): View
|
||||
{
|
||||
return view('layouts.guest');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user