Initial commit
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
<div class="bg-slate-950 border border-slate-800 rounded-xl p-6 shadow-sm space-y-6">
|
||||
|
||||
<!-- Top Header -->
|
||||
<div class="flex flex-col sm:flex-row justify-between sm:items-center gap-4 pb-6 border-b border-slate-900">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-slate-100 tracking-tight">Panneau de configuration RAG</h2>
|
||||
<p class="text-slate-500 text-xs mt-0.5">Indexez des sources et ajustez le comportement du chatbot.</p>
|
||||
</div>
|
||||
<button
|
||||
wire:confirm="Êtes-vous sûr de vouloir réinitialiser l'index Weaviate ? Toutes les données seront effacées."
|
||||
wire:click="clearDatabase"
|
||||
class="self-start px-3 py-1.5 bg-transparent hover:bg-rose-950/20 border border-slate-800 hover:border-rose-900/50 text-slate-400 hover:text-rose-400 rounded-lg transition duration-200 text-xs font-medium flex items-center gap-1.5"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
Réinitialiser l'index
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Alert Banner -->
|
||||
@if ($statusMessage)
|
||||
<div class="p-3 rounded-lg border text-xs transition-all duration-200 flex items-start gap-2.5
|
||||
@if($statusType === 'success') bg-emerald-950/20 border-emerald-900/50 text-emerald-400 @elseif($statusType === 'error') bg-rose-950/20 border-rose-900/50 text-rose-400 @else bg-slate-900 border-slate-800 text-slate-350 @endif">
|
||||
@if($statusType === 'info')
|
||||
<svg class="animate-spin h-4 w-4 text-slate-400 shrink-0 mt-0.5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
@else
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 shrink-0 mt-0.5 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
@endif
|
||||
<div class="leading-relaxed">{{ $statusMessage }}</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Part 1: Ingestion Grid (Web URL / PDF) -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- Ingest URL Form -->
|
||||
<form wire:submit.prevent="ingestUrl" class="space-y-3">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-400 mb-1.5">Ajouter une page web par URL</label>
|
||||
<input
|
||||
type="url"
|
||||
wire:model="url"
|
||||
placeholder="https://example.com/page-a-indexer"
|
||||
class="w-full bg-slate-900 border border-slate-800 focus:border-slate-700 focus:ring-0 rounded-lg px-3 py-2 text-slate-200 placeholder-slate-650 transition outline-none text-xs"
|
||||
/>
|
||||
@error('url') <span class="text-rose-500 text-[10px] mt-1 block">{{ $message }}</span> @enderror
|
||||
|
||||
<div class="flex items-center gap-4 mt-2.5">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<input type="checkbox" wire:model.live="crawlEntireSite" id="crawlEntireSite" class="rounded bg-slate-900 border-slate-800 text-cyan-600 focus:ring-0 focus:ring-offset-0 h-3.5 w-3.5" />
|
||||
<label for="crawlEntireSite" class="text-[10px] text-slate-400 cursor-pointer select-none">Explorer tout le site (Crawler)</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($crawlEntireSite)
|
||||
<div class="mt-2 pl-5 flex items-center gap-2">
|
||||
<label for="maxPages" class="text-[10px] text-slate-500">Limite de pages :</label>
|
||||
<input
|
||||
type="number"
|
||||
id="maxPages"
|
||||
wire:model="maxPages"
|
||||
min="1"
|
||||
max="100"
|
||||
class="w-16 bg-slate-900 border border-slate-800 focus:border-slate-700 focus:ring-0 roundedpx-2 py-1 text-slate-200 text-[10px] text-center outline-none"
|
||||
/>
|
||||
<span class="text-[9px] text-slate-600">(max. 100)</span>
|
||||
</div>
|
||||
@error('maxPages') <span class="text-rose-500 text-[10px] mt-1 block">{{ $message }}</span> @enderror
|
||||
@endif
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full bg-slate-900 hover:bg-slate-800 border border-slate-800 hover:border-slate-700 text-slate-200 font-medium py-2 px-3 rounded-lg transition text-xs flex items-center justify-center gap-1.5"
|
||||
>
|
||||
@if($crawlEntireSite)
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 1121.21 7.89L21 8" />
|
||||
</svg>
|
||||
Explorer et indexer le site
|
||||
@else
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
|
||||
</svg>
|
||||
Indexer la page
|
||||
@endif
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Ingest PDF Form -->
|
||||
<form wire:submit.prevent="ingestPdf" class="space-y-3">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-400 mb-1.5">Importer un fichier PDF</label>
|
||||
<div class="flex items-center justify-center w-full">
|
||||
<label class="flex flex-col items-center justify-center w-full h-24 border border-slate-800 border-dashed rounded-lg cursor-pointer bg-slate-900/40 hover:bg-slate-900 hover:border-slate-700 transition">
|
||||
<div class="flex flex-col items-center justify-center py-4">
|
||||
<svg class="w-6 h-6 mb-2 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
|
||||
</svg>
|
||||
<p class="text-[11px] text-slate-400">
|
||||
@if($pdfFile)
|
||||
<span class="text-slate-200 font-medium">{{ $pdfFile->getClientOriginalName() }}</span>
|
||||
@else
|
||||
<span>Sélectionner un fichier PDF</span>
|
||||
@endif
|
||||
</p>
|
||||
<p class="text-[9px] text-slate-600 mt-0.5">Taille max. 10 Mo</p>
|
||||
</div>
|
||||
<input type="file" wire:model="pdfFile" class="hidden" accept=".pdf" />
|
||||
</label>
|
||||
</div>
|
||||
@error('pdfFile') <span class="text-rose-500 text-[10px] mt-1 block">{{ $message }}</span> @enderror
|
||||
|
||||
<div class="flex items-center gap-2 mt-2">
|
||||
<input type="checkbox" wire:model="enableVision" id="enableVision" class="rounded bg-slate-900 border-slate-800 text-cyan-600 focus:ring-0 focus:ring-offset-0 h-3.5 w-3.5" />
|
||||
<label for="enableVision" class="text-[10px] text-slate-400 cursor-pointer select-none">Décrire les images via Vision AI (Mistral)</label>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full bg-slate-900 hover:bg-slate-800 border border-slate-800 hover:border-slate-700 text-slate-200 font-medium py-2 px-3 rounded-lg transition text-xs flex items-center justify-center gap-1.5"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
Indexer le document
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Part 2: System Prompt & Chatbot Settings Customization -->
|
||||
<form wire:submit.prevent="saveSystemPrompt" class="space-y-4 pt-4 border-t border-slate-900">
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-400 mb-1.5">Nom du Chatbot</label>
|
||||
<input
|
||||
type="text"
|
||||
wire:model="chatbotName"
|
||||
placeholder="Ex: Assistant Virtuel AGGLO"
|
||||
class="w-full bg-slate-900 border border-slate-800 focus:border-slate-700 focus:ring-0 rounded-lg px-3 py-2 text-slate-200 placeholder-slate-650 transition outline-none text-xs"
|
||||
/>
|
||||
@error('chatbotName') <span class="text-rose-500 text-[10px] mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-1.5">
|
||||
<label class="block text-xs font-semibold text-slate-400">Prompt système du Chatbot</label>
|
||||
<span class="text-[10px] text-slate-600">Définit le rôle, les instructions et les limites de l'IA</span>
|
||||
</div>
|
||||
<textarea
|
||||
wire:model="systemPrompt"
|
||||
rows="6"
|
||||
placeholder="Rédigez les consignes du chatbot..."
|
||||
class="w-full bg-slate-900 border border-slate-800 focus:border-slate-700 focus:ring-0 rounded-lg px-3 py-2 text-slate-200 placeholder-slate-650 transition outline-none text-xs font-mono leading-relaxed resize-y"
|
||||
></textarea>
|
||||
@error('systemPrompt') <span class="text-rose-500 text-[10px] mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
class="bg-slate-900 hover:bg-slate-800 border border-slate-800 hover:border-slate-700 text-slate-200 font-medium py-2 px-4 rounded-lg transition text-xs flex items-center justify-center gap-1.5"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4" />
|
||||
</svg>
|
||||
Enregistrer les paramètres
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Part 3: Embed Integration Script -->
|
||||
<div class="pt-4 border-t border-slate-900 space-y-2">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-xs font-semibold text-slate-350">Script d'intégration</h3>
|
||||
<span class="text-[9px] text-slate-500">Copiez ce script avant la balise </body> de votre site externe</span>
|
||||
</div>
|
||||
<div class="relative bg-slate-900 border border-slate-800 rounded-lg p-2.5 flex items-center justify-between font-mono text-[10px] text-slate-400">
|
||||
<code class="select-all overflow-x-auto whitespace-nowrap scrollbar-none pr-4"><script src="{{ url('/chatbot-widget.js') }}?chatbot={{ $chatbot->slug }}" defer></script></code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Part 4: Action History Logs -->
|
||||
<div class="pt-4 border-t border-slate-900">
|
||||
<h3 class="text-xs font-semibold text-slate-350 mb-3">Historique des actions</h3>
|
||||
|
||||
@if(empty($history))
|
||||
<p class="text-slate-600 text-[11px] italic">Aucune action enregistrée pour le moment.</p>
|
||||
@else
|
||||
<div class="overflow-hidden border border-slate-900 rounded-lg max-h-48 overflow-y-auto" style="scrollbar-width: thin; scrollbar-color: #1e293b #020617;">
|
||||
<table class="w-full text-[11px] text-left text-slate-400">
|
||||
<thead class="bg-slate-900 text-slate-300 font-medium text-[10px] uppercase tracking-wider sticky top-0">
|
||||
<tr>
|
||||
<th class="px-3 py-2">Date</th>
|
||||
<th class="px-3 py-2">Action</th>
|
||||
<th class="px-3 py-2">Cible</th>
|
||||
<th class="px-3 py-2 text-right">Statut</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-900">
|
||||
@foreach(array_reverse($history) as $log)
|
||||
<tr class="hover:bg-slate-900/40 transition-colors">
|
||||
<td class="px-3 py-2 text-slate-500 whitespace-nowrap">{{ $log['timestamp'] }}</td>
|
||||
<td class="px-3 py-2 font-medium text-slate-300 whitespace-nowrap">{{ $log['action'] }}</td>
|
||||
<td class="px-3 py-2 text-slate-400 truncate max-w-[150px]" title="{{ $log['target'] }}">{{ $log['target'] }}</td>
|
||||
<td class="px-3 py-2 text-right whitespace-nowrap">
|
||||
<span class="inline-flex items-center px-1.5 py-0.5 rounded text-[9px] font-medium
|
||||
@if($log['status'] === 'success') bg-emerald-950/40 text-emerald-400 border border-emerald-900/30 @else bg-rose-950/40 text-rose-400 border border-rose-900/30 @endif">
|
||||
{{ $log['status'] === 'success' ? 'Succès' : 'Échec' }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,108 @@
|
||||
<div class="space-y-8">
|
||||
<!-- Status message -->
|
||||
@if ($statusMessage)
|
||||
<div class="p-3 rounded-lg border text-xs transition-all duration-200 flex items-start gap-2.5 bg-emerald-950/20 border-emerald-900/50 text-emerald-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 shrink-0 mt-0.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<div class="leading-relaxed">{{ $statusMessage }}</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8">
|
||||
|
||||
<!-- Left Panel: Create Form -->
|
||||
<div class="lg:col-span-4 bg-slate-950 border border-slate-800 rounded-xl p-6 shadow-sm self-start">
|
||||
<h2 class="text-sm font-semibold text-slate-100 mb-4 pb-3 border-b border-slate-900">Nouveau Chatbot</h2>
|
||||
<form wire:submit.prevent="createChatbot" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-400 mb-1.5">Nom du client / Chatbot</label>
|
||||
<input
|
||||
type="text"
|
||||
wire:model="name"
|
||||
placeholder="Ex: Clinique du Parc"
|
||||
class="w-full bg-slate-900 border border-slate-800 focus:border-slate-700 focus:ring-0 rounded-lg px-3 py-2 text-slate-250 placeholder-slate-600 transition outline-none text-xs"
|
||||
/>
|
||||
@error('name') <span class="text-rose-500 text-[10px] mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-400 mb-1.5">Slug d'identification (Optionnel)</label>
|
||||
<input
|
||||
type="text"
|
||||
wire:model="slug"
|
||||
placeholder="Ex: clinique-du-parc"
|
||||
class="w-full bg-slate-900 border border-slate-800 focus:border-slate-700 focus:ring-0 rounded-lg px-3 py-2 text-slate-250 placeholder-slate-650 transition outline-none text-xs font-mono"
|
||||
/>
|
||||
<p class="text-[9px] text-slate-600 mt-1">L'identifiant unique utilisé dans l'URL. Généré automatiquement si laissé vide.</p>
|
||||
@error('slug') <span class="text-rose-500 text-[10px] mt-1 block">{{ $message }}</span> @enderror
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full bg-slate-800 hover:bg-slate-750 border border-slate-700 text-slate-200 font-medium py-2 px-3 rounded-lg transition text-xs flex items-center justify-center gap-1.5"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5 text-slate-450" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
Créer le chatbot
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Right Panel: Chatbots List -->
|
||||
<div class="lg:col-span-8 space-y-4">
|
||||
<h2 class="text-sm font-semibold text-slate-100 pb-2 border-b border-slate-900">Vos Chatbots Actifs</h2>
|
||||
|
||||
@if($chatbots->isEmpty())
|
||||
<div class="bg-slate-950 border border-slate-900 rounded-xl p-8 text-center text-slate-500 text-xs">
|
||||
<p class="italic">Aucun chatbot n'a été créé pour le moment. Utilisez le formulaire à gauche pour commencer.</p>
|
||||
</div>
|
||||
@else
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
@foreach($chatbots as $bot)
|
||||
<div class="bg-slate-950 border border-slate-850 rounded-xl p-5 shadow-sm space-y-4 flex flex-col justify-between">
|
||||
<div class="space-y-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<h3 class="text-sm font-semibold text-slate-200">{{ $bot->name }}</h3>
|
||||
<span class="text-[9px] font-mono bg-slate-900 border border-slate-800 px-1.5 py-0.5 rounded text-slate-500">{{ $bot->slug }}</span>
|
||||
</div>
|
||||
<p class="text-[10px] text-slate-500">Créé le {{ $bot->created_at->format('d/m/Y à H:i') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="pt-3 border-t border-slate-900 flex justify-between items-center gap-2">
|
||||
<div class="flex gap-2">
|
||||
<a
|
||||
href="{{ url('/admin/chatbots/' . $bot->slug) }}"
|
||||
class="px-2.5 py-1.5 bg-slate-900 hover:bg-slate-800 border border-slate-800 hover:border-slate-700 text-slate-300 hover:text-white rounded-lg transition text-[11px] font-medium flex items-center gap-1"
|
||||
>
|
||||
Gérer
|
||||
</a>
|
||||
<a
|
||||
href="{{ url('/chatbot/' . $bot->slug) }}"
|
||||
class="px-2.5 py-1.5 bg-transparent hover:bg-slate-900 border border-transparent hover:border-slate-800 text-slate-400 hover:text-slate-200 rounded-lg transition text-[11px] font-medium flex items-center gap-1"
|
||||
target="_blank"
|
||||
>
|
||||
Tester
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<button
|
||||
wire:confirm="Êtes-vous sûr de vouloir supprimer le chatbot '{{ $bot->name }}' ? Toutes les données vectorielles associées dans Weaviate seront également supprimées."
|
||||
wire:click="deleteChatbot({{ $bot->id }})"
|
||||
class="p-1.5 text-slate-600 hover:text-rose-500 hover:bg-rose-950/20 rounded-lg transition"
|
||||
title="Supprimer ce chatbot"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,119 @@
|
||||
<div class="flex flex-col bg-slate-900 border border-slate-800 rounded-2xl h-full w-full shadow-2xl backdrop-blur-xl bg-opacity-95 overflow-hidden" id="chatbot-container">
|
||||
<!-- Chat Header -->
|
||||
<div class="px-6 py-4 bg-slate-950 border-b border-slate-800 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="relative">
|
||||
<div class="w-10 h-10 rounded-xl bg-gradient-to-tr from-cyan-500 to-indigo-600 flex items-center justify-center font-bold text-white shadow-lg shadow-cyan-500/20">
|
||||
AI
|
||||
</div>
|
||||
<span class="absolute bottom-0 right-0 block h-2.5 w-2.5 rounded-full bg-emerald-400 ring-2 ring-slate-950"></span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-white">{{ $chatbotName }}</h3>
|
||||
<p class="text-[11px] text-emerald-400 font-medium">En ligne • RAG Activé</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
wire:click="clearChat"
|
||||
class="p-2 text-slate-400 hover:text-rose-400 hover:bg-rose-500/10 rounded-lg transition"
|
||||
title="Effacer la conversation"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Messages List -->
|
||||
<div class="flex-1 overflow-y-auto p-6 space-y-4 scroll-smooth" id="chat-messages-container" style="scrollbar-width: thin; scrollbar-color: #1e293b #0f172a;">
|
||||
@foreach($messages as $msg)
|
||||
<div class="flex {{ $msg['role'] === 'user' ? 'justify-end' : 'justify-start' }} transition-all duration-300">
|
||||
<div class="max-w-[80%] rounded-2xl px-4 py-3 text-sm leading-relaxed shadow-md
|
||||
@if($msg['role'] === 'user')
|
||||
bg-gradient-to-br from-cyan-600 to-indigo-700 text-white rounded-tr-none
|
||||
@else
|
||||
bg-slate-950 border border-slate-800 text-slate-200 rounded-tl-none
|
||||
@endif">
|
||||
|
||||
<div class="prose prose-invert prose-sm max-w-none text-slate-200">
|
||||
{!! \Illuminate\Support\Str::markdown($msg['content']) !!}
|
||||
</div>
|
||||
|
||||
@if(isset($msg['sources']) && !empty($msg['sources']))
|
||||
<div class="mt-2 pt-2 border-t border-slate-800 text-[10px] text-slate-400">
|
||||
<span class="font-semibold">Sources consultées :</span>
|
||||
<div class="flex flex-wrap gap-1 mt-1">
|
||||
@foreach($msg['sources'] as $src)
|
||||
<span class="bg-slate-900 border border-slate-800 px-1.5 py-0.5 rounded text-[9px] hover:text-white transition cursor-help" title="{{ $src }}">
|
||||
{{ basename($src) }}
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
@if($isSending)
|
||||
<div class="flex justify-start">
|
||||
<div class="bg-slate-950 border border-slate-800 rounded-2xl rounded-tl-none px-4 py-3 shadow-md flex items-center gap-1.5">
|
||||
<span class="w-2 h-2 bg-cyan-400 rounded-full animate-bounce" style="animation-delay: 0ms"></span>
|
||||
<span class="w-2 h-2 bg-cyan-400 rounded-full animate-bounce" style="animation-delay: 150ms"></span>
|
||||
<span class="w-2 h-2 bg-cyan-400 rounded-full animate-bounce" style="animation-delay: 300ms"></span>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Input Bar -->
|
||||
<div class="p-4 bg-slate-950 border-t border-slate-800">
|
||||
<form wire:submit.prevent="sendMessage" class="flex gap-2" id="chat-form">
|
||||
<input
|
||||
type="text"
|
||||
wire:model="userMessage"
|
||||
placeholder="Posez votre question sur le site..."
|
||||
class="flex-1 bg-slate-900 border border-slate-800 focus:border-cyan-500 focus:ring-1 focus:ring-cyan-500 rounded-xl px-4 py-3 text-slate-200 placeholder-slate-500 transition outline-none text-sm"
|
||||
@if($isSending) disabled @endif
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
class="bg-gradient-to-r from-cyan-600 to-indigo-600 hover:from-cyan-500 hover:to-indigo-500 disabled:from-slate-800 disabled:to-slate-800 text-white p-3 rounded-xl transition duration-300 shadow-lg shadow-cyan-600/10 flex items-center justify-center shrink-0"
|
||||
@if($isSending) disabled @endif
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 rotate-90" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
|
||||
</svg>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@script
|
||||
<script>
|
||||
// Automatic scroll to bottom on message updates
|
||||
const container = document.getElementById('chat-messages-container');
|
||||
const scrollToBottom = () => {
|
||||
if (container) {
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
// Scroll immediately on load
|
||||
scrollToBottom();
|
||||
|
||||
// Listen for standard Livewire updates
|
||||
Livewire.hook('request', ({ respond }) => {
|
||||
respond(() => {
|
||||
setTimeout(scrollToBottom, 50);
|
||||
});
|
||||
});
|
||||
|
||||
// Trigger AI response generation after sending message
|
||||
$wire.on('message-sent', () => {
|
||||
setTimeout(scrollToBottom, 50);
|
||||
$wire.getResponse();
|
||||
});
|
||||
</script>
|
||||
@endscript
|
||||
</div>
|
||||
Reference in New Issue
Block a user