1363 lines
41 KiB
JavaScript
1363 lines
41 KiB
JavaScript
// 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);
|
||
}
|
||
}
|