good presidio
This commit is contained in:
98
app/page.tsx
98
app/page.tsx
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user