ai-lab-transformers-playground/ui/script.js

1363 lines
41 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Configuration globale
let apiUrl = "http://localhost:8000";
// État de l'application
const appState = {
currentTab: "sentiment",
apiConnected: false,
textgenResults: {} // Pour stocker les textes originaux des résultats textgen
};
// 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();
});
});
}
// Fonctions de gestion du Markdown
function renderMarkdown(text) {
try {
// Configuration de marked pour la sécurité et les fonctionnalités
if (typeof marked !== "undefined") {
marked.setOptions({
breaks: true, // Convertir les retours à la ligne en <br>
gfm: true, // GitHub Flavored Markdown
sanitize: false, // Permet le HTML (attention à la sécurité)
smartLists: true,
smartypants: true
});
return marked.parse(text);
} else {
console.warn("Marked.js non disponible, affichage du texte brut");
return escapeHtml(text).replace(/\n/g, "<br>");
}
} catch (error) {
console.error("Erreur lors du rendu Markdown:", error);
return escapeHtml(text).replace(/\n/g, "<br>");
}
}
function escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
function toggleMarkdownView(containerId, text) {
const container = document.getElementById(containerId);
// Chercher dans le container principal ou dans un élément de génération
const textElement = container.querySelector(".text-content") || container.querySelector(".generation-text");
const toggleButton = container.querySelector(".markdown-toggle");
if (!textElement || !toggleButton) {
console.warn("Éléments non trouvés pour le toggle Markdown:", containerId);
return;
}
const isMarkdownView = toggleButton.dataset.view === "markdown";
if (isMarkdownView) {
// Passer en vue texte brut
textElement.innerHTML = escapeHtml(text).replace(/\n/g, "<br>");
textElement.classList.remove("markdown-content");
textElement.classList.add("raw-text");
toggleButton.innerHTML = "📝 Vue Markdown";
toggleButton.dataset.view = "raw";
} else {
// Passer en vue Markdown
textElement.innerHTML = renderMarkdown(text);
textElement.classList.remove("raw-text");
textElement.classList.add("markdown-content");
toggleButton.innerHTML = "📄 Texte brut";
toggleButton.dataset.view = "markdown";
}
}
// Nouvelle fonction pour gérer les toggles via data-attributes
function handleMarkdownToggle(button) {
const resultId = button.dataset.resultId;
const isMain = button.dataset.isMain === "true";
const genIndex = button.dataset.genIndex;
if (!resultId || !appState.textgenResults[resultId]) {
console.warn("Données du résultat textgen non trouvées");
return;
}
// Récupérer le texte original
let originalText;
if (isMain) {
originalText = appState.textgenResults[resultId].mainText;
} else if (genIndex !== undefined) {
originalText = appState.textgenResults[resultId].generations[parseInt(genIndex)];
} else {
console.warn("Type de texte non identifié");
return;
}
// Trouver le container et l'élément de texte
const container = button.closest(".textgen-output, .generation-item");
if (!container) {
console.warn("Container non trouvé");
return;
}
const textElement = container.querySelector(".text-content, .generation-text");
if (!textElement) {
console.warn("Élément de texte non trouvé");
return;
}
const isMarkdownView = button.dataset.view === "markdown";
if (isMarkdownView) {
// Passer en vue texte brut
textElement.innerHTML = escapeHtml(originalText).replace(/\n/g, "<br>");
textElement.classList.remove("markdown-content");
textElement.classList.add("raw-text");
button.innerHTML = "📝 Vue Markdown";
button.dataset.view = "raw";
} else {
// Passer en vue Markdown
textElement.innerHTML = renderMarkdown(originalText);
textElement.classList.remove("raw-text");
textElement.classList.add("markdown-content");
button.innerHTML = "📄 Texte brut";
button.dataset.view = "markdown";
}
}
function decodeHtml(html) {
const txt = document.createElement("textarea");
txt.innerHTML = html;
return txt.value;
}
// 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");
// Ajouter les event listeners pour les boutons markdown-toggle
setupMarkdownToggleListeners(container);
// 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);
}
// Fonction pour configurer les event listeners des boutons markdown
function setupMarkdownToggleListeners(container) {
// Event listeners pour les boutons markdown toggle
const toggleButtons = container.querySelectorAll(".markdown-toggle");
toggleButtons.forEach((button) => {
button.removeEventListener("click", handleMarkdownToggleClick);
button.addEventListener("click", handleMarkdownToggleClick);
});
// Event listeners pour les boutons de copie
const copyButtons = container.querySelectorAll(".copy-text-btn");
copyButtons.forEach((button) => {
button.removeEventListener("click", handleCopyTextClick);
button.addEventListener("click", handleCopyTextClick);
});
}
// Event handler pour les clics sur les boutons markdown toggle
function handleMarkdownToggleClick(event) {
event.preventDefault();
handleMarkdownToggle(event.target);
}
// Event handler pour les clics sur les boutons de copie
function handleCopyTextClick(event) {
event.preventDefault();
const textToCopy = decodeHtml(event.target.dataset.text);
copyToClipboard(textToCopy);
}
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");
} else if (errorMessage.includes("CUDA out of memory") || errorMessage.includes("mémoire")) {
suggestions.push("🔧 Essayez un modèle plus petit (GPT-2 au lieu de GPT-2 Medium)");
suggestions.push("📉 Réduisez le nombre de tokens à générer");
suggestions.push("🔢 Réduisez le nombre de séquences à générer");
suggestions.push("💻 Le modèle utilisera automatiquement le CPU si le GPU manque de mémoire");
} else if (errorMessage.includes("model") || errorMessage.includes("modèle")) {
suggestions.push("Essayez avec le modèle par défaut");
suggestions.push("Vérifiez que le nom du modèle est correct");
}
}
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 || "";
const systemPrompt = data.system_prompt || "";
const parameters = data.parameters || {};
const generations = data.generations || [];
// Créer un ID unique pour ce résultat
const resultId = "textgen_result_" + Date.now() + "_" + Math.random().toString(36).substr(2, 9);
// Stocker les données dans l'état global
appState.textgenResults[resultId] = {
mainText: generatedText,
generations: generations.map((gen) => gen.text || gen.continuation || "")
};
// Formatage du system prompt s'il existe
const systemPromptHtml = systemPrompt
? `
<div class="textgen-system-prompt">
<h4>🤖 Prompt System:</h4>
<div class="system-prompt-text">${systemPrompt}</div>
</div>
`
: "";
// Formatage des paramètres utilisés
const parametersHtml =
Object.keys(parameters).length > 0
? `
<div class="textgen-parameters">
<h4>⚙️ Paramètres utilisés:</h4>
<div class="parameters-grid">
<div class="param-item">
<span class="param-label">Tokens max:</span>
<span class="param-value">${parameters.max_new_tokens || "N/A"}</span>
</div>
<div class="param-item">
<span class="param-label">Séquences:</span>
<span class="param-value">${parameters.num_sequences || "N/A"}</span>
</div>
<div class="param-item">
<span class="param-label">Température:</span>
<span class="param-value">${parameters.temperature || "N/A"}</span>
</div>
<div class="param-item">
<span class="param-label">Échantillonnage:</span>
<span class="param-value">${parameters.do_sample ? "✅" : "❌"}</span>
</div>
</div>
</div>
`
: "";
// Formatage des générations multiples
const generationsHtml =
generations.length > 1
? `
<div class="textgen-generations">
<h4>🎯 Générations alternatives (${generations.length}):</h4>
<div class="generations-container">
${generations
.map((gen, index) => {
const genText = gen.text || gen.continuation || "Aucun texte";
const genId = `${resultId}_generation_${index}`;
return `
<div class="generation-item" data-index="${index}" id="${genId}">
<div class="generation-header">
<span class="generation-number">Génération ${index + 1}</span>
<button class="btn-minimal markdown-toggle"
data-view="markdown"
data-result-id="${resultId}"
data-gen-index="${index}">
📄 Texte brut
</button>
<button class="btn-minimal copy-text-btn"
data-text="${escapeHtml(genText)}">
📋 Copier
</button>
</div>
<div class="generation-text markdown-content">${renderMarkdown(genText)}</div>
</div>
`;
})
.join("")}
</div>
</div>
`
: "";
return `
<div class="textgen-result">
${systemPromptHtml}
<div class="textgen-prompt">
<h4>📝 Prompt initial:</h4>
<div class="prompt-text">${prompt}</div>
</div>
${parametersHtml}
<div class="textgen-output" id="${resultId}_main">
<h4>✨ Texte généré:</h4>
<div class="generated-text">
<div class="text-content markdown-content">${renderMarkdown(generatedText)}</div>
<div class="text-actions">
<button class="btn-minimal markdown-toggle"
data-view="markdown"
data-result-id="${resultId}"
data-is-main="true">
📄 Texte brut
</button>
<button class="btn-minimal copy-text-btn"
data-text="${escapeHtml(generatedText)}">
📋 Copier le texte
</button>
<button class="btn-minimal" onclick="regenerateText()">
🔄 Régénérer
</button>
</div>
</div>
</div>
${generationsHtml}
<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 systemPrompt = document.getElementById("textgenSystemPrompt").value.trim();
const model = document.getElementById("textgenModel").value;
const maxNewTokens = parseInt(document.getElementById("maxNewTokens").value) || 500;
const numReturnSequences = parseInt(document.getElementById("numReturnSequences").value) || 1;
const temperature = parseFloat(document.getElementById("temperature").value) || 1.0;
const doSample = document.getElementById("doSample").checked;
if (!text) {
showResult("textgenResult", { error: "Le prompt est requis" }, true);
return;
}
// Validate parameters
if (maxNewTokens < 1 || maxNewTokens > 2048) {
showResult("textgenResult", { error: "Le nombre de tokens doit être entre 1 et 2048" }, true);
return;
}
if (numReturnSequences < 1 || numReturnSequences > 5) {
showResult("textgenResult", { error: "Le nombre de séquences doit être entre 1 et 5" }, true);
return;
}
if (temperature < 0.1 || temperature > 2.0) {
showResult("textgenResult", { error: "La température doit être entre 0.1 et 2.0" }, true);
return;
}
try {
const data = {
text,
max_new_tokens: maxNewTokens,
num_return_sequences: numReturnSequences,
temperature: temperature,
do_sample: doSample
};
// Add system prompt if provided
if (systemPrompt) {
data.system_prompt = systemPrompt;
}
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: {
prompt: "Generate 10 ideas of what to build with a rapsberry pi",
systemPrompt: "You are a helpful AI assistant. Format your responses using complex Markdown (Title, lists etc..). Be clear and structured."
},
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":
if (typeof example === "object") {
document.getElementById("textgenPrompt").value = example.prompt;
document.getElementById("textgenSystemPrompt").value = example.systemPrompt;
} else {
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);
}
}