1058 lines
31 KiB
JavaScript
1058 lines
31 KiB
JavaScript
// 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 = '<span class="btn-icon">⏳</span>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 = '<span class="btn-icon">🔄</span>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 = `
|
||
<div class="notification-content">
|
||
<span class="notification-message">${message}</span>
|
||
<button class="notification-close" onclick="this.parentElement.parentElement.remove()">×</button>
|
||
</div>
|
||
`;
|
||
|
||
// 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 = `
|
||
<div class="api-details-header">
|
||
<h4>📊 Détails de l'API</h4>
|
||
<button class="btn-minimal" onclick="this.parentElement.parentElement.style.display='none'">×</button>
|
||
</div>
|
||
<div class="api-details-content">
|
||
<div class="detail-item">
|
||
<span class="detail-label">Statut:</span>
|
||
<span class="detail-value success">✅ ${healthData.status || "healthy"}</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Pipelines chargés:</span>
|
||
<span class="detail-value">${healthData.pipelines_loaded || 0}</span>
|
||
</div>
|
||
<div class="detail-item">
|
||
<span class="detail-label">Pipelines disponibles:</span>
|
||
<span class="detail-value">${(healthData.available_pipelines || []).join(", ") || "Aucun"}</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
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 = `
|
||
<div class="loading-container">
|
||
<div class="loading-spinner">
|
||
<div class="spinner"></div>
|
||
</div>
|
||
<div class="loading-content">
|
||
<h4>🔄 Traitement en cours...</h4>
|
||
<p>Analyse de votre texte par l'IA</p>
|
||
<div class="loading-progress">
|
||
<div class="progress-bar"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
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 = `
|
||
<div class="result-card">
|
||
<div class="result-header ${headerClass}">
|
||
<div class="result-header-main">
|
||
<span class="result-icon">${icon}</span>
|
||
<div class="result-title">
|
||
<span class="title">${title}</span>
|
||
<span class="timestamp">${timestamp}</span>
|
||
</div>
|
||
</div>
|
||
<div class="result-actions">
|
||
<button class="btn-minimal" onclick="copyResultToClipboard('${containerId}')" title="Copier le résultat">
|
||
📋
|
||
</button>
|
||
<button class="btn-minimal" onclick="toggleResultDetails('${containerId}')" title="Afficher/Masquer les détails JSON">
|
||
🔍
|
||
</button>
|
||
<button class="btn-minimal" onclick="document.getElementById('${containerId}').classList.remove('show')" title="Fermer">
|
||
×
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="result-content">
|
||
${formattedContent}
|
||
</div>
|
||
</div>
|
||
`;
|
||
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 = `
|
||
<div class="error-suggestions">
|
||
<h4>💡 Suggestions:</h4>
|
||
<ul>
|
||
${suggestions.map((s) => `<li>${s}</li>`).join("")}
|
||
</ul>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
return `
|
||
<div class="error-content">
|
||
<div class="error-message">
|
||
<strong>Message d'erreur:</strong>
|
||
<p>${errorMessage}</p>
|
||
</div>
|
||
${suggestionsHtml}
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
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 `<div class="result-json">${JSON.stringify(data, null, 2)}</div>`;
|
||
}
|
||
}
|
||
|
||
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 `
|
||
<div class="sentiment-result">
|
||
<div class="sentiment-main">
|
||
<div class="sentiment-label">
|
||
<span class="label-title">Sentiment détecté:</span>
|
||
<span class="badge ${badgeClass}">${sentiment || "Non déterminé"}</span>
|
||
</div>
|
||
<div class="confidence-section">
|
||
<div class="confidence-header">
|
||
<span class="confidence-title">Niveau de confiance:</span>
|
||
<span class="confidence-value">${confidencePercent}%</span>
|
||
</div>
|
||
<div class="confidence-bar">
|
||
<div class="confidence-progress" style="width: ${confidencePercent}%; background-color: ${progressColor}"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="sentiment-interpretation">
|
||
${getSentimentInterpretation(sentiment, confidence)}
|
||
</div>
|
||
<details class="result-details">
|
||
<summary>🔍 Détails techniques</summary>
|
||
<div class="result-json">${JSON.stringify(data, null, 2)}</div>
|
||
</details>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
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 `
|
||
<div class="entity-item">
|
||
<span class="entity ${label}" title="Confiance: ${confidence || "N/A"}%">
|
||
${entity.text}
|
||
</span>
|
||
<span class="entity-label">${entity.label}</span>
|
||
${confidence ? `<span class="entity-confidence">${confidence}%</span>` : ""}
|
||
</div>
|
||
`;
|
||
})
|
||
.join("");
|
||
} else {
|
||
entitiesHtml = `<div class="no-entities">🔍 Aucune entité nommée détectée dans ce texte</div>`;
|
||
}
|
||
|
||
// Statistiques des entités
|
||
const statsHtml =
|
||
Object.keys(entitiesStats).length > 0
|
||
? `
|
||
<div class="entities-stats">
|
||
<h4>📊 Statistiques des entités:</h4>
|
||
<div class="stats-grid">
|
||
${Object.entries(entitiesStats)
|
||
.map(
|
||
([type, count]) => `
|
||
<div class="stat-item">
|
||
<span class="stat-type">${type}</span>
|
||
<span class="stat-count">${count}</span>
|
||
</div>
|
||
`
|
||
)
|
||
.join("")}
|
||
</div>
|
||
</div>
|
||
`
|
||
: "";
|
||
|
||
return `
|
||
<div class="ner-result">
|
||
<div class="entities-section">
|
||
<h4>🏷️ Entités détectées:</h4>
|
||
<div class="entities-container">
|
||
${entitiesHtml}
|
||
</div>
|
||
</div>
|
||
${statsHtml}
|
||
<details class="result-details">
|
||
<summary>🔍 Détails techniques</summary>
|
||
<div class="result-json">${JSON.stringify(data, null, 2)}</div>
|
||
</details>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
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 `
|
||
<div class="qa-result">
|
||
<div class="qa-main">
|
||
<div class="qa-question">
|
||
<h4>❓ Question:</h4>
|
||
<p class="question-text">${data.question}</p>
|
||
</div>
|
||
<div class="qa-answer">
|
||
<h4>💡 Réponse:</h4>
|
||
<div class="answer-text">${data.answer || "Aucune réponse trouvée dans le contexte fourni"}</div>
|
||
</div>
|
||
${
|
||
confidence
|
||
? `
|
||
<div class="qa-confidence">
|
||
<div class="confidence-header">
|
||
<span>Fiabilité de la réponse:</span>
|
||
<span class="confidence-value">${confidence}%</span>
|
||
</div>
|
||
<div class="confidence-bar">
|
||
<div class="confidence-progress" style="width: ${confidence}%; background-color: ${progressColor}"></div>
|
||
</div>
|
||
</div>
|
||
`
|
||
: ""
|
||
}
|
||
</div>
|
||
<details class="result-details">
|
||
<summary>🔍 Détails techniques</summary>
|
||
<div class="result-json">${JSON.stringify(data, null, 2)}</div>
|
||
</details>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
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 `
|
||
<div class="prediction-item ${rankClass}">
|
||
<div class="prediction-rank">#${index + 1}</div>
|
||
<div class="prediction-content">
|
||
<span class="prediction-token">${pred.token || pred.token_str}</span>
|
||
${score ? `<span class="prediction-score">${score}%</span>` : ""}
|
||
</div>
|
||
${
|
||
score
|
||
? `
|
||
<div class="prediction-bar">
|
||
<div class="prediction-progress" style="width: ${score}%"></div>
|
||
</div>
|
||
`
|
||
: ""
|
||
}
|
||
</div>
|
||
`;
|
||
})
|
||
.join("");
|
||
} else {
|
||
predictionsHtml = `<div class="no-predictions">🔍 Aucune prédiction disponible</div>`;
|
||
}
|
||
|
||
return `
|
||
<div class="fillmask-result">
|
||
<div class="predictions-section">
|
||
<h4>🎭 Mots prédits pour remplacer [MASK]:</h4>
|
||
<div class="predictions-container">
|
||
${predictionsHtml}
|
||
</div>
|
||
</div>
|
||
<details class="result-details">
|
||
<summary>🔍 Détails techniques</summary>
|
||
<div class="result-json">${JSON.stringify(data, null, 2)}</div>
|
||
</details>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
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 = `
|
||
<div class="moderation-details">
|
||
<h4>📋 Détails de l'analyse:</h4>
|
||
<div class="moderation-categories">
|
||
${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 `
|
||
<div class="category-item">
|
||
<span class="category-label">${displayKey}:</span>
|
||
<span class="category-value">${displayValue}</span>
|
||
</div>
|
||
`;
|
||
})
|
||
.join("")}
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
return `
|
||
<div class="moderation-result">
|
||
<div class="moderation-status ${badgeClass}">
|
||
<span class="status-icon">${icon}</span>
|
||
<span class="status-text">${status}</span>
|
||
</div>
|
||
${categoriesHtml}
|
||
<details class="result-details">
|
||
<summary>🔍 Détails techniques</summary>
|
||
<div class="result-json">${JSON.stringify(data, null, 2)}</div>
|
||
</details>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function formatTextgenResult(data) {
|
||
const generatedText = data.generated_text || "Aucun texte généré";
|
||
const prompt = data.prompt || "";
|
||
|
||
return `
|
||
<div class="textgen-result">
|
||
<div class="textgen-prompt">
|
||
<h4>📝 Prompt initial:</h4>
|
||
<div class="prompt-text">${prompt}</div>
|
||
</div>
|
||
<div class="textgen-output">
|
||
<h4>✨ Texte généré:</h4>
|
||
<div class="generated-text">
|
||
<div class="text-content">${generatedText}</div>
|
||
<div class="text-actions">
|
||
<button class="btn-minimal" onclick="copyToClipboard('${generatedText.replace(/'/g, "\\'")}')">
|
||
📋 Copier le texte
|
||
</button>
|
||
<button class="btn-minimal" onclick="regenerateText()">
|
||
🔄 Régénérer
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<details class="result-details">
|
||
<summary>🔍 Détails techniques</summary>
|
||
<div class="result-json">${JSON.stringify(data, null, 2)}</div>
|
||
</details>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function formatBatchResult(data) {
|
||
if (!data.results || data.results.length === 0) {
|
||
return "<em>Aucun résultat</em>";
|
||
}
|
||
|
||
const resultsHtml = data.results
|
||
.map((result, index) => {
|
||
return `
|
||
<div class="batch-item">
|
||
<div class="batch-item-header">Résultat ${index + 1}</div>
|
||
<div class="result-json">${JSON.stringify(result, null, 2)}</div>
|
||
</div>
|
||
`;
|
||
})
|
||
.join("");
|
||
|
||
return `
|
||
<div style="margin-bottom: 1rem;">
|
||
<strong>Résumé:</strong> ${data.processed_count} traités, ${data.failed_count} échecs
|
||
</div>
|
||
<div class="batch-results">
|
||
${resultsHtml}
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// 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 `<div class="interpretation">${interpretation}</div>`;
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|