// Configuration globale let apiUrl = "http://localhost:8000"; // État de l'application const appState = { currentTab: "sentiment", apiConnected: false }; // Initialisation de l'application document.addEventListener("DOMContentLoaded", function () { initializeApp(); }); function initializeApp() { setupEventListeners(); checkApiStatus(); loadExamplesData(); } function setupEventListeners() { // Navigation entre les onglets document.querySelectorAll(".nav-tab").forEach((tab) => { tab.addEventListener("click", (e) => { const tabName = e.currentTarget.dataset.tab; switchTab(tabName); }); }); // Changement d'URL API document.getElementById("apiUrl").addEventListener("change", function () { apiUrl = this.value; checkApiStatus(); }); // Soumission des formulaires setupFormHandlers(); } function setupFormHandlers() { // Empêcher la soumission par défaut de tous les formulaires document.querySelectorAll("form").forEach((form) => { form.addEventListener("submit", (e) => { e.preventDefault(); }); }); } // Navigation function switchTab(tabName) { // Mise à jour de l'état appState.currentTab = tabName; // Mise à jour des onglets document.querySelectorAll(".nav-tab").forEach((tab) => { tab.classList.remove("active"); }); document.querySelector(`[data-tab="${tabName}"]`).classList.add("active"); // Mise à jour du contenu document.querySelectorAll(".tab-content").forEach((content) => { content.classList.remove("active"); }); document.getElementById(tabName).classList.add("active"); } // Vérification du statut de l'API async function checkApiStatus() { const statusElement = document.getElementById("apiStatus"); const indicator = statusElement.querySelector(".status-indicator"); const statusText = statusElement.querySelector("span"); const testButton = document.querySelector("button[onclick='checkApiStatus()']"); // Feedback visuel pendant le test if (testButton) { testButton.disabled = true; testButton.innerHTML = 'Test en cours...'; testButton.classList.add("loading"); } // Indicateur de chargement indicator.className = "status-indicator loading"; statusText.textContent = "Test de connexion..."; try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); const response = await fetch(`${apiUrl}/health`, { method: "GET", signal: controller.signal }); clearTimeout(timeoutId); if (response.ok) { const healthData = await response.json(); appState.apiConnected = true; indicator.className = "status-indicator online"; statusText.textContent = "API Connectée"; // Notification de succès showNotification("✅ Connexion API établie avec succès!", "success"); // Afficher les détails de l'API showApiDetails(healthData); } else { throw new Error(`Erreur HTTP ${response.status}`); } } catch (error) { appState.apiConnected = false; indicator.className = "status-indicator offline"; statusText.textContent = "API Déconnectée"; let errorMessage = "❌ Impossible de se connecter à l'API"; if (error.name === "AbortError") { errorMessage += " (Timeout)"; } else if (error.message.includes("fetch")) { errorMessage += " (Serveur inaccessible)"; } else { errorMessage += ` (${error.message})`; } showNotification(errorMessage, "error"); console.warn("Erreur de connexion API:", error.message); } finally { // Restaurer le bouton if (testButton) { testButton.disabled = false; testButton.innerHTML = '🔄Tester la connexion'; testButton.classList.remove("loading"); } } } // Système de notifications function showNotification(message, type = "info", duration = 5000) { // Créer le conteneur de notifications s'il n'existe pas let notificationContainer = document.getElementById("notification-container"); if (!notificationContainer) { notificationContainer = document.createElement("div"); notificationContainer.id = "notification-container"; notificationContainer.className = "notification-container"; document.body.appendChild(notificationContainer); } // Créer la notification const notification = document.createElement("div"); notification.className = `notification notification-${type}`; notification.innerHTML = `
${message}
`; // Ajouter l'animation d'entrée notification.style.transform = "translateX(100%)"; notification.style.opacity = "0"; notificationContainer.appendChild(notification); // Animation d'entrée setTimeout(() => { notification.style.transform = "translateX(0)"; notification.style.opacity = "1"; }, 10); // Suppression automatique if (duration > 0) { setTimeout(() => { if (notification.parentElement) { notification.style.transform = "translateX(100%)"; notification.style.opacity = "0"; setTimeout(() => notification.remove(), 300); } }, duration); } } // Affichage des détails de l'API function showApiDetails(healthData) { const existingDetails = document.getElementById("api-details"); if (existingDetails) { existingDetails.remove(); } const configSection = document.querySelector(".config-section .config-card"); const detailsDiv = document.createElement("div"); detailsDiv.id = "api-details"; detailsDiv.className = "api-details"; detailsDiv.innerHTML = `

