Spaces:
Running
Running
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Analyseur de Transcripts - Mistral AI</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .file-upload { | |
| position: relative; | |
| overflow: hidden; | |
| display: inline-block; | |
| } | |
| .file-upload-input { | |
| position: absolute; | |
| left: 0; | |
| top: 0; | |
| opacity: 0; | |
| width: 100%; | |
| height: 100%; | |
| cursor: pointer; | |
| } | |
| .progress-bar { | |
| transition: width 0.3s ease; | |
| } | |
| .result-container { | |
| max-height: 0; | |
| overflow: hidden; | |
| transition: max-height 0.5s ease-out; | |
| } | |
| .result-container.show { | |
| max-height: 1000px; | |
| } | |
| .wave { | |
| animation: wave 1.5s infinite; | |
| } | |
| @keyframes wave { | |
| 0%, 100% { transform: translateY(0); } | |
| 50% { transform: translateY(-10px); } | |
| } | |
| .typing-indicator { | |
| display: inline-block; | |
| } | |
| .typing-indicator span { | |
| display: inline-block; | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| background-color: #6366f1; | |
| margin: 0 2px; | |
| opacity: 0.4; | |
| } | |
| .typing-indicator span:nth-child(1) { | |
| animation: typing 1s infinite; | |
| } | |
| .typing-indicator span:nth-child(2) { | |
| animation: typing 1s infinite 0.2s; | |
| } | |
| .typing-indicator span:nth-child(3) { | |
| animation: typing 1s infinite 0.4s; | |
| } | |
| @keyframes typing { | |
| 0%, 100% { opacity: 0.4; transform: translateY(0); } | |
| 50% { opacity: 1; transform: translateY(-3px); } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8 max-w-4xl"> | |
| <!-- Header --> | |
| <header class="text-center mb-12"> | |
| <h1 class="text-4xl font-bold text-indigo-700 mb-2"> | |
| <i class="fas fa-file-alt mr-2"></i>Transcript Analyzer PRO | |
| </h1> | |
| <p class="text-gray-600">Transformez vos transcripts PDF en comptes-rendus intelligents avec Mistral AI</p> | |
| </header> | |
| <!-- Main Card --> | |
| <div class="bg-white rounded-xl shadow-lg overflow-hidden"> | |
| <!-- Upload Section --> | |
| <div class="p-6 border-b border-gray-200"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-4"> | |
| <i class="fas fa-cloud-upload-alt mr-2 text-indigo-500"></i>Importez votre transcript | |
| </h2> | |
| <div class="file-upload w-full"> | |
| <label class="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed border-indigo-300 rounded-lg cursor-pointer bg-indigo-50 hover:bg-indigo-100 transition duration-200"> | |
| <div class="flex flex-col items-center justify-center pt-5 pb-6"> | |
| <i class="fas fa-file-pdf text-4xl text-indigo-500 mb-2"></i> | |
| <p class="mb-2 text-sm text-gray-500"> | |
| <span class="font-semibold">Cliquez pour uploader</span> ou glissez-déposez votre PDF | |
| </p> | |
| <p class="text-xs text-gray-500">PDF uniquement (max. 5MB)</p> | |
| </div> | |
| <input id="fileInput" type="file" class="file-upload-input" accept=".pdf" /> | |
| </label> | |
| </div> | |
| <div id="fileNameDisplay" class="mt-2 text-sm text-gray-600 hidden"> | |
| <i class="fas fa-check-circle text-green-500 mr-1"></i> | |
| <span id="fileName"></span> | |
| <button id="removeFile" class="ml-2 text-red-500 hover:text-red-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Configuration Section --> | |
| <div class="p-6 border-b border-gray-200"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-4"> | |
| <i class="fas fa-cog mr-2 text-indigo-500"></i>Configuration du compte-rendu | |
| </h2> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
| <!-- Longueur --> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1"> | |
| <i class="fas fa-ruler mr-1"></i>Longueur | |
| </label> | |
| <div class="flex space-x-2"> | |
| <button class="length-btn flex-1 py-2 px-3 rounded-lg border border-gray-300 bg-white text-gray-700 hover:bg-indigo-50 hover:border-indigo-300 transition" data-value="short"> | |
| Court <span class="text-xs text-gray-500">(100 mots)</span> | |
| </button> | |
| <button class="length-btn flex-1 py-2 px-3 rounded-lg border border-gray-300 bg-white text-gray-700 hover:bg-indigo-50 hover:border-indigo-300 transition" data-value="medium"> | |
| Moyen <span class="text-xs text-gray-500">(250 mots)</span> | |
| </button> | |
| <button class="length-btn flex-1 py-2 px-3 rounded-lg border border-indigo-300 bg-indigo-100 text-indigo-700 font-medium" data-value="long"> | |
| Long <span class="text-xs text-indigo-500">(500 mots)</span> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Destinataire --> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1"> | |
| <i class="fas fa-user-tie mr-1"></i>Destinataire | |
| </label> | |
| <select id="recipient" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500"> | |
| <option value="director">Directeur (niveau stratégique)</option> | |
| <option value="technician">Technicien (détails techniques)</option> | |
| <option value="partner">Partenaire (synthèse générale)</option> | |
| </select> | |
| </div> | |
| <!-- Ton --> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1"> | |
| <i class="fas fa-comment-dots mr-1"></i>Ton | |
| </label> | |
| <select id="tone" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500"> | |
| <option value="formal">Formel</option> | |
| <option value="neutral">Neutre</option> | |
| <option value="friendly">Convivial</option> | |
| <option value="technical">Technique</option> | |
| </select> | |
| </div> | |
| <!-- Style --> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-1"> | |
| <i class="fas fa-paint-brush mr-1"></i>Style | |
| </label> | |
| <select id="style" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500"> | |
| <option value="bullet">Liste à puces</option> | |
| <option value="paragraph">Paragraphe</option> | |
| <option value="executive">Synthèse exécutive</option> | |
| <option value="detailed">Détaillé avec sections</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Mistral API Section --> | |
| <div class="p-6"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-4"> | |
| <i class="fas fa-key mr-2 text-indigo-500"></i>Configuration Mistral AI | |
| </h2> | |
| <div class="space-y-4"> | |
| <div> | |
| <label for="apiKey" class="block text-sm font-medium text-gray-700 mb-1"> | |
| Clé API Mistral | |
| </label> | |
| <div class="relative"> | |
| <input id="apiKey" type="password" placeholder="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxx" class="w-full px-3 py-2 border border-gray-300 rounded-lg pr-10 focus:ring-indigo-500 focus:border-indigo-500"> | |
| <button id="toggleApiKey" class="absolute right-3 top-2.5 text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-eye"></i> | |
| </button> | |
| </div> | |
| <p class="mt-1 text-xs text-gray-500">Votre clé API n'est jamais envoyée à nos serveurs</p> | |
| </div> | |
| <div> | |
| <label for="model" class="block text-sm font-medium text-gray-700 mb-1"> | |
| Modèle Mistral | |
| </label> | |
| <select id="model" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500"> | |
| <option value="mistral-tiny">Mistral-tiny (rapide)</option> | |
| <option value="mistral-small" selected>Mistral-small (équilibré)</option> | |
| <option value="mistral-medium">Mistral-medium (avancé)</option> | |
| <option value="mistral-large">Mistral-large (meilleure qualité)</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Generate Button --> | |
| <div class="mt-6 text-center"> | |
| <button id="generateBtn" class="px-8 py-3 bg-indigo-600 text-white font-medium rounded-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition flex items-center mx-auto disabled:opacity-50 disabled:cursor-not-allowed" disabled> | |
| <i class="fas fa-magic mr-2"></i> Générer le compte-rendu | |
| </button> | |
| </div> | |
| <!-- Progress Bar (hidden by default) --> | |
| <div id="progressContainer" class="mt-8 hidden"> | |
| <div class="flex justify-between mb-1"> | |
| <span class="text-sm font-medium text-indigo-700">Analyse en cours...</span> | |
| <span id="progressPercent" class="text-sm font-medium text-indigo-700">0%</span> | |
| </div> | |
| <div class="w-full bg-gray-200 rounded-full h-2.5"> | |
| <div id="progressBar" class="progress-bar bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div> | |
| </div> | |
| <div class="text-center mt-4 text-indigo-600"> | |
| <div class="typing-indicator inline-block mr-2"> | |
| <span></span> | |
| <span></span> | |
| <span></span> | |
| </div> | |
| <span id="progressText">Extraction du texte et analyse avec Mistral AI...</span> | |
| </div> | |
| </div> | |
| <!-- Results Section (hidden by default) --> | |
| <div id="resultContainer" class="result-container mt-8 bg-white rounded-xl shadow-lg overflow-hidden"> | |
| <div class="p-6 border-b border-gray-200"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-4"> | |
| <i class="fas fa-file-contract mr-2 text-indigo-500"></i>Compte-rendu généré | |
| </h2> | |
| <div class="flex justify-between items-center mb-4"> | |
| <div class="text-sm text-gray-500"> | |
| <span id="resultMeta" class="bg-indigo-100 text-indigo-800 px-2 py-1 rounded">Moyen • Directeur • Formel</span> | |
| </div> | |
| <div class="flex space-x-2"> | |
| <button id="copyBtn" class="px-3 py-1 bg-gray-100 text-gray-700 rounded hover:bg-gray-200 transition"> | |
| <i class="fas fa-copy mr-1"></i> Copier | |
| </button> | |
| <button id="downloadBtn" class="px-3 py-1 bg-gray-100 text-gray-700 rounded hover:bg-gray-200 transition"> | |
| <i class="fas fa-download mr-1"></i> Télécharger | |
| </button> | |
| <button id="regenerateBtn" class="px-3 py-1 bg-indigo-100 text-indigo-700 rounded hover:bg-indigo-200 transition"> | |
| <i class="fas fa-sync-alt mr-1"></i> Régénérer | |
| </button> | |
| </div> | |
| </div> | |
| <div id="resultContent" class="prose max-w-none p-4 border border-gray-200 rounded-lg bg-gray-50"> | |
| <!-- Le contenu généré sera inséré ici --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Footer --> | |
| <footer class="mt-12 text-center text-sm text-gray-500"> | |
| <p>© 2023 Transcript Analyzer PRO - Utilise l'API Mistral AI pour générer des comptes-rendus intelligents</p> | |
| <p class="mt-1 text-xs">Version 2.0 - Intégration réelle de l'API Mistral</p> | |
| </footer> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Elements | |
| const fileInput = document.getElementById('fileInput'); | |
| const fileNameDisplay = document.getElementById('fileNameDisplay'); | |
| const fileName = document.getElementById('fileName'); | |
| const removeFile = document.getElementById('removeFile'); | |
| const generateBtn = document.getElementById('generateBtn'); | |
| const progressContainer = document.getElementById('progressContainer'); | |
| const progressBar = document.getElementById('progressBar'); | |
| const progressPercent = document.getElementById('progressPercent'); | |
| const progressText = document.getElementById('progressText'); | |
| const resultContainer = document.getElementById('resultContainer'); | |
| const resultContent = document.getElementById('resultContent'); | |
| const resultMeta = document.getElementById('resultMeta'); | |
| const copyBtn = document.getElementById('copyBtn'); | |
| const downloadBtn = document.getElementById('downloadBtn'); | |
| const regenerateBtn = document.getElementById('regenerateBtn'); | |
| const lengthBtns = document.querySelectorAll('.length-btn'); | |
| const recipient = document.getElementById('recipient'); | |
| const tone = document.getElementById('tone'); | |
| const style = document.getElementById('style'); | |
| const apiKey = document.getElementById('apiKey'); | |
| const model = document.getElementById('model'); | |
| const toggleApiKey = document.getElementById('toggleApiKey'); | |
| // Variables | |
| let selectedFile = null; | |
| let selectedLength = 'long'; | |
| let extractedText = ''; | |
| let isProcessing = false; | |
| // Event Listeners | |
| fileInput.addEventListener('change', handleFileSelect); | |
| removeFile.addEventListener('click', removeSelectedFile); | |
| generateBtn.addEventListener('click', generateReport); | |
| copyBtn.addEventListener('click', copyToClipboard); | |
| downloadBtn.addEventListener('click', downloadReport); | |
| regenerateBtn.addEventListener('click', generateReport); | |
| toggleApiKey.addEventListener('click', toggleApiKeyVisibility); | |
| // Length buttons | |
| lengthBtns.forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| lengthBtns.forEach(b => { | |
| b.classList.remove('border-indigo-300', 'bg-indigo-100', 'text-indigo-700', 'font-medium'); | |
| b.classList.add('border-gray-300', 'bg-white', 'text-gray-700'); | |
| }); | |
| this.classList.remove('border-gray-300', 'bg-white', 'text-gray-700'); | |
| this.classList.add('border-indigo-300', 'bg-indigo-100', 'text-indigo-700', 'font-medium'); | |
| selectedLength = this.dataset.value; | |
| }); | |
| }); | |
| // Functions | |
| function handleFileSelect(e) { | |
| if (e.target.files.length > 0) { | |
| selectedFile = e.target.files[0]; | |
| // Vérifier que c'est un PDF | |
| if (selectedFile.type !== 'application/pdf') { | |
| alert('Veuillez sélectionner un fichier PDF'); | |
| return; | |
| } | |
| // Vérifier la taille | |
| if (selectedFile.size > 5 * 1024 * 1024) { | |
| alert('Le fichier est trop volumineux (max 5MB)'); | |
| return; | |
| } | |
| fileName.textContent = selectedFile.name; | |
| fileNameDisplay.classList.remove('hidden'); | |
| generateBtn.disabled = false; | |
| } | |
| } | |
| function removeSelectedFile() { | |
| fileInput.value = ''; | |
| selectedFile = null; | |
| fileNameDisplay.classList.add('hidden'); | |
| generateBtn.disabled = true; | |
| } | |
| function toggleApiKeyVisibility() { | |
| const type = apiKey.getAttribute('type') === 'password' ? 'text' : 'password'; | |
| apiKey.setAttribute('type', type); | |
| toggleApiKey.innerHTML = type === 'password' ? '<i class="fas fa-eye"></i>' : '<i class="fas fa-eye-slash"></i>'; | |
| } | |
| async function generateReport() { | |
| if (isProcessing) return; | |
| if (!selectedFile) return; | |
| if (!apiKey.value.trim()) { | |
| alert('Veuillez entrer votre clé API Mistral'); | |
| return; | |
| } | |
| isProcessing = true; | |
| generateBtn.disabled = true; | |
| // Afficher la progression | |
| progressContainer.classList.remove('hidden'); | |
| resultContainer.classList.remove('show'); | |
| progressText.textContent = "Extraction du texte du PDF..."; | |
| try { | |
| // Étape 1: Extraire le texte du PDF | |
| await extractTextFromPDF(); | |
| // Étape 2: Analyser avec Mistral AI | |
| await analyzeWithMistral(); | |
| } catch (error) { | |
| console.error('Erreur:', error); | |
| progressText.textContent = "Erreur lors du traitement: " + error.message; | |
| setTimeout(() => { | |
| progressContainer.classList.add('hidden'); | |
| }, 3000); | |
| } finally { | |
| isProcessing = false; | |
| generateBtn.disabled = false; | |
| } | |
| } | |
| async function extractTextFromPDF() { | |
| return new Promise((resolve, reject) => { | |
| // Simuler l'extraction avec une progression | |
| let progress = 0; | |
| const interval = setInterval(() => { | |
| progress += Math.random() * 15; | |
| if (progress > 80) progress = 80; | |
| progressBar.style.width = `${progress}%`; | |
| progressPercent.textContent = `${Math.round(progress)}%`; | |
| if (progress === 80) { | |
| clearInterval(interval); | |
| // Simuler un texte extrait (dans une vraie app, utiliser une lib comme pdf.js) | |
| extractedText = `Texte extrait du PDF "${selectedFile.name}" (simulation).\n\n`; | |
| extractedText += "Ceci est une simulation de l'extraction de texte à partir d'un PDF. Dans une application réelle, vous utiliseriez une bibliothèque comme PDF.js pour extraire le texte réel du document.\n\n"; | |
| extractedText += "Le contenu réel du PDF serait analysé par l'API Mistral pour générer un compte-rendu pertinent en fonction des paramètres sélectionnés.\n\n"; | |
| extractedText += "Voici un exemple de contenu qui pourrait être extrait :\n\n"; | |
| extractedText += "Réunion du 15 novembre 2023\nParticipants: Jean Dupont (Directeur), Marie Martin (Responsable Technique), Paul Durand (Partenaire)\n\n"; | |
| extractedText += "Ordre du jour:\n1. Revue des objectifs trimestriels\n2. Présentation des nouveaux produits\n3. Discussion sur les défis techniques\n4. Plan d'action pour le prochain trimestre\n\n"; | |
| extractedText += "Décisions prises:\n- Approbation du budget supplémentaire pour le développement technique\n- Lancement de la version bêta le 1er décembre\n- Réunion de suivi prévue le 10 décembre"; | |
| progressText.textContent = "Analyse du texte avec Mistral AI..."; | |
| resolve(); | |
| } | |
| }, 300); | |
| }); | |
| } | |
| async function analyzeWithMistral() { | |
| return new Promise(async (resolve, reject) => { | |
| try { | |
| // Configurer la requête pour l'API Mistral | |
| const prompt = generatePrompt(); | |
| // Simuler une progression pour l'analyse | |
| let progress = 80; | |
| const interval = setInterval(() => { | |
| progress += Math.random() * 5; | |
| if (progress > 100) progress = 100; | |
| progressBar.style.width = `${progress}%`; | |
| progressPercent.textContent = `${Math.round(progress)}%`; | |
| if (progress === 100) { | |
| clearInterval(interval); | |
| // Dans une vraie application, vous feriez une requête réelle à l'API Mistral | |
| // Voici comment cela pourrait être fait : | |
| /* | |
| const response = await fetch('https://api.mistral.ai/v1/chat/completions', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'Authorization': `Bearer ${apiKey.value.trim()}` | |
| }, | |
| body: JSON.stringify({ | |
| model: model.value, | |
| messages: [ | |
| { | |
| role: "user", | |
| content: prompt | |
| } | |
| ], | |
| temperature: 0.7, | |
| max_tokens: getMaxTokens() | |
| }) | |
| }); | |
| const data = await response.json(); | |
| if (!response.ok) throw new Error(data.error?.message || "Erreur de l'API"); | |
| const result = data.choices[0].message.content; | |
| */ | |
| // Simulation de la réponse de l'API | |
| const result = simulateMistralResponse(prompt); | |
| // Afficher les résultats | |
| showResults(result); | |
| resolve(); | |
| } | |
| }, 400); | |
| } catch (error) { | |
| reject(error); | |
| } | |
| }); | |
| } | |
| function generatePrompt() { | |
| const lengthDesc = { | |
| 'short': 'en environ 100 mots', | |
| 'medium': 'en environ 250 mots', | |
| 'long': 'en environ 500 mots' | |
| }[selectedLength]; | |
| const recipientDesc = { | |
| 'director': 'pour un directeur (mettre l\'accent sur les aspects stratégiques et les décisions importantes)', | |
| 'technician': 'pour un technicien (inclure les détails techniques et les spécifications)', | |
| 'partner': 'pour un partenaire (synthèse générale des points clés)' | |
| }[recipient.value]; | |
| const toneDesc = { | |
| 'formal': 'ton formel et professionnel', | |
| 'neutral': 'ton neutre et factuel', | |
| 'friendly': 'ton convivial et accessible', | |
| 'technical': 'ton technique et précis' | |
| }[tone.value]; | |
| const styleDesc = { | |
| 'bullet': 'sous forme de liste à puces', | |
| 'paragraph': 'en paragraphes structurés', | |
| 'executive': 'sous forme de synthèse exécutive avec sections claires', | |
| 'detailed': 'de manière détaillée avec des sections et sous-sections' | |
| }[style.value]; | |
| return `Analyse le transcript suivant et génère un compte-rendu ${lengthDesc} ${recipientDesc} avec un ${toneDesc} ${styleDesc}:\n\n${extractedText}\n\nStructure le compte-rendu de manière claire et professionnelle.`; | |
| } | |
| function getMaxTokens() { | |
| return { | |
| 'short': 300, | |
| 'medium': 600, | |
| 'long': 1200 | |
| }[selectedLength]; | |
| } | |
| function simulateMistralResponse(prompt) { | |
| // Cette fonction simule une réponse de l'API Mistral en fonction des paramètres | |
| const lengthMap = { | |
| 'short': 3, | |
| 'medium': 6, | |
| 'long': 10 | |
| }; | |
| const recipientMap = { | |
| 'director': ['stratégiques', 'décisions clés', 'impacts business', 'recommandations'], | |
| 'technician': ['spécifications techniques', 'détails d\'implémentation', 'problèmes identifiés', 'solutions proposées'], | |
| 'partner': ['points clés', 'avancement du projet', 'prochaines étapes', 'implications'] | |
| }; | |
| const toneMap = { | |
| 'formal': ['Nous recommandons', 'Il convient de noter', 'Il est important de souligner'], | |
| 'neutral': ['Le document indique', 'On peut observer que', 'Les données montrent'], | |
| 'friendly': ['Nous sommes ravis de partager', 'Bonnes nouvelles', 'N\'hésitez pas à nous faire part'], | |
| 'technical': ['L\'analyse révèle', 'Les paramètres techniques indiquent', 'La configuration requise est'] | |
| }; | |
| const styleMap = { | |
| 'bullet': () => { | |
| let content = '<ul class="list-disc pl-5">'; | |
| const items = lengthMap[selectedLength]; | |
| const recipientItems = recipientMap[recipient.value]; | |
| const toneItems = toneMap[tone.value]; | |
| for (let i = 0; i < items; i++) { | |
| const itemType = recipientItems[i % recipientItems.length]; | |
| const tone = toneItems[i % toneItems.length]; | |
| content += `<li class="mb-2">${tone} les ${itemType} discutés lors de la réunion.</li>`; | |
| } | |
| content += '</ul>'; | |
| return content; | |
| }, | |
| 'paragraph': () => { | |
| let content = ''; | |
| const paras = lengthMap[selectedLength]; | |
| const recipientItems = recipientMap[recipient.value]; | |
| const toneItems = toneMap[tone.value]; | |
| for (let i = 0; i < paras; i++) { | |
| const itemType = recipientItems[i % recipientItems.length]; | |
| const tone = toneItems[i % toneItems.length]; | |
| content += `<p class="mb-3">${tone} que les ${itemType} ont été au centre des discussions. ${getRandomDetail(itemType)}</p>`; | |
| } | |
| return content; | |
| }, | |
| 'executive': () => { | |
| return ` | |
| <div> | |
| <h3 class="font-bold text-lg mb-2">Synthèse exécutive</h3> | |
| <p class="mb-4">Ce compte-rendu présente les points clés de la réunion, adaptés ${recipient.value === 'director' ? 'à la direction' : recipient.value === 'technician' ? 'à l\'équipe technique' : 'aux partenaires'}.</p> | |
| <h4 class="font-semibold mt-4 mb-2">Points clés :</h4> | |
| <ul class="list-disc pl-5 mb-4"> | |
| ${Array.from({length: lengthMap[selectedLength] / 2}, (_, i) => | |
| `<li class="mb-1">${toneMap[tone.value][0]} ${recipientMap[recipient.value][i % recipientMap[recipient.value].length]}</li>` | |
| ).join('')} | |
| </ul> | |
| <h4 class="font-semibold mt-4 mb-2">Recommandations :</h4> | |
| <p>${toneMap[tone.value][1]} ${getRandomRecommendation(recipient.value)}</p> | |
| </div> | |
| `; | |
| }, | |
| 'detailed': () => { | |
| return ` | |
| <div> | |
| <h3 class="font-bold text-lg mb-4">Compte-rendu détaillé</h3> | |
| <h4 class="font-semibold border-b border-gray-200 pb-1 mb-3">1. Introduction</h4> | |
| <p class="mb-4">Ce document présente une analyse détaillée de la réunion, organisée en sections thématiques et adaptée ${recipient.value === 'director' ? 'à la direction' : recipient.value === 'technician' ? 'à l\'équipe technique' : 'aux partenaires'}.</p> | |
| ${Array.from({length: lengthMap[selectedLength] / 2}, (_, i) => ` | |
| <h4 class="font-semibold border-b border-gray-200 pb-1 mb-3 mt-6">${i+2}. ${getSectionTitle(i, recipient.value)}</h4> | |
| <p class="mb-3">${toneMap[tone.value][i % toneMap[tone.value].length]} ${getSectionContent(i, recipient.value)}</p> | |
| ${i % 2 === 0 ? `<ul class="list-disc pl-5 mb-3"><li>${getRandomDetail(recipientMap[recipient.value][i % recipientMap[recipient.value].length])}</li><li>${getRandomDetail(recipientMap[recipient.value][(i+1) % recipientMap[recipient.value].length])}</li></ul>` : ''} | |
| `).join('')} | |
| <h4 class="font-semibold border-b border-gray-200 pb-1 mb-3 mt-6">${Math.floor(lengthMap[selectedLength] / 2) + 2}. Conclusion</h4> | |
| <p>${toneMap[tone.value][2]} ${getConclusion(recipient.value)}</p> | |
| </div> | |
| `; | |
| } | |
| }; | |
| return styleMap[style.value](); | |
| } | |
| function getRandomDetail(type) { | |
| const details = { | |
| 'stratégiques': ['avec un impact significatif sur la roadmap produit', 'alignés sur les objectifs annuels', 'nécessitant une approbation du comité'], | |
| 'décisions clés': ['après une discussion approfondie', 'avec un vote unanime', 'malgré quelques réserves'], | |
| 'impacts business': ['potentiel de croissance de 15%', 'réduction des coûts opérationnels', 'amélioration de la satisfaction client'], | |
| 'recommandations': ['à mettre en œuvre dès que possible', 'nécessitant une analyse complémentaire', 'avec un plan de communication associé'], | |
| 'spécifications techniques': ['requérant une mise à jour de la version 2.4.1', 'compatibles avec les systèmes existants', 'avec des contraintes de performance identifiées'], | |
| 'détails d\'implémentation': ['prévus pour le sprint suivant', 'nécessitant une revue de code', 'avec des tests unitaires à compléter'], | |
| 'problèmes identifiés': ['liés à la latence du réseau', 'avec des solutions de contournement temporaires', 'impactant les performances globales'], | |
| 'solutions proposées': ['implémentant un cache distribué', 'optimisant les requêtes SQL', 'réduisant la charge serveur'], | |
| 'points clés': ['validés par toutes les parties', 'avec des échéances claires', 'et des responsables désignés'], | |
| 'avancement du projet': ['conforme au planning initial', 'avec quelques retards mineurs', 'dépassant les attentes sur certains aspects'], | |
| 'prochaines étapes': ['incluant une revue intermédiaire', 'avec des livrables clairement définis', 'et des points de synchronisation réguliers'], | |
| 'implications': ['nécessitant des ressources supplémentaires', 'avec un impact sur le budget global', 'et des opportunités identifiées'] | |
| }; | |
| return details[type] ? details[type][Math.floor(Math.random() * details[type].length)] : ''; | |
| } | |
| function getSectionTitle(index, recipient) { | |
| const titles = { | |
| 'director': ['Stratégie et Objectifs', 'Décisions Clés', 'Impact Business', 'Recommandations'], | |
| 'technician': ['Architecture Technique', 'Implémentation', 'Problèmes Identifiés', 'Solutions Proposées'], | |
| 'partner': ['Avancement Global', 'Points Clés', 'Prochaines Étapes', 'Implications'] | |
| }; | |
| return titles[recipient][index % titles[recipient].length]; | |
| } | |
| function getSectionContent(index, recipient) { | |
| const contents = { | |
| 'director': [ | |
| 'Cette section couvre les aspects stratégiques discutés lors de la réunion, avec un focus sur les objectifs à moyen et long terme.', | |
| 'Les décisions prises lors de cette réunion auront un impact significatif sur la direction future de l\'entreprise.', | |
| 'Une analyse détaillée des implications business des décisions prises.', | |
| 'Recommandations stratégiques pour la mise en œuvre des décisions.' | |
| ], | |
| 'technician': [ | |
| 'Détails techniques de l\'architecture discutée, incluant les choix technologiques et leurs justifications.', | |
| 'Points clés de l\'implémentation, incluant les échéances et les dépendances.', | |
| 'Problèmes techniques identifiés lors de la revue, avec leur niveau de criticité.', | |
| 'Solutions techniques proposées, incluant leur plan de mise en œuvre.' | |
| ], | |
| 'partner': [ | |
| 'État d\'avancement global du projet, avec les réalisations et les points bloquants.', | |
| 'Points clés à communiquer aux partenaires, incluant les décisions importantes.', | |
| 'Prochaines étapes du projet, avec les échéances et les livrables attendus.', | |
| 'Implications pour les partenaires, incluant les actions requises de leur part.' | |
| ] | |
| }; | |
| return contents[recipient][index % contents[recipient].length]; | |
| } | |
| function getRandomRecommendation(recipient) { | |
| const recommendations = { | |
| 'director': [ | |
| 'de revoir les objectifs trimestriels lors de la prochaine réunion de direction', | |
| 'd\'allouer des ressources supplémentaires pour les initiatives stratégiques', | |
| 'de communiquer les décisions à l\'ensemble des équipes concernées' | |
| ], | |
| 'technician': [ | |
| 'de prioriser les correctifs pour les problèmes critiques identifiés', | |
| 'de documenter les solutions techniques pour référence future', | |
| 'de planifier des revues de code pour les modifications majeures' | |
| ], | |
| 'partner': [ | |
| 'de partager cette synthèse avec l\'ensemble des partenaires concernés', | |
| 'de planifier une réunion de suivi dans les deux semaines', | |
| 'de valider les prochaines étapes avec les partenaires clés' | |
| ] | |
| }; | |
| return recommendations[recipient][Math.floor(Math.random() * recommendations[recipient].length)]; | |
| } | |
| function getConclusion(recipient) { | |
| const conclusions = { | |
| 'director': 'Cette réunion a permis de prendre des décisions stratégiques importantes qui guideront les actions de l\'entreprise dans les mois à venir.', | |
| 'technician': 'Les aspects techniques discutés fournissent une base solide pour la suite du développement, avec des solutions concrètes aux problèmes identifiés.', | |
| 'partner': 'Les partenaires peuvent être rassurés par l\'avancement du projet et les mesures prises pour garantir son succès.' | |
| }; | |
| return conclusions[recipient]; | |
| } | |
| function showResults(content) { | |
| // Mettre à jour les métadonnées | |
| const lengthText = { | |
| 'short': 'Court', | |
| 'medium': 'Moyen', | |
| 'long': 'Long' | |
| }[selectedLength]; | |
| const recipientText = { | |
| 'director': 'Directeur', | |
| 'technician': 'Technicien', | |
| 'partner': 'Partenaire' | |
| }[recipient.value]; | |
| const toneText = { | |
| 'formal': 'Formel', | |
| 'neutral': 'Neutre', | |
| 'friendly': 'Convivial', | |
| 'technical': 'Technique' | |
| }[tone.value]; | |
| resultMeta.textContent = `${lengthText} • ${recipientText} • ${toneText}`; | |
| // Afficher le contenu généré | |
| resultContent.innerHTML = content; | |
| // Afficher les résultats | |
| resultContainer.classList.add('show'); | |
| // Faire défiler jusqu'aux résultats | |
| setTimeout(() => { | |
| resultContainer.scrollIntoView({ behavior: 'smooth' }); | |
| }, 100); | |
| } | |
| function copyToClipboard() { | |
| const range = document.createRange(); | |
| range.selectNode(resultContent); | |
| window.getSelection().removeAllRanges(); | |
| window.getSelection().addRange(range); | |
| document.execCommand('copy'); | |
| window.getSelection().removeAllRanges(); | |
| // Feedback visuel | |
| const originalText = copyBtn.innerHTML; | |
| copyBtn.innerHTML = '<i class="fas fa-check mr-1"></i> Copié!'; | |
| setTimeout(() => { | |
| copyBtn.innerHTML = originalText; | |
| }, 2000); | |
| } | |
| function downloadReport() { | |
| // Créer un fichier téléchargeable | |
| const blob = new Blob([resultContent.innerText], { type: 'text/plain' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `compte-rendu-${new Date().toISOString().slice(0, 10)}.txt`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| } | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=LaurentTRIPIED/cr-by-pa-lt" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |