good presidio

This commit is contained in:
nBiqoz
2025-06-18 13:45:23 +02:00
parent 4c0622e47e
commit 8c7a19e881
8 changed files with 898 additions and 469 deletions

View File

@@ -31,15 +31,13 @@ import {
Check,
Eye,
} from "lucide-react";
import MarkdownModal from "./components/MarkdownModal";
import PresidioModal from "./components/PresidioModal";
// Définition de la structure d'un objet page, exporté pour être réutilisé
export type PageObject = {
pageNumber: number;
htmlContent: string;
};
// Interface mise à jour pour utiliser la nouvelle structure PageObject
interface ProcessedFile {
id: string;
name: string;
@@ -50,7 +48,7 @@ interface ProcessedFile {
piiCount?: number;
errorMessage?: string;
processedBlob?: Blob;
textContent?: PageObject[];
anonymizedText?: string;
}
interface AnonymizationOptions {
@@ -58,7 +56,7 @@ interface AnonymizationOptions {
}
const piiOptions = [
{ id: "name", label: "Nom & Prénom", icon: User },
{ id: "name", label: "Nom & Prénom", icon: User },
{ id: "email", label: "Email", icon: AtSign },
{ id: "location", label: "Lieu", icon: MapPin },
{ id: "birthdate", label: "Date de naissance", icon: Cake },
@@ -81,17 +79,22 @@ export default function Home() {
useState<AnonymizationOptions>(
piiOptions.reduce((acc, option) => ({ ...acc, [option.id]: true }), {})
);
// CORRECTION : L'état de la modale attend maintenant la nouvelle structure de données
const [modalContent, setModalContent] = useState<PageObject[] | null>(null);
const [showPresidioModal, setShowPresidioModal] = useState(false);
const [anonymizedResult, setAnonymizedResult] = useState<{
text: string;
piiCount: number;
} | null>(null);
const handleOptionChange = (id: string) =>
setAnonymizationOptions((prev) => ({ ...prev, [id]: !prev[id] }));
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files?.length) {
setFile(e.target.files[0]);
setError(null);
}
};
const handleDrop = useCallback((e: React.DragEvent) => {
e.preventDefault();
setIsDragOver(false);
@@ -100,13 +103,16 @@ export default function Home() {
setError(null);
}
}, []);
const handleDragOver = useCallback((e: React.DragEvent) => {
e.preventDefault();
setIsDragOver(true);
}, []);
const handleDragLeave = useCallback(() => {
setIsDragOver(false);
}, []);
const formatFileSize = (bytes: number): string => {
if (bytes === 0) return "0 Bytes";
const k = 1024;
@@ -114,11 +120,14 @@ export default function Home() {
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
};
const clearFile = () => {
setFile(null);
setError(null);
};
const clearHistory = () => setHistory([]);
const removeFromHistory = (id: string) =>
setHistory((prev) => prev.filter((item) => item.id !== id));
@@ -128,18 +137,24 @@ export default function Home() {
const url = URL.createObjectURL(fileToDownload.processedBlob);
const a = document.createElement("a");
a.href = url;
a.download = `anonymized_${
fileToDownload.name.split(".")[0] || "file"
}.txt`;
a.download = `anonymized_${fileToDownload.name
.split(".")
.slice(0, -1)
.join(".")}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
a.remove();
};
const handlePreview = (id: string) => {
const fileToPreview = history.find((item) => item.id === id);
if (fileToPreview?.textContent) {
setModalContent(fileToPreview.textContent);
if (fileToPreview?.anonymizedText && fileToPreview.piiCount !== undefined) {
setAnonymizedResult({
text: fileToPreview.anonymizedText,
piiCount: fileToPreview.piiCount,
});
setShowPresidioModal(true);
}
};
@@ -167,16 +182,12 @@ export default function Home() {
const processFile = async () => {
if (!file) return;
const n8nWebhookUrl = process.env.NEXT_PUBLIC_N8N_WEBHOOK_URL;
if (!n8nWebhookUrl) {
setError("L'URL du service est manquante. Contactez l'administrateur.");
return;
}
setIsProcessing(true);
setProgress(0);
setError(null);
const fileId = `${Date.now()}-${file.name}`;
setHistory((prev) => [
{
id: fileId,
@@ -188,51 +199,31 @@ export default function Home() {
...prev,
]);
setFile(null);
setProgress(10);
try {
const formData = new FormData();
formData.append("file", file);
formData.append("options", JSON.stringify(anonymizationOptions));
setProgress(25);
setProgress(30);
const response = await fetch(n8nWebhookUrl, {
const response = await fetch("/api/process-document", {
method: "POST",
body: formData,
});
setProgress(70);
if (!response.ok) {
const errorResult = await response
.json()
.catch(() => ({ error: `Erreur serveur [${response.status}]` }));
throw new Error(errorResult.error || `Échec du traitement.`);
}
setProgress(75);
const result = await response.json();
const docData = result.anonymizedDocument;
if (!docData || !Array.isArray(docData.pages)) {
if (!response.ok) {
throw new Error(
"Format de réponse invalide du service d'anonymisation."
result.error || "Une erreur est survenue lors du traitement."
);
}
const textContent: PageObject[] = docData.pages;
const piiCount: number = docData.piiCount || 0;
const { anonymizedText, piiCount } = result;
const fullText = textContent
.map(
(page) =>
`--- Page ${page.pageNumber} ---\n${page.htmlContent.replace(
/<[^>]*>/g,
"\n"
)}`
)
.join("\n\n");
const processedBlob = new Blob([fullText], {
const processedBlob = new Blob([anonymizedText], {
type: "text/plain;charset=utf-8",
});
setProgress(90);
setHistory((prev) =>
prev.map((item) =>
@@ -243,7 +234,7 @@ export default function Home() {
processedSize: formatFileSize(processedBlob.size),
piiCount,
processedBlob,
textContent,
anonymizedText,
}
: item
)
@@ -338,7 +329,7 @@ export default function Home() {
</div>
</div>
<div className="flex items-center gap-1.5 ml-2">
{item.status === "completed" && item.textContent && (
{item.status === "completed" && item.anonymizedText && (
<Button
onClick={() => handlePreview(item.id)}
variant="ghost"
@@ -560,10 +551,13 @@ export default function Home() {
</div>
</main>
<MarkdownModal
content={modalContent}
onClose={() => setModalContent(null)}
/>
{showPresidioModal && (
<PresidioModal
anonymizedText={anonymizedResult?.text || null}
piiCount={anonymizedResult?.piiCount || 0}
onClose={() => setShowPresidioModal(false)}
/>
)}
</div>
);
}