📊 Détails de l'API

Statut: ✅ ${healthData.status || "healthy"}
Pipelines chargés: ${healthData.pipelines_loaded || 0}
Pipelines disponibles: ${(healthData.available_pipelines || []).join(", ") || "Aucun"}
`; configSection.appendChild(detailsDiv); } async function makeApiRequest(endpoint, data) { if (!appState.apiConnected) { throw new Error("API non connectée. Vérifiez la configuration."); } try { const response = await fetch(`${apiUrl}${endpoint}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }); const result = await response.json(); if (!response.ok) { throw new Error(result.detail || `Erreur ${response.status}`); } return result; } catch (error) { if (error.name === "TypeError" && error.message.includes("fetch")) { throw new Error("Impossible de contacter l'API. Vérifiez que le serveur est démarré."); } throw error; } } // Gestion de l'affichage des résultats améliorée function showLoading(containerId) { const container = document.getElementById(containerId); container.innerHTML = `

🔄 Traitement en cours...

Analyse de votre texte par l'IA

`; container.classList.add("show"); } function showResult(containerId, data, isError = false) { const container = document.getElementById(containerId); const headerClass = isError ? "error" : "success"; const icon = isError ? "❌" : "✅"; const title = isError ? "Erreur" : "Résultat"; let formattedContent; if (isError) { formattedContent = formatErrorResult(data); } else { formattedContent = formatResult(data, containerId); } // Ajouter un timestamp const timestamp = new Date().toLocaleTimeString("fr-FR"); container.innerHTML = `
${icon}
${title} ${timestamp}
${formattedContent}
`; container.classList.add("show"); // Animation d'entrée const resultCard = container.querySelector(".result-card"); resultCard.style.transform = "translateY(20px)"; resultCard.style.opacity = "0"; setTimeout(() => { resultCard.style.transform = "translateY(0)"; resultCard.style.opacity = "1"; }, 10); } function formatErrorResult(error) { let errorMessage = "Une erreur inattendue s'est produite"; let suggestions = []; if (typeof error === "object" && error.error) { errorMessage = error.error; // Suggestions basées sur le type d'erreur if (errorMessage.includes("API non connectée")) { suggestions.push("Vérifiez que le serveur API est démarré"); suggestions.push("Testez la connexion avec le bouton 'Tester la connexion'"); } else if (errorMessage.includes("requis")) { suggestions.push("Assurez-vous que tous les champs obligatoires sont remplis"); } else if (errorMessage.includes("MASK")) { suggestions.push("Utilisez [MASK] dans votre texte pour le fill-mask"); } } let suggestionsHtml = ""; if (suggestions.length > 0) { suggestionsHtml = `

💡 Suggestions:

`; } return `
Message d'erreur:

${errorMessage}

${suggestionsHtml}
`; } function formatResult(data, containerId) { const type = containerId.replace("Result", ""); switch (type) { case "sentiment": return formatSentimentResult(data); case "ner": return formatNerResult(data); case "qa": return formatQaResult(data); case "fillmask": return formatFillmaskResult(data); case "moderation": return formatModerationResult(data); case "textgen": return formatTextgenResult(data); case "batch": return formatBatchResult(data); default: return `
${JSON.stringify(data, null, 2)}
`; } } function formatSentimentResult(data) { const sentiment = data.sentiment || data.label; const confidence = data.confidence || data.score; const badgeClass = sentiment?.toLowerCase() === "positive" ? "positive" : sentiment?.toLowerCase() === "negative" ? "negative" : "neutral"; // Calcul de la barre de progression pour la confiance const confidencePercent = confidence ? (confidence * 100).toFixed(1) : 0; const progressColor = confidencePercent > 80 ? "var(--success-500)" : confidencePercent > 60 ? "var(--warning-500)" : "var(--error-500)"; return `
Sentiment détecté: ${sentiment || "Non déterminé"}
Niveau de confiance: ${confidencePercent}%
${getSentimentInterpretation(sentiment, confidence)}
🔍 Détails techniques
${JSON.stringify(data, null, 2)}
`; } function formatNerResult(data) { let entitiesHtml = ""; let entitiesStats = {}; if (data.entities && data.entities.length > 0) { // Compter les entités par type data.entities.forEach((entity) => { const label = entity.label; entitiesStats[label] = (entitiesStats[label] || 0) + 1; }); entitiesHtml = data.entities .map((entity, index) => { const label = entity.label?.toLowerCase() || "misc"; const confidence = entity.confidence ? (entity.confidence * 100).toFixed(1) : null; return `
${entity.text} ${entity.label} ${confidence ? `${confidence}%` : ""}
`; }) .join(""); } else { entitiesHtml = `
🔍 Aucune entité nommée détectée dans ce texte
`; } // Statistiques des entités const statsHtml = Object.keys(entitiesStats).length > 0 ? `

