AI Analysis: enforce 7-day rate limit per candidate
This commit is contained in:
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
|||||||
use App\Models\Candidate;
|
use App\Models\Candidate;
|
||||||
use App\Services\AIAnalysisService;
|
use App\Services\AIAnalysisService;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
|
||||||
class AIAnalysisController extends Controller
|
class AIAnalysisController extends Controller
|
||||||
{
|
{
|
||||||
@@ -21,6 +22,16 @@ class AIAnalysisController extends Controller
|
|||||||
abort(403);
|
abort(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restriction: Une analyse tous les 7 jours maximum par candidat
|
||||||
|
if ($candidate->ai_analysis && isset($candidate->ai_analysis['analyzed_at'])) {
|
||||||
|
$lastAnalysis = Carbon::parse($candidate->ai_analysis['analyzed_at']);
|
||||||
|
if ($lastAnalysis->diffInDays(now()) < 7) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => "Une analyse a déjà été effectuée il y a moins de 7 jours. Merci de patienter avant de relancer l'IA."
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$analysis = $this->aiService->analyze($candidate, $request->provider);
|
$analysis = $this->aiService->analyze($candidate, $request->provider);
|
||||||
|
|
||||||
|
|||||||
@@ -115,8 +115,9 @@ class AIAnalysisService
|
|||||||
default => $this->callOllama($prompt),
|
default => $this->callOllama($prompt),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Inject provider name for display
|
// Inject metadata for display and tracking
|
||||||
$analysis['provider'] = $provider;
|
$analysis['provider'] = $provider;
|
||||||
|
$analysis['analyzed_at'] = now()->toIso8601String();
|
||||||
|
|
||||||
return $analysis;
|
return $analysis;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -365,14 +365,19 @@ const runAI = async () => {
|
|||||||
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-sm border border-slate-200 dark:border-slate-700 p-8 overflow-hidden relative">
|
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-sm border border-slate-200 dark:border-slate-700 p-8 overflow-hidden relative">
|
||||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8">
|
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-bold text-lg mb-4 flex items-center gap-3">
|
<h3 class="font-bold text-lg mb-4 flex items-center gap-3 flex-wrap">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-indigo-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-indigo-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9l-.707.707M12 21v-1m4.243-4.243l-.707-.707m2.828-9.9l-.707.707" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9l-.707.707M12 21v-1m4.243-4.243l-.707-.707m2.828-9.9l-.707.707" />
|
||||||
</svg>
|
</svg>
|
||||||
Analyse IA complète
|
Analyse IA complète
|
||||||
<span v-if="aiAnalysis?.provider" class="text-xs px-2 py-0.5 rounded-full bg-slate-100 text-slate-500 uppercase font-bold border border-slate-200">
|
<div class="flex items-center gap-2">
|
||||||
{{ aiAnalysis.provider }}
|
<span v-if="aiAnalysis?.provider" class="text-xs px-2 py-0.5 rounded-full bg-slate-100 text-slate-500 uppercase font-bold border border-slate-200">
|
||||||
</span>
|
{{ aiAnalysis.provider }}
|
||||||
|
</span>
|
||||||
|
<span v-if="aiAnalysis?.analyzed_at" class="text-[10px] text-slate-400 italic font-normal">
|
||||||
|
Effectuée le {{ new Date(aiAnalysis.analyzed_at).toLocaleDateString('fr-FR') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</h3>
|
</h3>
|
||||||
<p class="text-xs text-slate-400 mt-1 uppercase font-bold tracking-widest">Choisir l'IA pour l'analyse du matching</p>
|
<p class="text-xs text-slate-400 mt-1 uppercase font-bold tracking-widest">Choisir l'IA pour l'analyse du matching</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
<?php
|
|
||||||
require __DIR__.'/vendor/autoload.php';
|
|
||||||
$app = require_once __DIR__.'/bootstrap/app.php';
|
|
||||||
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
|
||||||
$kernel->bootstrap();
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
$apiKey = env('GEMINI_API_KEY');
|
|
||||||
if (!$apiKey) {
|
|
||||||
echo "API Key not found in .env\n";
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Testing Gemini models with key: " . substr($apiKey, 0, 5) . "...\n";
|
|
||||||
|
|
||||||
$response = Http::get("https://generativelanguage.googleapis.com/v1/models?key=" . $apiKey);
|
|
||||||
|
|
||||||
if ($response->successful()) {
|
|
||||||
$models = $response->json('models');
|
|
||||||
foreach ($models as $model) {
|
|
||||||
echo "- " . $model['name'] . " (" . implode(", ", $model['supportedGenerationMethods']) . ")\n";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
echo "Failed to list models: " . $response->status() . " - " . $response->body() . "\n";
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user