templates/components/_chatbot.html.twig line 1

Open in your IDE?
  1. <div x-data="chatbot()" class="chatbot-container">
  2. <div @click="toggle()" class="chatbot-toggle">
  3. <i class="fas fa-robot text-white text-2xl"></i>
  4. </div>
  5. <div class="chatbot-window" :class="{'open': isOpen}" x-cloak>
  6. <!-- Header -->
  7. <div class="bg-gradient-to-r from-[#006633] to-[#008844] text-white p-4">
  8. <div class="flex justify-between items-center">
  9. <div class="flex items-center space-x-3">
  10. <div class="w-10 h-10 bg-white bg-opacity-20 rounded-full flex items-center justify-center">
  11. <i class="fas fa-robot text-xl"></i>
  12. </div>
  13. <div>
  14. <h3 class="font-semibold">IBUNAS.IA</h3>
  15. <p class="text-xs text-green-100">Assistant juridique intelligent</p>
  16. </div>
  17. </div>
  18. <button @click="toggle()" class="text-white hover:text-gray-200">
  19. <i class="fas fa-times"></i>
  20. </button>
  21. </div>
  22. </div>
  23. <!-- Messages -->
  24. <div id="chatbot-messages" class="flex-1 overflow-y-auto p-4 bg-gray-50 space-y-4">
  25. <template x-for="message in messages" :key="message.id">
  26. <div :class="message.sender === 'user' ? 'flex justify-end' : 'flex justify-start'">
  27. <div :class="message.sender === 'user'
  28. ? 'bg-[#006633] text-white rounded-2xl rounded-br-none'
  29. : 'bg-white border border-gray-200 text-gray-800 rounded-2xl rounded-bl-none'"
  30. class="max-w-[80%] px-4 py-2 shadow-sm">
  31. <p class="text-sm" x-text="message.text"></p>
  32. <p class="text-xs mt-1 opacity-70" x-text="new Date(message.timestamp).toLocaleTimeString()"></p>
  33. </div>
  34. </div>
  35. </template>
  36. <div x-show="isLoading" class="flex justify-start">
  37. <div class="bg-white border border-gray-200 rounded-2xl rounded-bl-none px-4 py-3">
  38. <div class="flex space-x-1">
  39. <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
  40. <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.2s"></div>
  41. <div class="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0.4s"></div>
  42. </div>
  43. </div>
  44. </div>
  45. </div>
  46. <!-- Input -->
  47. <div class="p-4 bg-white border-t">
  48. <form @submit.prevent="send()" class="flex space-x-2">
  49. <input type="text"
  50. x-model="currentMessage"
  51. placeholder="Posez votre question..."
  52. class="flex-1 border border-gray-300 rounded-xl px-4 py-2 focus:outline-none focus:border-[#006633] focus:ring-1 focus:ring-[#006633]">
  53. <button type="submit"
  54. :disabled="isLoading"
  55. class="bg-gradient-to-r from-[#006633] to-[#008844] text-white px-4 py-2 rounded-xl hover:from-[#004d26] hover:to-[#006633] transition disabled:opacity-50">
  56. <i class="fas fa-paper-plane"></i>
  57. </button>
  58. </form>
  59. <div class="mt-2 text-xs text-gray-400 text-center">
  60. <i class="fas fa-lightbulb mr-1"></i> Ex: "Rechercher la loi sur l'urbanisme" | "Derniers décrets"
  61. </div>
  62. </div>
  63. </div>
  64. </div>
  65. <style>
  66. .chatbot-container {
  67. position: fixed;
  68. bottom: 20px;
  69. right: 20px;
  70. z-index: 9999;
  71. }
  72. .chatbot-toggle {
  73. width: 60px;
  74. height: 60px;
  75. background: linear-gradient(135deg, #006633, #008844);
  76. border-radius: 50%;
  77. display: flex;
  78. align-items: center;
  79. justify-content: center;
  80. cursor: pointer;
  81. box-shadow: 0 4px 15px rgba(0,0,0,0.2);
  82. transition: transform 0.3s ease;
  83. }
  84. .chatbot-toggle:hover {
  85. transform: scale(1.1);
  86. }
  87. .chatbot-window {
  88. position: absolute;
  89. bottom: 80px;
  90. right: 0;
  91. width: 380px;
  92. height: 500px;
  93. background: white;
  94. border-radius: 20px;
  95. box-shadow: 0 10px 40px rgba(0,0,0,0.2);
  96. display: flex;
  97. flex-direction: column;
  98. overflow: hidden;
  99. transition: all 0.3s ease;
  100. opacity: 0;
  101. transform: scale(0.8);
  102. transform-origin: bottom right;
  103. pointer-events: none;
  104. }
  105. .chatbot-window.open {
  106. opacity: 1;
  107. transform: scale(1);
  108. pointer-events: all;
  109. }
  110. @media (max-width: 640px) {
  111. .chatbot-window {
  112. width: calc(100vw - 40px);
  113. right: 0;
  114. left: auto;
  115. }
  116. }
  117. [x-cloak] {
  118. display: none;
  119. }
  120. @keyframes bounce {
  121. 0%, 100% { transform: translateY(0); }
  122. 50% { transform: translateY(-5px); }
  123. }
  124. .animate-bounce {
  125. animation: bounce 0.6s infinite;
  126. }
  127. </style>
  128. <script>
  129. function chatbot() {
  130. return {
  131. isOpen: false,
  132. isLoading: false,
  133. currentMessage: '',
  134. messages: [
  135. {
  136. id: 1,
  137. sender: 'bot',
  138. text: 'Bonjour ! Je suis IBUNAS.IA, votre assistant juridique intelligent. Je peux vous aider à rechercher des lois, décrets, arrêtés et vous informer sur les publications officielles. Comment puis-je vous aider ?',
  139. timestamp: Date.now()
  140. }
  141. ],
  142. toggle() {
  143. this.isOpen = !this.isOpen;
  144. if (this.isOpen) {
  145. this.$nextTick(() => {
  146. this.scrollToBottom();
  147. });
  148. }
  149. },
  150. async send() {
  151. if (!this.currentMessage.trim()) return;
  152. if (this.isLoading) return;
  153. // Ajouter le message de l'utilisateur
  154. const userMessage = {
  155. id: Date.now(),
  156. sender: 'user',
  157. text: this.currentMessage,
  158. timestamp: Date.now()
  159. };
  160. this.messages.push(userMessage);
  161. const question = this.currentMessage;
  162. this.currentMessage = '';
  163. this.isLoading = true;
  164. this.$nextTick(() => {
  165. this.scrollToBottom();
  166. });
  167. try {
  168. // Appel à l'API
  169. const response = await this.callAPI(question);
  170. const botMessage = {
  171. id: Date.now() + 1,
  172. sender: 'bot',
  173. text: response,
  174. timestamp: Date.now()
  175. };
  176. this.messages.push(botMessage);
  177. } catch (error) {
  178. console.error('Erreur:', error);
  179. const errorMessage = {
  180. id: Date.now() + 1,
  181. sender: 'bot',
  182. text: "Désolé, une erreur s'est produite. Veuillez réessayer ou contacter l'administrateur.",
  183. timestamp: Date.now()
  184. };
  185. this.messages.push(errorMessage);
  186. } finally {
  187. this.isLoading = false;
  188. this.$nextTick(() => {
  189. this.scrollToBottom();
  190. });
  191. }
  192. },
  193. async callAPI(question) {
  194. // Configuration de l'API - À remplacer par votre URL réelle
  195. const API_URL = '/api/chatbot'; // Remplacez par votre endpoint API
  196. const API_KEY = 'votre-cle-api'; // Si nécessaire
  197. // Simuler une réponse API pour la démonstration (à supprimer quand l'API est prête)
  198. const reponsesSimulees = this.getSimulatedResponse(question);
  199. if (reponsesSimulees) {
  200. // Simuler un délai réseau
  201. await new Promise(resolve => setTimeout(resolve, 1000));
  202. return reponsesSimulees;
  203. }
  204. // Appel API réel
  205. try {
  206. const response = await fetch(API_URL, {
  207. method: 'POST',
  208. headers: {
  209. 'Content-Type': 'application/json',
  210. 'Accept': 'application/json',
  211. 'X-API-Key': API_KEY
  212. },
  213. body: JSON.stringify({
  214. question: question,
  215. context: this.getConversationContext()
  216. })
  217. });
  218. if (!response.ok) {
  219. throw new Error(`HTTP error! status: ${response.status}`);
  220. }
  221. const data = await response.json();
  222. return data.reponse || data.message || data.text || "Je n'ai pas compris votre question. Pouvez-vous reformuler ?";
  223. } catch (error) {
  224. console.error('API Error:', error);
  225. // Fallback vers la simulation si l'API échoue
  226. return this.getSimulatedResponse(question) || "Désolé, je ne peux pas répondre pour le moment. Veuillez réessayer plus tard.";
  227. }
  228. },
  229. getConversationContext() {
  230. // Récupérer les 5 derniers messages pour le contexte
  231. const lastMessages = this.messages.slice(-5);
  232. return lastMessages.map(msg => ({
  233. role: msg.sender === 'user' ? 'user' : 'assistant',
  234. content: msg.text
  235. }));
  236. },
  237. getSimulatedResponse(question) {
  238. const q = question.toLowerCase();
  239. // Réponses simulées basées sur le contenu du Journal Officiel
  240. if (q.includes('bonjour') || q.includes('salut') || q.includes('coucou')) {
  241. return "Bonjour ! Comment puis-je vous aider avec les publications du Journal Officiel aujourd'hui ?";
  242. }
  243. if (q.includes('loi') || q.includes('code')) {
  244. if (q.includes('impôt') || q.includes('impots') || q.includes('fiscal')) {
  245. return "📜 **Code général des impôts 2026**\n\nLe nouveau code général des impôts a été publié dans le JO n°2026-014 du 25 février 2026. Les principales modifications concernent :\n\n• Nouveau barème de l'IRPP\n• Réduction du taux de l'IS de 30% à 25%\n• Nouvelles exonérations pour les zones franches\n\nSouhaitez-vous plus de détails sur un article spécifique ?";
  246. }
  247. if (q.includes('investissement') || q.includes('investissements')) {
  248. return "📜 **Loi sur les investissements n°2026-001**\n\nPubliée au JO du 15 janvier 2026, cette loi modifie le code des investissements. Les principaux avantages :\n\n• Exonération totale d'impôts pendant 5 ans\n• Réduction des droits de douane\n• Simplification des procédures administratives\n\nPuis-je vous aider à trouver des informations plus spécifiques ?";
  249. }
  250. if (q.includes('urbanisme') || q.includes('construction')) {
  251. return "📜 **Code de l'urbanisme**\n\nLa version consolidée du code de l'urbanisme est disponible dans nos archives. Les dernières modifications datent de janvier 2026. Vous pouvez consulter :\n\n• Les règles de constructibilité\n• Les permis de construire\n• Les plans d'occupation des sols\n\nSouhaitez-vous que je recherche un article particulier ?";
  252. }
  253. return "📚 **Textes législatifs disponibles**\n\nNous avons plusieurs lois récentes :\n\n• Loi n°2026-001 sur les investissements\n• Loi n°2026-002 sur le blanchiment\n• Code général des impôts 2026\n• Code du travail révisé\n\nQuelle loi vous intéresse précisément ?";
  254. }
  255. if (q.includes('décret') || q.includes('decret') || q.includes('arrêté') || q.includes('arrete')) {
  256. if (q.includes('nomination')) {
  257. return "📋 **Décret n°2026-045/PR**\n\nPublié le 20 janvier 2026, ce décret porte nomination des membres du gouvernement. La composition complète est disponible dans le JO n°2026-013.\n\nSouhaitez-vous la liste complète des ministres ?";
  258. }
  259. if (q.includes('agence') || q.includes('numérique')) {
  260. return "📋 **Décret n°2026-044/PR**\n\nCréation de l'Agence Nationale du Numérique (ANN) publiée dans le JO du 20 février 2026. L'agence est chargée de :\n\n• Coordonner la transformation numérique\n• Gérer les infrastructures TIC publiques\n• Promouvoir l'e-gouvernement";
  261. }
  262. return "📋 **Décrets et arrêtés récents**\n\nDernières publications :\n\n• Décret n°2026-045/PR - Nomination gouvernement\n• Décret n°2026-044/PR - Création ANN\n• Arrêté n°2026-089/MF - Modalités paiement impôts\n\nLequel souhaitez-vous consulter ?";
  263. }
  264. if (q.includes('journal officiel') || q.includes('jo') || q.includes('numéro') || q.includes('numero')) {
  265. if (q.includes('2026-015') || q.includes('015')) {
  266. return "📰 **Journal Officiel n°2026-015**\n\nPublié le 1er mars 2026, ce numéro contient :\n\n• 12 actes publiés\n• 5 institutions émetteurs\n• 1 234 consultations\n\nContenu principal :\n- Loi de finances rectificative\n- Décrets d'application\n- Appels d'offres\n\nSouhaitez-vous télécharger la version PDF ?";
  267. }
  268. if (q.includes('2026-014') || q.includes('014')) {
  269. return "📰 **Journal Officiel n°2026-014**\n\nPublié le 25 février 2026 :\n\n• 8 actes publiés\n• Code général des impôts 2026\n• Arrêtés ministériels\n\nTéléchargement PDF disponible sur demande.";
  270. }
  271. if (q.includes('2026-013') || q.includes('013')) {
  272. return "📰 **Journal Officiel n°2026-013**\n\nPublié le 20 février 2026 :\n\n• 15 actes publiés\n• Nominations gouvernementales\n• Création de l'Agence Nationale du Numérique\n• Textes réglementaires";
  273. }
  274. if (q.includes('2026-012') || q.includes('012')) {
  275. return "📰 **Journal Officiel n°2026-012**\n\nPublié le 15 février 2026 :\n\n• 10 actes publiés\n• Lois sur les investissements\n• Réformes administratives";
  276. }
  277. return "📰 **Derniers numéros du Journal Officiel**\n\n• JO 2026-015 (01 Mars 2026) - 12 actes\n• JO 2026-014 (25 Fév 2026) - 8 actes\n• JO 2026-013 (20 Fév 2026) - 15 actes\n• JO 2026-012 (15 Fév 2026) - 10 actes\n\nQuel numéro souhaitez-vous consulter ?";
  278. }
  279. if (q.includes('appel') || q.includes('offre') || q.includes('marché') || q.includes('marche')) {
  280. return "🏗️ **Appels d'offres en cours**\n\nDerniers appels d'offres publiés :\n\n1. **AO n°2026-001** - Construction nouvelle Présidence\n Date limite : 30/04/2026\n\n2. **AO n°2026-002** - Fourniture matériel informatique\n Date limite : 15/04/2026\n\n3. **Consultation** - Étude faisabilité port\n Date limite : 30/05/2026\n\nSouhaitez-vous plus de détails sur un appel d'offres spécifique ?";
  281. }
  282. if (q.includes('association') || q.includes('récépissé') || q.includes('recepisse')) {
  283. return "🏢 **Associations**\n\nLes récépissés de déclaration d'associations sont publiés dans les JO. Tarifs :\n\n• Déclaration : 12 000 KMF\n• Modification statuts : 8 000 KMF\n• Dissolution : 5 000 KMF\n\nBesoin d'aide pour préparer votre dossier ?";
  284. }
  285. if (q.includes('foncier') || q.includes('terrain') || q.includes('propriété')) {
  286. return "🏠 **Actes fonciers**\n\nLes réquisitions foncières et transactions immobilières sont publiées au JO. Tarifs :\n\n• Base : 15 000 KMF\n• +3 000 KMF / 300 caractères\n\nNous pouvons vous aider à publier votre annonce foncière.";
  287. }
  288. if (q.includes('abonnement') || q.includes('abonner') || q.includes('prix') || q.includes('tarif')) {
  289. return "💳 **Nos offres d'abonnement**\n\n**Standard** : 50 000 KMF/an\n• Accès numéros complets PDF\n• Recherche par filtres\n• Textes isolés moins de 5 ans\n\n**Premium** : 90 000 KMF/an\n• Tout Standard +\n• Archives complètes depuis 1975\n• Recherche IA avancée\n• Alertes personnalisées\n\n**-20% sur la première année !**\n\nSouhaitez-vous vous abonner ?";
  290. }
  291. if (q.includes('contact') || q.includes('aide') || q.includes('support')) {
  292. return "📞 **Nous contacter**\n\n• Email : contact@journalofficiel.km\n• Téléphone : +269 123 45 67\n• Horaires : Lundi-Vendredi 8h-17h\n\nNotre équipe vous répondra dans les meilleurs délais.";
  293. }
  294. if (q.includes('merci')) {
  295. return "Avec plaisir ! N'hésitez pas si vous avez d'autres questions. Je suis là pour vous aider ! 🙏";
  296. }
  297. // Réponse par défaut
  298. return "Je suis votre assistant pour le Journal Officiel de l'Union des Comores. Je peux vous aider à :\n\n• 📰 Consulter les derniers numéros du JO\n• 📜 Rechercher des lois, décrets et arrêtés\n• 🏗️ Trouver des appels d'offres\n• 💳 Vous informer sur les tarifs de publication\n• 📞 Vous orienter vers les services compétents\n\nQue puis-je faire pour vous aujourd'hui ?";
  299. },
  300. scrollToBottom() {
  301. const container = document.getElementById('chatbot-messages');
  302. if (container) {
  303. container.scrollTop = container.scrollHeight;
  304. }
  305. }
  306. }
  307. }
  308. </script>