📊 Statistiques des entités:

${Object.entries(entitiesStats) .map( ([type, count]) => `
${type} ${count}
` ) .join("")}
` : ""; return `

🏷️ Entités détectées:

${entitiesHtml}
${statsHtml}
🔍 Détails techniques
${JSON.stringify(data, null, 2)}
`; } function formatQaResult(data) { const confidence = data.confidence ? (data.confidence * 100).toFixed(1) : null; const progressColor = confidence > 80 ? "var(--success-500)" : confidence > 60 ? "var(--warning-500)" : "var(--error-500)"; return `

❓ Question:

${data.question}

💡 Réponse:

${data.answer || "Aucune réponse trouvée dans le contexte fourni"}
${ confidence ? `
Fiabilité de la réponse: ${confidence}%
` : "" }
🔍 Détails techniques
${JSON.stringify(data, null, 2)}
`; } function formatFillmaskResult(data) { let predictionsHtml = ""; if (data.predictions && data.predictions.length > 0) { predictionsHtml = data.predictions .map((pred, index) => { const score = pred.score ? (pred.score * 100).toFixed(1) : null; const rankClass = index === 0 ? "rank-first" : index === 1 ? "rank-second" : "rank-other"; return `
#${index + 1}
${pred.token || pred.token_str} ${score ? `${score}%` : ""}
${ score ? `
` : "" }
`; }) .join(""); } else { predictionsHtml = `
🔍 Aucune prédiction disponible
`; } return `

🎭 Mots prédits pour remplacer [MASK]:

${predictionsHtml}
🔍 Détails techniques
${JSON.stringify(data, null, 2)}
`; } function formatModerationResult(data) { const flagged = data.flagged; const badgeClass = flagged ? "negative" : "positive"; const status = flagged ? "CONTENU SIGNALÉ" : "CONTENU APPROPRIÉ"; const icon = flagged ? "⚠️" : "✅"; let categoriesHtml = ""; if (data.categories) { const categories = Object.entries(data.categories); if (categories.length > 0) { categoriesHtml = `

📋 Détails de l'analyse:

${categories .map(([key, value]) => { let displayKey = key; let displayValue = value; if (key === "toxic_score") { displayKey = "Score de toxicité"; displayValue = typeof value === "number" ? `${(value * 100).toFixed(1)}%` : value; } else if (key === "is_modified") { displayKey = "Contenu modifié"; displayValue = value ? "Oui" : "Non"; } else if (key === "words_replaced") { displayKey = "Mots remplacés"; } else if (key === "restored_text") { displayKey = "Texte restauré"; } return `
${displayKey}: ${displayValue}
`; }) .join("")}
`; } } return `
${icon} ${status}
${categoriesHtml}
🔍 Détails techniques
${JSON.stringify(data, null, 2)}
`; } function formatTextgenResult(data) { const generatedText = data.generated_text || "Aucun texte généré"; const prompt = data.prompt || ""; return `

📝 Prompt initial:

${prompt}

✨ Texte généré:

