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> |