Spaces:
Running
Running
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>Assistant Vérité & Légalité</title> | |
| <style> | |
| /* --- VARIABLES & RESET --- */ | |
| :root { | |
| --bg-color: #0f172a; | |
| --chat-bg: #1e293b; | |
| --primary-color: #3b82f6; | |
| --primary-hover: #2563eb; | |
| --text-color: #f1f5f9; | |
| --text-muted: #94a3b8; | |
| --user-msg-bg: #3b82f6; | |
| --ai-msg-bg: #334155; | |
| --border-radius: 12px; | |
| --font-main: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | |
| --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| -webkit-tap-highlight-color: transparent; | |
| } | |
| body { | |
| font-family: var(--font-main); | |
| background-color: var(--bg-color); | |
| color: var(--text-color); | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| } | |
| /* --- HEADER --- */ | |
| header { | |
| background-color: rgba(30, 41, 59, 0.95); | |
| backdrop-filter: blur(10px); | |
| padding: 1rem; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.1); | |
| z-index: 10; | |
| } | |
| .brand { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| font-weight: 700; | |
| font-size: 1.1rem; | |
| color: var(--primary-color); | |
| } | |
| .brand svg { | |
| width: 24px; | |
| height: 24px; | |
| fill: currentColor; | |
| } | |
| .header-actions button { | |
| background: transparent; | |
| border: none; | |
| color: var(--text-muted); | |
| cursor: pointer; | |
| padding: 8px; | |
| border-radius: 50%; | |
| transition: all 0.2s; | |
| } | |
| .header-actions button:hover { | |
| background-color: rgba(255, 255, 255, 0.1); | |
| color: var(--text-color); | |
| } | |
| .built-with { | |
| font-size: 0.75rem; | |
| color: var(--text-muted); | |
| text-decoration: none; | |
| margin-right: 10px; | |
| } | |
| .built-with:hover { | |
| color: var(--primary-color); | |
| } | |
| /* --- CHAT AREA --- */ | |
| #chat-container { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 1rem; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1rem; | |
| scroll-behavior: smooth; | |
| } | |
| .message { | |
| max-width: 85%; | |
| padding: 12px 16px; | |
| border-radius: var(--border-radius); | |
| line-height: 1.5; | |
| position: relative; | |
| animation: fadeIn 0.3s ease-out; | |
| word-wrap: break-word; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .message.user { | |
| align-self: flex-end; | |
| background-color: var(--user-msg-bg); | |
| color: white; | |
| border-bottom-right-radius: 2px; | |
| } | |
| .message.assistant { | |
| align-self: flex-start; | |
| background-color: var(--ai-msg-bg); | |
| color: var(--text-color); | |
| border-bottom-left-radius: 2px; | |
| } | |
| .message.system { | |
| align-self: center; | |
| background-color: transparent; | |
| color: var(--text-muted); | |
| font-size: 0.85rem; | |
| text-align: center; | |
| max-width: 100%; | |
| } | |
| .typing-indicator { | |
| display: inline-flex; | |
| gap: 4px; | |
| } | |
| .typing-indicator span { | |
| width: 6px; | |
| height: 6px; | |
| background-color: var(--text-muted); | |
| border-radius: 50%; | |
| animation: bounce 1.4s infinite ease-in-out both; | |
| } | |
| .typing-indicator span:nth-child(1) { animation-delay: -0.32s; } | |
| .typing-indicator span:nth-child(2) { animation-delay: -0.16s; } | |
| @keyframes bounce { | |
| 0%, 80%, 100% { transform: scale(0); } | |
| 40% { transform: scale(1); } | |
| } | |
| /* --- INPUT AREA --- */ | |
| .input-area { | |
| background-color: var(--chat-bg); | |
| padding: 1rem; | |
| border-top: 1px solid rgba(255, 255, 255, 0.1); | |
| display: flex; | |
| align-items: flex-end; | |
| gap: 10px; | |
| } | |
| textarea { | |
| flex: 1; | |
| background-color: rgba(0, 0, 0, 0.2); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| border-radius: 20px; | |
| color: var(--text-color); | |
| padding: 12px 16px; | |
| font-family: inherit; | |
| font-size: 1rem; | |
| resize: none; | |
| max-height: 120px; | |
| min-height: 48px; | |
| outline: none; | |
| transition: border-color 0.2s; | |
| } | |
| textarea:focus { | |
| border-color: var(--primary-color); | |
| } | |
| #send-btn { | |
| background-color: var(--primary-color); | |
| color: white; | |
| border: none; | |
| border-radius: 50%; | |
| width: 48px; | |
| height: 48px; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| cursor: pointer; | |
| transition: background-color 0.2s, transform 0.1s; | |
| flex-shrink: 0; | |
| } | |
| #send-btn:hover { | |
| background-color: var(--primary-hover); | |
| } | |
| #send-btn:active { | |
| transform: scale(0.95); | |
| } | |
| #send-btn:disabled { | |
| background-color: var(--text-muted); | |
| cursor: not-allowed; | |
| opacity: 0.5; | |
| } | |
| /* --- MODAL --- */ | |
| .modal-overlay { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0, 0, 0, 0.7); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 100; | |
| opacity: 0; | |
| pointer-events: none; | |
| transition: opacity 0.3s; | |
| } | |
| .modal-overlay.active { | |
| opacity: 1; | |
| pointer-events: all; | |
| } | |
| .modal { | |
| background-color: var(--chat-bg); | |
| padding: 2rem; | |
| border-radius: var(--border-radius); | |
| width: 90%; | |
| max-width: 400px; | |
| box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5); | |
| transform: translateY(20px); | |
| transition: transform 0.3s; | |
| } | |
| .modal-overlay.active .modal { | |
| transform: translateY(0); | |
| } | |
| .modal h2 { | |
| margin-bottom: 1rem; | |
| color: var(--primary-color); | |
| } | |
| .modal label { | |
| display: block; | |
| margin-bottom: 0.5rem; | |
| color: var(--text-muted); | |
| font-size: 0.9rem; | |
| } | |
| .modal input { | |
| width: 100%; | |
| padding: 10px; | |
| border-radius: 8px; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| background-color: rgba(0, 0, 0, 0.2); | |
| color: white; | |
| margin-bottom: 1.5rem; | |
| outline: none; | |
| } | |
| .modal input:focus { | |
| border-color: var(--primary-color); | |
| } | |
| .modal-actions { | |
| display: flex; | |
| justify-content: flex-end; | |
| gap: 10px; | |
| } | |
| .btn { | |
| padding: 8px 16px; | |
| border-radius: 6px; | |
| border: none; | |
| cursor: pointer; | |
| font-weight: 600; | |
| transition: opacity 0.2s; | |
| } | |
| .btn-secondary { | |
| background-color: transparent; | |
| color: var(--text-muted); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| .btn-primary { | |
| background-color: var(--primary-color); | |
| color: white; | |
| } | |
| .btn:hover { | |
| opacity: 0.9; | |
| } | |
| /* --- SCROLLBAR --- */ | |
| ::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: transparent; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: rgba(255, 255, 255, 0.2); | |
| border-radius: 3px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Header --> | |
| <header> | |
| <div class="brand"> | |
| <!-- Icone Balance/Justice --> | |
| <svg viewBox="0 0 24 24"> | |
| <path d="M12 2L1 21h22L12 2zm0 3.5L19.5 19h-15L12 5.5zM11 10v2h2v-2h-2zm0 4v4h2v-4h-2z" style="display:none;"/> <!-- Placeholder logic --> | |
| <path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/> | |
| </svg> | |
| VéritéAI | |
| </div> | |
| <div class="header-actions"> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">Built with anycoder</a> | |
| <button id="settings-btn" title="Paramètres API"> | |
| <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <circle cx="12" cy="12" r="3"></circle> | |
| <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path> | |
| </svg> | |
| </button> | |
| </div> | |
| </header> | |
| <!-- Chat Container --> | |
| <main id="chat-container"> | |
| <div class="message system"> | |
| Bienvenue. Je suis programmé pour répondre avec précision, honnêteté et clarté sur les aspects légaux. | |
| <br><br> | |
| <i>Pour une intelligence réelle, configurez votre clé API dans les paramètres (icône engrenage). Sinon, je répondrai en mode simulation.</i> | |
| </div> | |
| <!-- Messages will be injected here via JS --> | |
| </main> | |
| <!-- Input Area --> | |
| <footer class="input-area"> | |
| <textarea id="user-input" placeholder="Posez votre question ici..." rows="1"></textarea> | |
| <button id="send-btn" disabled> | |
| <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <line x1="22" y1="2" x2="11" y2="13"></line> | |
| <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon> | |
| </svg> | |
| </button> | |
| </footer> | |
| <!-- Settings Modal --> | |
| <div class="modal-overlay" id="settings-modal"> | |
| <div class="modal"> | |
| <h2>Configuration</h2> | |
| <label for="api-key">Clé API OpenAI (Optionnel)</label> | |
| <input type="password" id="api-key" placeholder="sk-..."> | |
| <p style="font-size: 0.8rem; color: #64748b; margin-bottom: 1rem;"> | |
| Laissée vide, l'application utilisera un mode de simulation local. | |
| </p> | |
| <div class="modal-actions"> | |
| <button class="btn btn-secondary" id="close-settings">Annuler</button> | |
| <button class="btn btn-primary" id="save-settings">Sauvegarder</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // --- DOM Elements --- | |
| const chatContainer = document.getElementById('chat-container'); | |
| const userInput = document.getElementById('user-input'); | |
| const sendBtn = document.getElementById('send-btn'); | |
| const settingsBtn = document.getElementById('settings-btn'); | |
| const settingsModal = document.getElementById('settings-modal'); | |
| const closeSettingsBtn = document.getElementById('close-settings'); | |
| const saveSettingsBtn = document.getElementById('save-settings'); | |
| const apiKeyInput = document.getElementById('api-key'); | |
| // --- State --- | |
| let apiKey = localStorage.getItem('truthai_api_key') || ''; | |
| let isProcessing = false; | |
| // --- Initialization --- | |
| if (apiKey) { | |
| apiKeyInput.value = apiKey; | |
| } | |
| // Auto-resize textarea | |
| userInput.addEventListener('input', function() { | |
| this.style.height = 'auto'; | |
| this.style.height = (this.scrollHeight) + 'px'; | |
| if (this.value.trim() === '') { | |
| sendBtn.disabled = true; | |
| } else { | |
| sendBtn.disabled = false; | |
| } | |
| }); | |
| // --- Event Listeners --- | |
| sendBtn.addEventListener('click', handleSend); | |
| userInput.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| handleSend(); | |
| } | |
| }); | |
| // Modal Logic | |
| settingsBtn.addEventListener('click', () => { | |
| settingsModal.classList.add('active'); | |
| apiKeyInput.value = apiKey; | |
| }); | |
| closeSettingsBtn.addEventListener('click', () => { | |
| settingsModal.classList.remove('active'); | |
| }); | |
| saveSettingsBtn.addEventListener('click', () => { | |
| apiKey = apiKeyInput.value.trim(); | |
| localStorage.setItem('truthai_api_key', apiKey); | |
| settingsModal.classList.remove('active'); | |
| addMessage('system', 'Configuration sauvegardée. ' + (apiKey ? 'Mode API activé.' : 'Mode Simulation activé.')); | |
| }); | |
| // --- Core Logic --- | |
| async function handleSend() { | |
| const text = userInput.value.trim(); | |
| if (!text || isProcessing) return; | |
| // Reset Input | |
| userInput.value = ''; | |
| userInput.style.height = 'auto'; | |
| sendBtn.disabled = true; | |
| // Add User Message | |
| addMessage('user', text); | |
| isProcessing = true; | |
| // Show Loading | |
| const loadingId = addLoadingIndicator(); | |
| try { | |
| let responseText = ""; | |
| if (apiKey) { | |
| // Call OpenAI API | |
| responseText = await fetchOpenAIResponse(text); | |
| } else { | |
| // Simulation Mode | |
| await new Promise(r => setTimeout(r, 1500)); // Fake delay | |
| responseText = getSimulationResponse(text); | |
| } | |
| // Remove loading and add response | |
| removeMessage(loadingId); | |
| typeWriterEffect(responseText); | |
| } catch (error) { | |
| removeMessage(loadingId); | |
| addMessage('system', 'Erreur: ' + error.message); | |
| isProcessing = false; | |
| } | |
| } | |
| function addMessage(sender, text) { | |
| const div = document.createElement('div'); | |
| div.className = `message ${sender}`; | |
| div.innerHTML = formatText(text); | |
| chatContainer.appendChild(div); | |
| scrollToBottom(); | |
| return div; // Return element reference if needed | |
| } | |
| function addLoadingIndicator() { | |
| const id = 'loading-' + Date.now(); | |
| const div = document.createElement('div'); | |
| div.id = id; | |
| div.className = 'message assistant'; | |
| div.innerHTML = `<div class="typing-indicator"><span></span><span></span><span></span></div>`; | |
| chatContainer.appendChild(div); | |
| scrollToBottom(); | |
| return id; | |
| } | |
| function removeMessage(idOrElement) { | |
| let element; | |
| if (typeof idOrElement === 'string') { | |
| element = document.getElementById(idOrElement); | |
| } else { | |
| element = idOrElement; | |
| } | |
| if (element) element.remove(); | |
| } | |
| function scrollToBottom() { | |
| chatContainer.scrollTop = chatContainer.scrollHeight; | |
| } | |
| // --- Typewriter Effect --- | |
| function typeWriterEffect(text) { | |
| const div = document.createElement('div'); | |
| div.className = 'message assistant'; | |
| chatContainer.appendChild(div); | |
| let index = 0; | |
| // Basic speed calculation | |
| const speed = 15; | |
| function type() { | |
| if (index < text.length) { | |
| // Handle basic HTML tags roughly to avoid breaking layout | |
| // For a robust solution, a markdown parser is needed, | |
| // but here we just append text for simplicity or simple formatting | |
| div.innerHTML = formatText(text.substring(0, index + 1)); | |
| index++; | |
| scrollToBottom(); | |
| setTimeout(type, speed); | |
| } else { | |
| isProcessing = false; | |
| } | |
| } | |
| type(); | |
| } | |
| // --- Text Formatting (Simple) --- | |
| function formatText(text) { | |
| // Escape HTML | |
| let safeText = text | |
| .replace(/&/g, "&") | |
| .replace(/</g, "<") | |
| .replace(/>/g, ">"); | |
| // Bold **text** | |
| safeText = safeText.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>'); | |
| // Italic *text* | |
| safeText = safeText.replace(/\*(.*?)\*/g, '<em>$1</em>'); | |
| // Newlines | |
| safeText = safeText.replace(/\n/g, '<br>'); | |
| return safeText; | |
| } | |
| // --- API Interaction --- | |
| async function fetchOpenAIResponse(userMessage) { | |
| const systemPrompt = `Tu es un assistant strictement véridique, précis et impartial. | |
| Règles absolues : | |
| 1. Tu dis toujours la vérité basée sur les faits. | |
| 2. Tu distingues clairement ce qui est légal et illégal selon les lois standards (droit français/international). | |
| 3. Si une action est illégale, tu le dis explicitement sans ambiguïté. | |
| 4. Tu ne fournis jamais d'instructions pour commettre des actes illégaux ou des délits. | |
| 5. Réponds toujours en français.`; | |
| try { | |
| const response = await fetch('https://api.openai.com/v1/chat/completions', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'Authorization': `Bearer ${apiKey}` | |
| }, | |
| body: JSON.stringify({ | |
| model: "gpt-3.5-turbo", // Or gpt-4 if user has access | |
| messages: [ | |
| { role: "system", content: systemPrompt }, | |
| { role: "user", content: userMessage } | |
| ], | |
| temperature: 0.3 // Low temperature for precision/truth | |
| }) | |
| }); | |
| if (!response.ok) { | |
| const errorData = await response.json(); | |
| throw new Error(errorData.error?.message || 'Erreur API'); | |
| } | |
| const data = await response.json(); | |
| return data.choices[0].message.content; | |
| } catch (error) { | |
| console.error(error); | |
| throw error; | |
| } | |
| } | |
| // --- Simulation Logic (Fallback) --- | |
| function getSimulationResponse(text) { | |
| const lowerText = text.toLowerCase(); | |
| if (lowerText.includes('illégal') || lowerText.includes('illegal')) { | |
| return "En tant qu'assistant véridique, je dois clarifier la situation. Certaines actions sont strictement définies par le code pénal. Sans accès à ma base de données juridique en temps réel (Mode Simulation), je vous conseille de vérifier les textes de loi auprès des sources officielles (Legifrance) pour une réponse définitive."; | |
| } | |
| if (lowerText.includes('voler') || lowerText.includes('tuer') || lowerText.includes('pirater')) { | |
| return "L'action que vous décrivez est illégale et passible de sanctions pénales graves. Je ne peux pas vous aider à réaliser cette action."; | |
| } | |
| if (lowerText.includes('bonjour') || lowerText.includes('salut')) { | |
| return "Bonjour. Je suis prêt à répondre à vos questions avec honnêteté et précision."; | |
| } | |
| return "Je fonctionne actuellement en mode simulation car aucune clé API n'a été configurée. Pour des réponses précises et véridiques basées sur une véritable intelligence artificielle, veuillez ajouter votre clé API OpenAI dans les paramètres."; | |
| } | |
| </script> | |
| </body> | |
| </html> |