${generatedText}
🔍 Détails techniques
${JSON.stringify(data, null, 2)}
`; } function formatBatchResult(data) { if (!data.results || data.results.length === 0) { return "Aucun résultat"; } const resultsHtml = data.results .map((result, index) => { return `
Résultat ${index + 1}
${JSON.stringify(result, null, 2)}
`; }) .join(""); return `
Résumé: ${data.processed_count} traités, ${data.failed_count} échecs
${resultsHtml}
`; } // Fonctions de traitement pour chaque endpoint async function analyzeSentiment(event) { event.preventDefault(); showLoading("sentimentResult"); const text = document.getElementById("sentimentText").value.trim(); const model = document.getElementById("sentimentModel").value; if (!text) { showResult("sentimentResult", { error: "Le texte est requis" }, true); return; } try { const data = { text }; if (model) data.model_name = model; const result = await makeApiRequest("/sentiment", data); showResult("sentimentResult", result); } catch (error) { showResult("sentimentResult", { error: error.message }, true); } } async function analyzeNER(event) { event.preventDefault(); showLoading("nerResult"); const text = document.getElementById("nerText").value.trim(); const model = document.getElementById("nerModel").value; if (!text) { showResult("nerResult", { error: "Le texte est requis" }, true); return; } try { const data = { text }; if (model) data.model_name = model; const result = await makeApiRequest("/ner", data); showResult("nerResult", result); } catch (error) { showResult("nerResult", { error: error.message }, true); } } async function answerQuestion(event) { event.preventDefault(); showLoading("qaResult"); const question = document.getElementById("qaQuestion").value.trim(); const context = document.getElementById("qaContext").value.trim(); const model = document.getElementById("qaModel").value; if (!question || !context) { showResult("qaResult", { error: "La question et le contexte sont requis" }, true); return; } try { const data = { question, context }; if (model) data.model_name = model; const result = await makeApiRequest("/qa", data); showResult("qaResult", result); } catch (error) { showResult("qaResult", { error: error.message }, true); } } async function fillMask(event) { event.preventDefault(); showLoading("fillmaskResult"); const text = document.getElementById("fillmaskText").value.trim(); const model = document.getElementById("fillmaskModel").value; if (!text) { showResult("fillmaskResult", { error: "Le texte est requis" }, true); return; } if (!text.includes("[MASK]")) { showResult("fillmaskResult", { error: "Le texte doit contenir [MASK]" }, true); return; } try { const data = { text }; if (model) data.model_name = model; const result = await makeApiRequest("/fillmask", data); showResult("fillmaskResult", result); } catch (error) { showResult("fillmaskResult", { error: error.message }, true); } } async function moderateContent(event) { event.preventDefault(); showLoading("moderationResult"); const text = document.getElementById("moderationText").value.trim(); const model = document.getElementById("moderationModel").value; if (!text) { showResult("moderationResult", { error: "Le texte est requis" }, true); return; } try { const data = { text }; if (model) data.model_name = model; const result = await makeApiRequest("/moderation", data); showResult("moderationResult", result); } catch (error) { showResult("moderationResult", { error: error.message }, true); } } async function generateText(event) { event.preventDefault(); showLoading("textgenResult"); const text = document.getElementById("textgenPrompt").value.trim(); const model = document.getElementById("textgenModel").value; if (!text) { showResult("textgenResult", { error: "Le prompt est requis" }, true); return; } try { const data = { text }; if (model) data.model_name = model; const result = await makeApiRequest("/textgen", data); showResult("textgenResult", result); } catch (error) { showResult("textgenResult", { error: error.message }, true); } } async function processBatch() { showLoading("batchResult"); const type = document.getElementById("batchType").value; const textsInput = document.getElementById("batchTexts").value.trim(); if (!textsInput) { showResult("batchResult", { error: "Les textes sont requis" }, true); return; } const texts = textsInput .split("\n") .filter((line) => line.trim()) .map((line) => line.trim()); if (texts.length === 0) { showResult("batchResult", { error: "Aucun texte valide fourni" }, true); return; } try { const data = { texts }; const result = await makeApiRequest(`/${type}/batch`, data); showResult("batchResult", result); } catch (error) { showResult("batchResult", { error: error.message }, true); } } // Exemples prédéfinis (en anglais pour optimiser la compatibilité avec les modèles) const examples = { sentiment: "I love this project! It's really well designed and useful for testing NLP APIs.", ner: "Apple Inc. is an American multinational technology company headquartered in Cupertino, California. Tim Cook is the current CEO of Apple.", qa: { question: "What is the capital of France?", context: "France is a country located in Western Europe. Paris is the capital and largest city of France. The city is famous for the Eiffel Tower and the Louvre Museum." }, fillmask: "The capital of France is [MASK].", moderation: "This project is fantastic! Thank you for this excellent work.", textgen: "Once upon a time, in a distant kingdom,", batch: `I love this product! This is really terrible. Not bad at all. Excellent work! I hate all of this.` }; function loadExample(type) { const example = examples[type]; if (!example) return; switch (type) { case "sentiment": document.getElementById("sentimentText").value = example; break; case "ner": document.getElementById("nerText").value = example; break; case "qa": document.getElementById("qaQuestion").value = example.question; document.getElementById("qaContext").value = example.context; break; case "fillmask": document.getElementById("fillmaskText").value = example; break; case "moderation": document.getElementById("moderationText").value = example; break; case "textgen": document.getElementById("textgenPrompt").value = example; break; case "batch": document.getElementById("batchTexts").value = example; break; } } function loadExamplesData() { // Cette fonction peut être étendue pour charger des exemples depuis une source externe console.log("Exemples chargés"); } // Fonctions utilitaires pour l'UX function getSentimentInterpretation(sentiment, confidence) { const confidenceLevel = confidence > 0.8 ? "très élevée" : confidence > 0.6 ? "élevée" : confidence > 0.4 ? "modérée" : "faible"; let interpretation = ""; if (sentiment?.toLowerCase() === "positive") { interpretation = `😊 Le texte exprime un sentiment positif avec une confiance ${confidenceLevel}.`; } else if (sentiment?.toLowerCase() === "negative") { interpretation = `😔 Le texte exprime un sentiment négatif avec une confiance ${confidenceLevel}.`; } else { interpretation = `😐 Le sentiment du texte est neutre avec une confiance ${confidenceLevel}.`; } return `
${interpretation}
`; } function copyToClipboard(text) { navigator.clipboard .writeText(text) .then(() => { showNotification("📋 Texte copié dans le presse-papiers!", "success", 2000); }) .catch(() => { showNotification("❌ Impossible de copier le texte", "error", 2000); }); } function copyResultToClipboard(containerId) { const container = document.getElementById(containerId); const resultData = container.querySelector(".result-json"); if (resultData) { copyToClipboard(resultData.textContent); } } function toggleResultDetails(containerId) { const container = document.getElementById(containerId); const details = container.querySelector(".result-details"); if (details) { details.open = !details.open; } } function regenerateText() { const currentPrompt = document.getElementById("textgenPrompt").value; if (currentPrompt) { const form = document.querySelector("#textgen form"); const event = new Event("submit"); generateText(event); } } // Amélioration des fonctions de traitement avec feedback async function analyzeSentiment(event) { event.preventDefault(); showLoading("sentimentResult"); const text = document.getElementById("sentimentText").value.trim(); const model = document.getElementById("sentimentModel").value; if (!text) { showResult("sentimentResult", { error: "Le texte est requis pour l'analyse de sentiment" }, true); return; } try { showNotification("🔄 Analyse de sentiment en cours...", "info", 2000); const data = { text }; if (model) data.model_name = model; const result = await makeApiRequest("/sentiment", data); showResult("sentimentResult", result); showNotification("✅ Analyse de sentiment terminée!", "success", 3000); } catch (error) { showResult("sentimentResult", { error: error.message }, true); showNotification("❌ Erreur lors de l'analyse de sentiment", "error", 4000); } } async function analyzeNER(event) { event.preventDefault(); showLoading("nerResult"); const text = document.getElementById("nerText").value.trim(); const model = document.getElementById("nerModel").value; if (!text) { showResult("nerResult", { error: "Le texte est requis pour la reconnaissance d'entités" }, true); return; } try { showNotification("🔄 Reconnaissance d'entités en cours...", "info", 2000); const data = { text }; if (model) data.model_name = model; const result = await makeApiRequest("/ner", data); showResult("nerResult", result); const entityCount = result.entities ? result.entities.length : 0; showNotification(`✅ ${entityCount} entité(s) détectée(s)!`, "success", 3000); } catch (error) { showResult("nerResult", { error: error.message }, true); showNotification("❌ Erreur lors de la reconnaissance d'entités", "error", 4000); } } async function answerQuestion(event) { event.preventDefault(); showLoading("qaResult"); const question = document.getElementById("qaQuestion").value.trim(); const context = document.getElementById("qaContext").value.trim(); const model = document.getElementById("qaModel").value; if (!question || !context) { showResult("qaResult", { error: "La question et le contexte sont requis" }, true); return; } try { showNotification("🔄 Recherche de réponse en cours...", "info", 2000); const data = { question, context }; if (model) data.model_name = model; const result = await makeApiRequest("/qa", data); showResult("qaResult", result); showNotification("✅ Réponse trouvée!", "success", 3000); } catch (error) { showResult("qaResult", { error: error.message }, true); showNotification("❌ Erreur lors de la recherche de réponse", "error", 4000); } }