pdf6 / index.html
DaniGoua's picture
Add 1 files
5563d55 verified
raw
history blame contribute delete
49.8 kB
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UMR - Contrôle Qualité PDF</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.12.313/pdf.min.js"></script>
<style>
.pdf-container {
height: 500px;
overflow-y: auto;
border: 1px solid #e5e7eb;
position: relative;
}
.chat-container {
height: 400px;
overflow-y: auto;
}
.highlight {
background-color: rgba(255, 255, 0, 0.3);
}
.page-selector {
position: absolute;
bottom: 10px;
left: 10px;
z-index: 100;
background: white;
padding: 5px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.control-zone {
position: absolute;
border: 2px dashed rgba(59, 130, 246, 0.5);
background-color: rgba(59, 130, 246, 0.2);
z-index: 10;
cursor: move;
}
#zoneCreator {
position: relative;
}
.zone-tooltip {
position: absolute;
background: white;
padding: 5px;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
z-index: 20;
font-size: 12px;
pointer-events: none;
}
.zone-actions {
position: absolute;
top: -25px;
right: 0;
display: flex;
gap: 5px;
}
.zone-action-btn {
background: white;
border: 1px solid #e5e7eb;
border-radius: 4px;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 12px;
}
</style>
</head>
<body class="bg-gray-50">
<div class="container mx-auto px-4 py-8">
<header class="mb-8">
<h1 class="text-3xl font-bold text-blue-800">UMR - Contrôle Qualité des Courriers PDF</h1>
<p class="text-gray-600 mt-2">Comparez vos documents PDF avec les modèles de référence</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<!-- Document de Référence -->
<div class="bg-white rounded-lg shadow p-4">
<h2 class="text-xl font-semibold mb-4 text-blue-700">Document de Référence</h2>
<div class="mb-4">
<input type="file" id="referenceFile" accept=".pdf" class="block w-full text-sm text-gray-500
file:mr-4 file:py-2 file:px-4
file:rounded-md file:border-0
file:text-sm file:font-semibold
file:bg-blue-50 file:text-blue-700
hover:file:bg-blue-100">
</div>
<div class="relative pdf-container" id="referenceViewer">
<canvas id="referenceCanvas"></canvas>
<div class="page-selector hidden" id="refPageSelector">
Page <span id="refCurrentPage">1</span> sur <span id="refTotalPages">0</span>
<button class="ml-2 px-2 py-1 bg-blue-100 rounded" id="refPrevPage"></button>
<button class="ml-1 px-2 py-1 bg-blue-100 rounded" id="refNextPage"></button>
</div>
</div>
<button id="defineZonesBtn" class="mt-4 hidden bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded">
Définir les zones de contrôle
</button>
</div>
<!-- Document à Contrôler -->
<div class="bg-white rounded-lg shadow p-4">
<h2 class="text-xl font-semibold mb-4 text-blue-700">Document à Contrôler</h2>
<div class="mb-4">
<input type="file" id="controlFile" accept=".pdf" class="block w-full text-sm text-gray-500
file:mr-4 file:py-2 file:px-4
file:rounded-md file:border-0
file:text-sm file:font-semibold
file:bg-blue-50 file:text-blue-700
hover:file:bg-blue-100">
</div>
<div class="relative pdf-container" id="controlViewer">
<canvas id="controlCanvas"></canvas>
<div class="page-selector hidden" id="ctrlPageSelector">
Page <span id="ctrlCurrentPage">1</span> sur <span id="ctrlTotalPages">0</span>
<button class="ml-2 px-2 py-1 bg-blue-100 rounded" id="ctrlPrevPage"></button>
<button class="ml-1 px-2 py-1 bg-blue-100 rounded" id="ctrlNextPage"></button>
</div>
</div>
</div>
</div>
<!-- Zone Creator (hidden by default) -->
<div id="zoneCreator" class="hidden bg-white rounded-lg shadow p-4 mb-6">
<h2 class="text-xl font-semibold mb-4 text-blue-700">Définition des Zones de Contrôle</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">Type de document</label>
<select id="docType" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md">
<option value="lettre">Lettre du Président</option>
<option value="lexique">Lexique</option>
<option value="attestation_alloc">Attestation Fiscale Allocataire</option>
<option value="attestation_cotis">Attestation Fiscale Cotisant</option>
<option value="releve">Relevé de Situation</option>
<option value="contact">Page de Contact</option>
</select>
</div>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">Nom de la zone</label>
<input type="text" id="zoneName" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" placeholder="Ex: En-tête, Logo, Adresse...">
</div>
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-1">Page</label>
<input type="number" id="zonePage" min="1" value="1" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
</div>
<div class="flex space-x-4">
<button id="saveZoneBtn" class="bg-green-600 hover:bg-green-700 text-white font-medium py-2 px-4 rounded">
Enregistrer la zone
</button>
<button id="cancelZoneBtn" class="bg-gray-600 hover:bg-gray-700 text-white font-medium py-2 px-4 rounded">
Annuler
</button>
</div>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="font-medium text-gray-800 mb-3">Instructions</h3>
<ol class="list-decimal list-inside text-sm text-gray-700 space-y-2">
<li>Cliquez et glissez pour dessiner une zone sur le document de référence</li>
<li>Ajustez la taille et la position si nécessaire</li>
<li>Remplissez les informations sur la zone (type, nom, page)</li>
<li>Cliquez sur "Enregistrer la zone" pour la sauvegarder</li>
<li>Répétez pour toutes les zones à contrôler</li>
</ol>
<div class="mt-4 p-3 bg-blue-50 rounded-lg">
<h4 class="font-medium text-blue-800 mb-2">Conseils :</h4>
<ul class="list-disc list-inside text-sm text-blue-700 space-y-1">
<li>Nommez clairement vos zones pour les retrouver facilement</li>
<li>Créez des zones suffisamment larges pour couvrir le texte</li>
<li>Vérifiez que la page indiquée correspond bien à la zone</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Control Interface -->
<div class="bg-white rounded-lg shadow p-4 mb-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-blue-700">Interface de Contrôle</h2>
<button id="startControlBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded">
Lancer le contrôle
</button>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Document Types -->
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="font-medium text-gray-800 mb-3">Types de documents détectés</h3>
<ul id="docTypesList" class="space-y-2">
<li class="text-sm text-gray-500 italic">Aucun document chargé</li>
</ul>
</div>
<!-- Anomalies -->
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="font-medium text-gray-800 mb-3">Anomalies détectées</h3>
<ul id="anomaliesList" class="space-y-2">
<li class="text-sm text-gray-500 italic">Aucune anomalie détectée</li>
</ul>
</div>
<!-- Actions -->
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="font-medium text-gray-800 mb-3">Actions</h3>
<div class="space-y-3">
<button id="exportCSVBtn" class="w-full bg-green-600 hover:bg-green-700 text-white font-medium py-2 px-4 rounded">
Exporter en CSV
</button>
<button id="generatePDFBtn" class="w-full bg-purple-600 hover:bg-purple-700 text-white font-medium py-2 px-4 rounded">
Générer PDF corrigé
</button>
<button id="resetBtn" class="w-full bg-red-600 hover:bg-red-700 text-white font-medium py-2 px-4 rounded">
Réinitialiser
</button>
</div>
</div>
</div>
</div>
<!-- Chat Interface -->
<div class="bg-white rounded-lg shadow p-4">
<h2 class="text-xl font-semibold mb-4 text-blue-700">Interface Conversationnelle</h2>
<div class="chat-container mb-4 p-4 bg-gray-50 rounded-lg" id="chatMessages">
<div class="text-center text-gray-500 italic">Bienvenue dans l'interface de contrôle qualité. Chargez vos documents pour commencer.</div>
</div>
<div class="flex">
<input type="text" id="chatInput" placeholder="Posez votre question..." class="flex-1 px-4 py-2 border border-gray-300 rounded-l-md focus:outline-none focus:ring-blue-500 focus:border-blue-500">
<button id="sendMessageBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-r-md">
Envoyer
</button>
</div>
</div>
</div>
<script>
// Initialize PDF.js
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.12.313/pdf.worker.min.js';
// Global variables
let referencePdf = null;
let controlPdf = null;
let referenceZones = [];
let currentZone = null;
let isCreatingZone = false;
let startX, startY;
let chatHistory = [];
let detectedAnomalies = [];
let detectedDocTypes = [];
let activeZoneIndex = -1;
let isDraggingZone = false;
let dragOffsetX = 0;
let dragOffsetY = 0;
// DOM elements
const referenceFileInput = document.getElementById('referenceFile');
const controlFileInput = document.getElementById('controlFile');
const referenceCanvas = document.getElementById('referenceCanvas');
const controlCanvas = document.getElementById('controlCanvas');
const refPageSelector = document.getElementById('refPageSelector');
const ctrlPageSelector = document.getElementById('ctrlPageSelector');
const refCurrentPage = document.getElementById('refCurrentPage');
const refTotalPages = document.getElementById('refTotalPages');
const ctrlCurrentPage = document.getElementById('ctrlCurrentPage');
const ctrlTotalPages = document.getElementById('ctrlTotalPages');
const refPrevPage = document.getElementById('refPrevPage');
const refNextPage = document.getElementById('refNextPage');
const ctrlPrevPage = document.getElementById('ctrlPrevPage');
const ctrlNextPage = document.getElementById('ctrlNextPage');
const defineZonesBtn = document.getElementById('defineZonesBtn');
const zoneCreator = document.getElementById('zoneCreator');
const docTypeSelect = document.getElementById('docType');
const zoneNameInput = document.getElementById('zoneName');
const zonePageInput = document.getElementById('zonePage');
const saveZoneBtn = document.getElementById('saveZoneBtn');
const cancelZoneBtn = document.getElementById('cancelZoneBtn');
const startControlBtn = document.getElementById('startControlBtn');
const docTypesList = document.getElementById('docTypesList');
const anomaliesList = document.getElementById('anomaliesList');
const exportCSVBtn = document.getElementById('exportCSVBtn');
const generatePDFBtn = document.getElementById('generatePDFBtn');
const resetBtn = document.getElementById('resetBtn');
const chatMessages = document.getElementById('chatMessages');
const chatInput = document.getElementById('chatInput');
const sendMessageBtn = document.getElementById('sendMessageBtn');
// Event listeners
referenceFileInput.addEventListener('change', handleReferenceFile);
controlFileInput.addEventListener('change', handleControlFile);
defineZonesBtn.addEventListener('click', startZoneCreation);
saveZoneBtn.addEventListener('click', saveZone);
cancelZoneBtn.addEventListener('click', cancelZoneCreation);
startControlBtn.addEventListener('click', startControlProcess);
exportCSVBtn.addEventListener('click', exportToCSV);
generatePDFBtn.addEventListener('click', generateCorrectedPDF);
resetBtn.addEventListener('click', resetApplication);
sendMessageBtn.addEventListener('click', sendChatMessage);
chatInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') sendChatMessage();
});
// Navigation buttons
refPrevPage.addEventListener('click', () => navigatePage('ref', -1));
refNextPage.addEventListener('click', () => navigatePage('ref', 1));
ctrlPrevPage.addEventListener('click', () => navigatePage('ctrl', -1));
ctrlNextPage.addEventListener('click', () => navigatePage('ctrl', 1));
// Functions
function handleReferenceFile(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async function() {
try {
const typedArray = new Uint8Array(reader.result);
referencePdf = await pdfjsLib.getDocument(typedArray).promise;
refTotalPages.textContent = referencePdf.numPages;
refPageSelector.classList.remove('hidden');
await renderPage('ref', 1);
defineZonesBtn.classList.remove('hidden');
addChatMessage("Système", "Document de référence chargé avec succès. Vous pouvez maintenant définir les zones de contrôle.");
} catch (error) {
console.error('Error loading PDF:', error);
addChatMessage("Erreur", "Erreur lors du chargement du PDF de référence.");
}
};
reader.readAsArrayBuffer(file);
}
function handleControlFile(e) {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async function() {
try {
const typedArray = new Uint8Array(reader.result);
controlPdf = await pdfjsLib.getDocument(typedArray).promise;
ctrlTotalPages.textContent = controlPdf.numPages;
ctrlPageSelector.classList.remove('hidden');
await renderPage('ctrl', 1);
addChatMessage("Système", "Document à contrôler chargé avec succès. Vous pouvez maintenant lancer le contrôle.");
} catch (error) {
console.error('Error loading PDF:', error);
addChatMessage("Erreur", "Erreur lors du chargement du PDF à contrôler.");
}
};
reader.readAsArrayBuffer(file);
}
async function renderPage(type, pageNum) {
try {
const pdf = type === 'ref' ? referencePdf : controlPdf;
const canvas = type === 'ref' ? referenceCanvas : controlCanvas;
const pageInfo = type === 'ref' ? refCurrentPage : ctrlCurrentPage;
if (!pdf) return;
const page = await pdf.getPage(pageNum);
const viewport = page.getViewport({ scale: 1.0 });
// Adjust canvas size
canvas.height = viewport.height;
canvas.width = viewport.width;
// Update current page display
pageInfo.textContent = pageNum;
// Render PDF page
await page.render({
canvasContext: canvas.getContext('2d'),
viewport: viewport
}).promise;
// Update zone display if reference
if (type === 'ref') {
displayZonesForPage(pageNum);
}
return viewport;
} catch (error) {
console.error('Error rendering page:', error);
}
}
function navigatePage(type, direction) {
const currentPage = type === 'ref' ? parseInt(refCurrentPage.textContent) : parseInt(ctrlCurrentPage.textContent);
const totalPages = type === 'ref' ? referencePdf.numPages : controlPdf.numPages;
let newPage = currentPage + direction;
if (newPage < 1) newPage = 1;
if (newPage > totalPages) newPage = totalPages;
renderPage(type, newPage);
}
function startZoneCreation() {
isCreatingZone = true;
zoneCreator.classList.remove('hidden');
defineZonesBtn.classList.add('hidden');
// Set up canvas for zone creation
referenceCanvas.style.cursor = 'crosshair';
referenceCanvas.addEventListener('mousedown', startZoneDrawing);
referenceCanvas.addEventListener('mousemove', drawZone);
referenceCanvas.addEventListener('mouseup', endZoneDrawing);
addChatMessage("Système", "Mode création de zones activé. Cliquez et glissez pour définir une zone de contrôle.");
}
function startZoneDrawing(e) {
if (!isCreatingZone) return;
const rect = referenceCanvas.getBoundingClientRect();
startX = e.clientX - rect.left;
startY = e.clientY - rect.top;
// Check if we're clicking on an existing zone
const existingZone = findZoneAtPosition(startX, startY);
if (existingZone) {
activeZoneIndex = existingZone.index;
isDraggingZone = true;
dragOffsetX = startX - existingZone.zone.x;
dragOffsetY = startY - existingZone.zone.y;
return;
}
// Remove any existing temporary zone
if (currentZone) {
currentZone.remove();
currentZone = null;
}
// Create new zone element
currentZone = document.createElement('div');
currentZone.className = 'control-zone';
currentZone.style.left = startX + 'px';
currentZone.style.top = startY + 'px';
currentZone.style.width = '0';
currentZone.style.height = '0';
document.getElementById('referenceViewer').appendChild(currentZone);
}
function findZoneAtPosition(x, y) {
const zones = document.querySelectorAll('.control-zone');
for (let i = 0; i < zones.length; i++) {
const zone = zones[i];
const zoneRect = zone.getBoundingClientRect();
const canvasRect = referenceCanvas.getBoundingClientRect();
const zoneX = zoneRect.left - canvasRect.left;
const zoneY = zoneRect.top - canvasRect.top;
const zoneWidth = zoneRect.width;
const zoneHeight = zoneRect.height;
if (x >= zoneX && x <= zoneX + zoneWidth && y >= zoneY && y <= zoneY + zoneHeight) {
// Find the corresponding zone in our referenceZones array
for (let j = 0; j < referenceZones.length; j++) {
if (Math.abs(referenceZones[j].x - zoneX) < 5 &&
Math.abs(referenceZones[j].y - zoneY) < 5 &&
Math.abs(referenceZones[j].width - zoneWidth) < 5 &&
Math.abs(referenceZones[j].height - zoneHeight) < 5) {
return { index: j, zone: referenceZones[j] };
}
}
}
}
return null;
}
function drawZone(e) {
if (!isCreatingZone) return;
const rect = referenceCanvas.getBoundingClientRect();
const currentX = e.clientX - rect.left;
const currentY = e.clientY - rect.top;
if (isDraggingZone && activeZoneIndex >= 0) {
// Update zone position
const zone = referenceZones[activeZoneIndex];
zone.x = currentX - dragOffsetX;
zone.y = currentY - dragOffsetY;
// Update zone display
displayZonesForPage(parseInt(refCurrentPage.textContent));
return;
}
if (!currentZone) return;
const width = currentX - startX;
const height = currentY - startY;
currentZone.style.width = Math.abs(width) + 'px';
currentZone.style.height = Math.abs(height) + 'px';
if (width < 0) {
currentZone.style.left = currentX + 'px';
}
if (height < 0) {
currentZone.style.top = currentY + 'px';
}
}
function endZoneDrawing() {
if (!isCreatingZone) return;
if (isDraggingZone) {
isDraggingZone = false;
activeZoneIndex = -1;
return;
}
if (currentZone) {
// Show zone info tooltip
showZoneInfoTooltip();
}
// Clean up event listeners
referenceCanvas.removeEventListener('mousemove', drawZone);
referenceCanvas.removeEventListener('mouseup', endZoneDrawing);
}
function showZoneInfoTooltip() {
if (!currentZone) return;
const tooltip = document.createElement('div');
tooltip.className = 'zone-tooltip';
tooltip.innerHTML = `
<div class="font-medium mb-1">Nouvelle zone</div>
<div class="text-xs">Remplissez les détails à droite</div>
`;
const rect = currentZone.getBoundingClientRect();
tooltip.style.left = (rect.left + rect.width/2 - 75) + 'px';
tooltip.style.top = (rect.top - 60) + 'px';
document.body.appendChild(tooltip);
// Remove tooltip after 3 seconds or when user starts interacting
setTimeout(() => {
if (tooltip.parentNode) {
tooltip.parentNode.removeChild(tooltip);
}
}, 3000);
// Also remove when user clicks anywhere
document.addEventListener('click', function removeTooltip() {
if (tooltip.parentNode) {
tooltip.parentNode.removeChild(tooltip);
}
document.removeEventListener('click', removeTooltip);
}, { once: true });
}
function saveZone() {
if (!currentZone) {
addChatMessage("Erreur", "Veuillez d'abord créer une zone en cliquant et glissant sur le document.");
return;
}
const rect = currentZone.getBoundingClientRect();
const canvasRect = referenceCanvas.getBoundingClientRect();
// Calculate relative coordinates
const x = rect.left - canvasRect.left;
const y = rect.top - canvasRect.top;
const width = rect.width;
const height = rect.height;
if (width < 10 || height < 10) {
addChatMessage("Erreur", "La zone est trop petite. Veuillez créer une zone plus grande.");
return;
}
if (!zoneNameInput.value.trim()) {
addChatMessage("Erreur", "Veuillez donner un nom à la zone.");
zoneNameInput.focus();
return;
}
const zone = {
type: docTypeSelect.value,
name: zoneNameInput.value.trim(),
page: parseInt(zonePageInput.value),
x: x,
y: y,
width: width,
height: height
};
referenceZones.push(zone);
// Clean up
if (currentZone) {
currentZone.remove();
currentZone = null;
}
isCreatingZone = false;
// Reset UI
referenceCanvas.style.cursor = 'default';
// Display saved zones
displayZonesForPage(parseInt(zonePageInput.value));
// Reset form
zoneNameInput.value = '';
addChatMessage("Système", `Zone "${zone.name}" enregistrée pour le type "${docTypeSelect.options[docTypeSelect.selectedIndex].text}".`);
}
function cancelZoneCreation() {
if (currentZone) {
currentZone.remove();
currentZone = null;
}
isCreatingZone = false;
isDraggingZone = false;
activeZoneIndex = -1;
referenceCanvas.style.cursor = 'default';
zoneCreator.classList.add('hidden');
defineZonesBtn.classList.remove('hidden');
// Re-enable zone creation mode if there are zones to edit
if (referenceZones.length > 0) {
defineZonesBtn.click();
}
addChatMessage("Système", "Création de zone annulée.");
}
function displayZonesForPage(pageNum) {
// Remove all existing zone displays except the current one being created
document.querySelectorAll('.control-zone').forEach(zone => {
if (zone !== currentZone) zone.remove();
});
// Remove all zone action buttons
document.querySelectorAll('.zone-actions').forEach(actions => {
actions.remove();
});
// Display zones for this page
referenceZones.filter(zone => zone.page === pageNum).forEach((zone, index) => {
const zoneElement = document.createElement('div');
zoneElement.className = 'control-zone';
zoneElement.style.left = zone.x + 'px';
zoneElement.style.top = zone.y + 'px';
zoneElement.style.width = zone.width + 'px';
zoneElement.style.height = zone.height + 'px';
zoneElement.dataset.index = index;
// Add actions buttons
const actions = document.createElement('div');
actions.className = 'zone-actions';
actions.innerHTML = `
<button class="zone-action-btn edit-zone" title="Modifier">✏️</button>
<button class="zone-action-btn delete-zone" title="Supprimer">🗑️</button>
`;
zoneElement.appendChild(actions);
// Position actions
actions.style.left = (zone.width - actions.offsetWidth) + 'px';
document.getElementById('referenceViewer').appendChild(zoneElement);
// Add event listeners to action buttons
zoneElement.querySelector('.edit-zone').addEventListener('click', (e) => {
e.stopPropagation();
editZone(index);
});
zoneElement.querySelector('.delete-zone').addEventListener('click', (e) => {
e.stopPropagation();
deleteZone(index);
});
});
}
function editZone(index) {
const zone = referenceZones[index];
// Fill the form with zone data
docTypeSelect.value = zone.type;
zoneNameInput.value = zone.name;
zonePageInput.value = zone.page;
// Highlight the zone
document.querySelectorAll('.control-zone').forEach(z => {
z.style.borderColor = 'rgba(59, 130, 246, 0.5)';
});
const zoneElement = document.querySelector(`.control-zone[data-index="${index}"]`);
if (zoneElement) {
zoneElement.style.borderColor = 'rgba(255, 165, 0, 0.8)';
}
addChatMessage("Système", `Modification de la zone "${zone.name}". Vous pouvez ajuster ses propriétés ou la déplacer directement sur le document.`);
}
function deleteZone(index) {
const zoneName = referenceZones[index].name;
if (confirm(`Voulez-vous vraiment supprimer la zone "${zoneName}" ?`)) {
referenceZones.splice(index, 1);
displayZonesForPage(parseInt(refCurrentPage.textContent));
addChatMessage("Système", `Zone "${zoneName}" supprimée.`);
}
}
async function startControlProcess() {
if (!referencePdf || !controlPdf) {
addChatMessage("Erreur", "Veuillez charger les deux documents avant de lancer le processus.");
return;
}
if (referenceZones.length === 0) {
addChatMessage("Erreur", "Veuillez définir des zones de contrôle avant de lancer le processus.");
return;
}
addChatMessage("Système", "Début du processus de contrôle...");
// Reset previous results
detectedAnomalies = [];
detectedDocTypes = [];
// Simulate document type detection (in a real app, this would analyze the PDF)
detectDocumentTypes();
// Simulate comparison (in a real app, this would compare text in the zones)
simulateComparison();
// Update UI with results
updateResultsUI();
addChatMessage("Système", "Contrôle terminé. " + detectedAnomalies.length + " anomalies détectées.");
}
function detectDocumentTypes() {
// In a real app, this would analyze the PDF content to detect document types
// For this demo, we'll just use some sample data
detectedDocTypes = [
{ type: 'lettre', page: 1, status: 'détecté' },
{ type: 'lexique', page: 2, status: 'détecté' },
{ type: 'attestation_alloc', page: 3, status: 'détecté' },
{ type: 'releve', page: 4, status: 'détecté' },
{ type: 'contact', page: 5, status: 'détecté' }
];
}
function simulateComparison() {
// In a real app, this would compare text in the defined zones
// For this demo, we'll generate some sample anomalies
// Sample anomalies for different document types
detectedAnomalies = [
{
docType: 'lettre',
page: 1,
location: 'Haut de la page',
description: 'Le logo en en-tête est décalé de 5px vers la droite',
severity: 'Majeure',
refText: 'Logo UMR positionné à x:50, y:50',
controlText: 'Logo UMR positionné à x:55, y:50'
},
{
docType: 'lettre',
page: 1,
location: 'Bas de la page',
description: 'La mention légale est incomplète',
severity: 'Mineure',
refText: 'UMR est une SA au capital de 247 668 709 €. Siège social : 12 Rue de Cornulier - 75 000 PARIS.',
controlText: 'UMR est une SA au capital de 247 668 709 €.'
},
{
docType: 'attestation_alloc',
page: 3,
location: 'Milieu de la page',
description: 'La phrase "Au titre des pensions : 1 632 €" est absente',
severity: 'Majeure',
refText: 'Au titre des pensions : 1 632 €',
controlText: ''
},
{
docType: 'releve',
page: 4,
location: 'Bas de la page',
description: 'La valeur du point affichée diffère',
severity: 'Variable',
refText: 'valeur de service du point* de votre contrat PER Corem a été revalorisée à hauteur de 3,5% au 01/01/2025',
controlText: 'valeur de service du point* de votre contrat PER Corem a été revalorisée à hauteur de 2,5% au 01/01/2025',
needsConfirmation: true
}
];
}
function updateResultsUI() {
// Update document types list
docTypesList.innerHTML = '';
if (detectedDocTypes.length === 0) {
docTypesList.innerHTML = '<li class="text-sm text-gray-500 italic">Aucun type de document détecté</li>';
} else {
detectedDocTypes.forEach(doc => {
const li = document.createElement('li');
li.className = 'text-sm';
li.innerHTML = `<span class="font-medium">${getDocTypeName(doc.type)}</span> (Page ${doc.page}) - <span class="text-green-600">${doc.status}</span>`;
docTypesList.appendChild(li);
});
}
// Update anomalies list
anomaliesList.innerHTML = '';
if (detectedAnomalies.length === 0) {
anomaliesList.innerHTML = '<li class="text-sm text-gray-500 italic">Aucune anomalie détectée</li>';
} else {
detectedAnomalies.forEach((anomaly, index) => {
const li = document.createElement('li');
li.className = 'text-sm';
let severityClass = 'text-yellow-600';
if (anomaly.severity === 'Majeure') severityClass = 'text-red-600';
if (anomaly.severity === 'Variable') severityClass = 'text-blue-600';
li.innerHTML = `
<div class="font-medium">${getDocTypeName(anomaly.docType)} (${anomaly.location}, Page ${anomaly.page})</div>
<div>${anomaly.description} <span class="${severityClass}">(${anomaly.severity})</span></div>
${anomaly.needsConfirmation ? '<div class="text-blue-600 mt-1">[Nécessite confirmation]</div>' : ''}
`;
li.addEventListener('click', () => showAnomalyDetails(index));
anomaliesList.appendChild(li);
});
}
}
function getDocTypeName(typeCode) {
const types = {
'lettre': 'Lettre du Président',
'lexique': 'Lexique',
'attestation_alloc': 'Attestation Fiscale Allocataire',
'attestation_cotis': 'Attestation Fiscale Cotisant',
'releve': 'Relevé de Situation',
'contact': 'Page de Contact'
};
return types[typeCode] || typeCode;
}
function showAnomalyDetails(index) {
const anomaly = detectedAnomalies[index];
let message = `
<strong>${getDocTypeName(anomaly.docType)} (Page ${anomaly.page})</strong><br>
<span class="${anomaly.severity === 'Majeure' ? 'text-red-600' : anomaly.severity === 'Variable' ? 'text-blue-600' : 'text-yellow-600'}">
${anomaly.severity}: ${anomaly.description}
</span><br><br>
<strong>Document de référence:</strong><br>
${anomaly.refText || 'Aucun texte correspondant'}<br><br>
<strong>Document à contrôler:</strong><br>
${anomaly.controlText || 'Aucun texte correspondant'}
`;
if (anomaly.needsConfirmation) {
message += `<br><br><div class="mt-4">
<button onclick="confirmAnomaly(${index}, true)" class="mr-2 bg-green-600 hover:bg-green-700 text-white font-medium py-1 px-3 rounded text-sm">
Confirmer la différence
</button>
<button onclick="confirmAnomaly(${index}, false)" class="bg-red-600 hover:bg-red-700 text-white font-medium py-1 px-3 rounded text-sm">
Rejeter la différence
</button>
</div>`;
}
addChatMessage("Détail de l'anomalie", message);
}
function confirmAnomaly(index, isConfirmed) {
const anomaly = detectedAnomalies[index];
if (isConfirmed) {
addChatMessage("Système", `Vous avez confirmé que la différence dans "${anomaly.description}" est normale.`);
// In a real app, you might mark this as confirmed in your data structure
} else {
addChatMessage("Système", `Vous avez indiqué que la différence dans "${anomaly.description}" est anormale.`);
// In a real app, you might mark this as a real anomaly
}
// Remove the needsConfirmation flag
detectedAnomalies[index].needsConfirmation = false;
// Update the UI
updateResultsUI();
}
function exportToCSV() {
if (detectedAnomalies.length === 0) {
addChatMessage("Erreur", "Aucune anomalie à exporter.");
return;
}
// Create CSV content
let csvContent = "Type de document,Page,Localisation,Description,Sévérité,Texte de référence,Texte du document\n";
detectedAnomalies.forEach(anomaly => {
csvContent += `"${getDocTypeName(anomaly.docType)}",${anomaly.page},"${anomaly.location}","${anomaly.description}","${anomaly.severity}","${anomaly.refText.replace(/"/g, '""')}","${anomaly.controlText.replace(/"/g, '""')}"\n`;
});
// Create download link
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.setAttribute('href', url);
link.setAttribute('download', 'anomalies_detectees.csv');
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
addChatMessage("Système", "Export CSV des anomalies terminé.");
}
function generateCorrectedPDF() {
// In a real app, this would generate a corrected PDF
// For this demo, we'll just show a message
addChatMessage("Système", "Génération du PDF corrigé en cours...");
setTimeout(() => {
addChatMessage("Système", "PDF corrigé généré avec succès. Les anomalies ont été surlignées.");
// Simulate download (in a real app, this would be the actual PDF)
const link = document.createElement('a');
link.setAttribute('href', '#');
link.setAttribute('download', 'document_corrige.pdf');
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}, 2000);
}
function resetApplication() {
// Reset file inputs
referenceFileInput.value = '';
controlFileInput.value = '';
// Reset PDF viewers
referenceCanvas.getContext('2d').clearRect(0, 0, referenceCanvas.width, referenceCanvas.height);
controlCanvas.getContext('2d').clearRect(0, 0, controlCanvas.width, controlCanvas.height);
// Hide page selectors
refPageSelector.classList.add('hidden');
ctrlPageSelector.classList.add('hidden');
// Reset zone creation
if (currentZone) {
currentZone.remove();
currentZone = null;
}
isCreatingZone = false;
isDraggingZone = false;
activeZoneIndex = -1;
referenceCanvas.style.cursor = 'default';
zoneCreator.classList.add('hidden');
defineZonesBtn.classList.add('hidden');
// Clear zones
referenceZones = [];
document.querySelectorAll('.control-zone').forEach(zone => zone.remove());
document.querySelectorAll('.zone-actions').forEach(actions => actions.remove());
// Reset results
detectedAnomalies = [];
detectedDocTypes = [];
docTypesList.innerHTML = '<li class="text-sm text-gray-500 italic">Aucun type de document détecté</li>';
anomaliesList.innerHTML = '<li class="text-sm text-gray-500 italic">Aucune anomalie détectée</li>';
// Clear chat
chatHistory = [];
chatMessages.innerHTML = '<div class="text-center text-gray-500 italic">Bienvenue dans l\'interface de contrôle qualité. Chargez vos documents pour commencer.</div>';
// Reset PDF objects
referencePdf = null;
controlPdf = null;
addChatMessage("Système", "Application réinitialisée. Vous pouvez charger de nouveaux documents.");
}
function addChatMessage(sender, message) {
const messageDiv = document.createElement('div');
messageDiv.className = 'mb-3';
const senderSpan = document.createElement('span');
senderSpan.className = 'font-medium text-blue-700';
senderSpan.textContent = sender + ': ';
const contentDiv = document.createElement('div');
contentDiv.className = 'mt-1 ml-4 p-3 bg-white rounded-lg shadow-sm';
contentDiv.innerHTML = message;
messageDiv.appendChild(senderSpan);
messageDiv.appendChild(contentDiv);
chatMessages.appendChild(messageDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
// Add to history
chatHistory.push({ sender, message });
}
function sendChatMessage() {
const message = chatInput.value.trim();
if (!message) return;
addChatMessage("Vous", message);
chatInput.value = '';
// Simulate AI response
setTimeout(() => {
let response = "Je suis désolé, je ne peux pas répondre à cette question dans cette version de démonstration. Dans une version complète, je pourrais analyser les documents et répondre à vos questions sur les anomalies détectées.";
// Simple keyword responses for demo
if (message.toLowerCase().includes('bonjour') || message.toLowerCase().includes('salut')) {
response = "Bonjour ! Comment puis-je vous aider avec le contrôle de vos documents aujourd'hui ?";
} else if (message.toLowerCase().includes('aide') || message.toLowerCase().includes('comment')) {
response = "Pour utiliser cette application :<br>1. Chargez un document de référence<br>2. Définissez les zones de contrôle<br>3. Chargez un document à vérifier<br>4. Lancez le contrôle<br>Je vous signalerai alors les anomalies détectées.";
} else if (message.toLowerCase().includes('anomalie') && detectedAnomalies.length > 0) {
response = `Il y a actuellement ${detectedAnomalies.length} anomalies détectées. Cliquez sur une anomalie dans la liste pour voir les détails.`;
} else if (message.toLowerCase().includes('type') && detectedDocTypes.length > 0) {
const typesList = detectedDocTypes.map(doc => getDocTypeName(doc.type)).join(', ');
response = `Les types de documents détectés sont : ${typesList}.`;
}
addChatMessage("Assistant", response);
}, 500);
}
// Make confirmAnomaly available globally for the chat messages
window.confirmAnomaly = confirmAnomaly;
</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=DaniGoua/pdf6" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>