Initial commit

This commit is contained in:
jeremy bayse
2026-06-22 16:40:24 +02:00
commit ad127156ff
78 changed files with 13478 additions and 0 deletions
+183
View File
@@ -0,0 +1,183 @@
<?php
namespace App\Livewire;
use App\Models\Chatbot;
use App\Models\ChatbotHistory;
use App\Services\DocumentIngestionService;
use App\Services\WeaviateService;
use Livewire\Component;
use Livewire\WithFileUploads;
class AdminDashboard extends Component
{
use WithFileUploads;
public Chatbot $chatbot;
public string $url = '';
public $pdfFile;
public string $statusMessage = '';
public string $statusType = ''; // 'success', 'error', 'info'
public string $systemPrompt = '';
public string $chatbotName = '';
public array $history = [];
public bool $enableVision = true;
public bool $crawlEntireSite = false;
public int $maxPages = 30;
public function mount(Chatbot $chatbot)
{
$this->chatbot = $chatbot;
$this->systemPrompt = $chatbot->system_prompt ?? '';
$this->chatbotName = $chatbot->name;
$this->loadHistory();
}
protected function loadHistory()
{
$this->history = $this->chatbot->histories()
->latest()
->limit(15)
->get()
->map(function ($item) {
return [
'action' => $item->action,
'target' => $item->target,
'status' => $item->status,
'timestamp' => $item->created_at->format('d/m/Y H:i')
];
})->toArray();
}
public function saveSystemPrompt()
{
$this->validate([
'systemPrompt' => 'required|string|max:5000',
'chatbotName' => 'required|string|max:100',
]);
$this->chatbot->update([
'name' => $this->chatbotName,
'system_prompt' => $this->systemPrompt
]);
$this->logAction("Configuration Chatbot", "Nom modifié en '{$this->chatbotName}'", "success");
$this->statusMessage = "Les paramètres du chatbot ont été mis à jour avec succès !";
$this->statusType = 'success';
$this->dispatch('settings-updated');
}
protected function logAction(string $action, string $target, string $status)
{
$this->chatbot->histories()->create([
'action' => $action,
'target' => $target,
'status' => $status
]);
$this->loadHistory();
}
public function ingestUrl(DocumentIngestionService $ingestionService)
{
$this->validate([
'url' => 'required|url',
'maxPages' => 'required|integer|min:1|max:100',
]);
if ($this->crawlEntireSite) {
$this->statusMessage = "Exploration et indexation de l'ensemble du site en cours (cela peut prendre quelques minutes)...";
$this->statusType = 'info';
// Increase time limit for crawler execution
@set_time_limit(300);
$indexedCount = $ingestionService->crawlAndIngestSite($this->url, (string) $this->chatbot->id, $this->maxPages);
if ($indexedCount > 0) {
$this->logAction("Crawler Site Web", "{$this->url} ({$indexedCount} pages indexées)", "success");
$this->statusMessage = "Le site a été exploré et {$indexedCount} pages ont été indexées avec succès !";
$this->statusType = 'success';
$this->url = '';
} else {
$this->logAction("Crawler Site Web", $this->url, "failed");
$this->statusMessage = "L'exploration du site a échoué. Veuillez vérifier les logs et l'URL.";
$this->statusType = 'error';
}
} else {
$this->statusMessage = "Indexation de l'URL en cours...";
$this->statusType = 'info';
$success = $ingestionService->ingestUrl($this->url, (string) $this->chatbot->id);
if ($success) {
$this->logAction("Indexation URL", $this->url, "success");
$this->statusMessage = "L'URL a été indexée avec succès dans Weaviate !";
$this->statusType = 'success';
$this->url = '';
} else {
$this->logAction("Indexation URL", $this->url, "failed");
$this->statusMessage = "Impossible d'indexer l'URL. Veuillez vérifier les logs et l'état de Weaviate.";
$this->statusType = 'error';
}
}
}
public function ingestPdf(DocumentIngestionService $ingestionService)
{
$this->validate([
'pdfFile' => 'required|file|mimes:pdf|max:10240', // Max 10MB
]);
$this->statusMessage = "Indexation du fichier PDF en cours...";
$this->statusType = 'info';
$originalName = $this->pdfFile->getClientOriginalName();
$storedPath = $this->pdfFile->storeAs('temp', $originalName);
$absolutePath = storage_path('app/private/' . $storedPath);
if (!file_exists($absolutePath)) {
$absolutePath = storage_path('app/' . $storedPath);
}
$success = $ingestionService->ingestPdf($absolutePath, $originalName, (string) $this->chatbot->id, $this->enableVision);
// Cleanup
if (file_exists($absolutePath)) {
@unlink($absolutePath);
}
if ($success) {
$this->logAction("Indexation PDF", $originalName, "success");
$this->statusMessage = "Le fichier PDF '{$originalName}' a été indexé avec succès !";
$this->statusType = 'success';
$this->pdfFile = null;
} else {
$this->logAction("Indexation PDF", $originalName, "failed");
$this->statusMessage = "Une erreur s'est produite lors de l'indexation du PDF.";
$this->statusType = 'error';
}
}
public function clearDatabase(WeaviateService $weaviateService)
{
$success = $weaviateService->clearChatbotData((string) $this->chatbot->id);
if ($success) {
$this->logAction("Réinitialisation Index", "Suppression de toutes les données vectorielles", "success");
$this->statusMessage = "La base de données vectorielle du chatbot a été réinitialisée avec succès.";
$this->statusType = 'success';
} else {
$this->logAction("Réinitialisation Index", "Suppression de toutes les données vectorielles", "failed");
$this->statusMessage = "Impossible de vider les données Weaviate.";
$this->statusType = 'error';
}
}
public function render()
{
return view('livewire.admin-dashboard');
}
}
+119
View File
@@ -0,0 +1,119 @@
<?php
namespace App\Livewire;
use App\Models\Chatbot as ChatbotModel;
use App\Services\MistralService;
use App\Services\WeaviateService;
use Livewire\Component;
use Livewire\Attributes\On;
class Chatbot extends Component
{
public ChatbotModel $chatbot;
public string $userMessage = '';
public array $messages = [];
public bool $isSending = false;
public string $chatbotName = '';
public function mount(ChatbotModel $chatbot)
{
$this->chatbot = $chatbot;
$this->loadSettings();
// Load messages from session specific to this chatbot's slug
$this->messages = session()->get("chat_messages_{$chatbot->slug}", [
[
'role' => 'assistant',
'content' => "Bonjour ! Je suis votre assistant virtuel RAG. Je peux répondre à vos questions en me basant sur le contenu de ce site. Que puis-je faire pour vous aujourd'hui ?"
]
]);
}
#[On('settings-updated')]
public function loadSettings()
{
// Reload chatbot model from database to get fresh settings
$this->chatbot->refresh();
$this->chatbotName = $this->chatbot->name;
}
public function sendMessage(WeaviateService $weaviate, MistralService $mistral)
{
$this->validate([
'userMessage' => 'required|string|max:1000',
]);
$query = trim($this->userMessage);
// 1. Add user message
$this->messages[] = [
'role' => 'user',
'content' => $query
];
$this->userMessage = '';
$this->isSending = true;
// Save to session immediately for visual update
session()->put("chat_messages_{$this->chatbot->slug}", $this->messages);
$this->dispatch('message-sent');
}
public function getResponse(WeaviateService $weaviate, MistralService $mistral)
{
if (!$this->isSending) {
return;
}
$lastMessage = end($this->messages);
if (!$lastMessage || $lastMessage['role'] !== 'user') {
$this->isSending = false;
return;
}
$query = $lastMessage['content'];
// 2. Search Weaviate for context using the chatbot ID
$chunks = $weaviate->search($query, (string) $this->chatbot->id, 4);
// 3. Generate response via Mistral AI
$history = array_slice($this->messages, 0, -1);
$response = $mistral->generateResponse($query, $chunks, $history, $this->chatbot->system_prompt);
// Extract sources
$sources = [];
if (!empty($chunks)) {
foreach ($chunks as $chunk) {
if (isset($chunk['source'])) {
$sources[] = $chunk['source'];
}
}
}
$sources = array_unique($sources);
// 4. Add assistant response
$this->messages[] = [
'role' => 'assistant',
'content' => $response ?? "Désolé, je n'ai pas pu obtenir de réponse.",
'sources' => $sources
];
$this->isSending = false;
// Save to session
session()->put("chat_messages_{$this->chatbot->slug}", $this->messages);
}
public function clearChat()
{
session()->forget("chat_messages_{$this->chatbot->slug}");
$this->mount($this->chatbot);
}
public function render()
{
return view('livewire.chatbot');
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php
namespace App\Livewire;
use App\Models\Chatbot;
use App\Services\WeaviateService;
use Illuminate\Support\Str;
use Livewire\Component;
class ChatbotManager extends Component
{
public string $name = '';
public string $slug = '';
public string $statusMessage = '';
public string $statusType = '';
public function createChatbot()
{
// Auto-generate slug if empty
if (empty($this->slug)) {
$this->slug = Str::slug($this->name);
} else {
$this->slug = Str::slug($this->slug);
}
$this->validate([
'name' => 'required|string|max:100',
'slug' => 'required|string|unique:chatbots,slug|max:100',
]);
$chatbot = Chatbot::create([
'name' => $this->name,
'slug' => $this->slug,
]);
$this->name = '';
$this->slug = '';
$this->statusMessage = "Le chatbot '{$chatbot->name}' a été créé avec succès !";
$this->statusType = 'success';
}
public function deleteChatbot(int $id, WeaviateService $weaviateService)
{
$chatbot = Chatbot::find($id);
if ($chatbot) {
$name = $chatbot->name;
// Delete data in Weaviate for this chatbot
$weaviateService->clearChatbotData((string) $chatbot->id);
// Delete database records
$chatbot->delete();
$this->statusMessage = "Le chatbot '{$name}' et toutes ses données vectorielles ont été supprimés.";
$this->statusType = 'success';
}
}
public function render()
{
$chatbots = Chatbot::latest()->get();
return view('livewire.chatbot-manager', compact('chatbots'));
}
}