From 012e4985fb832f52a02c1c7d0732b2f443a673a9 Mon Sep 17 00:00:00 2001 From: Usama <59267656+ussaama@users.noreply.github.com> Date: Tue, 17 Mar 2026 21:33:25 +0500 Subject: [PATCH] ECHO-675 ECHO-721 Anonymize transcripts fixes --- .../src/components/common/Markdown.tsx | 34 ++++++++++++- .../src/components/common/RedactedText.tsx | 48 ++++++++++++++----- .../form/MarkdownWYSIWYG/MarkdownWYSIWYG.tsx | 28 ++++++++++- .../participant/UserChunkMessage.tsx | 3 -- echo/frontend/src/locales/de-DE.po | 26 +++++----- echo/frontend/src/locales/en-US.po | 26 +++++----- echo/frontend/src/locales/es-ES.po | 26 +++++----- echo/frontend/src/locales/fr-FR.po | 26 +++++----- echo/frontend/src/locales/it-IT.po | 26 +++++----- echo/frontend/src/locales/nl-NL.po | 26 +++++----- echo/server/dembrane/api/verify.py | 22 +++++++-- echo/server/dembrane/reply_utils.py | 4 ++ .../generate_artifact.en.jinja | 23 ++++++++- .../get_reply_system.de.jinja | 21 ++++++++ .../get_reply_system.en.jinja | 23 ++++++++- .../get_reply_system.es.jinja | 21 ++++++++ .../get_reply_system.fr.jinja | 21 ++++++++ .../get_reply_system.it.jinja | 21 ++++++++ .../get_reply_system.nl.jinja | 21 ++++++++ .../prompt_templates/revise_artifact.en.jinja | 23 ++++++++- 20 files changed, 364 insertions(+), 105 deletions(-) diff --git a/echo/frontend/src/components/common/Markdown.tsx b/echo/frontend/src/components/common/Markdown.tsx index 542a8752..d54cef8f 100644 --- a/echo/frontend/src/components/common/Markdown.tsx +++ b/echo/frontend/src/components/common/Markdown.tsx @@ -1,6 +1,12 @@ -import { useEffect } from "react"; +import { useEffect, useMemo } from "react"; +import type { Components } from "react-markdown"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; +import { + escapeRedactedTokens, + REDACTED_CODE_PREFIX, + RedactedBadge, +} from "@/components/common/RedactedText"; import { cn } from "@/lib/utils"; export const Markdown = ({ @@ -25,6 +31,29 @@ export const Markdown = ({ } }, []); + const processedContent = useMemo( + () => escapeRedactedTokens(content), + [content], + ); + + const components = useMemo( + () => ({ + code({ children, className: codeClassName, ...props }) { + const text = String(children).trim(); + if (!codeClassName && text.startsWith(REDACTED_CODE_PREFIX)) { + const type = text.slice(REDACTED_CODE_PREFIX.length); + return ; + } + return ( + + {children} + + ); + }, + }), + [], + ); + return ( - {content} + {processedContent} ); }; diff --git a/echo/frontend/src/components/common/RedactedText.tsx b/echo/frontend/src/components/common/RedactedText.tsx index 632636e4..2a3efeda 100644 --- a/echo/frontend/src/components/common/RedactedText.tsx +++ b/echo/frontend/src/components/common/RedactedText.tsx @@ -2,9 +2,37 @@ import { t } from "@lingui/core/macro"; import { Text, Tooltip } from "@mantine/core"; import { type ReactNode, useMemo } from "react"; -const REDACTED_PATTERN = //g; +export const REDACTED_PATTERN = //g; +export const REDACTED_CODE_PREFIX = "redacted:"; -const getRedactedLabels = (): Record => ({ +const SAFE_REDACTED_PATTERN = /`redacted:([a-z_]+)`/g; + +/** + * Converts `` tokens to backtick-wrapped inline code + * (`` `redacted:type` ``) that MDX/Markdown editors preserve verbatim. + */ +export const escapeRedactedTokens = (text: string): string => { + if (!text || !text.includes(" `\`${REDACTED_CODE_PREFIX}${type}\``, + ); +}; + +/** + * Reverses `escapeRedactedTokens`, restoring `` `redacted:type` `` + * back to `` for downstream rendering. + */ +export const unescapeRedactedTokens = (text: string): string => { + if (!text || !text.includes("`redacted:")) { + return text; + } + return text.replace(SAFE_REDACTED_PATTERN, ""); +}; + +export const getRedactedLabels = (): Record => ({ address: t`Address`, card: t`Card`, email: t`Email`, @@ -16,7 +44,7 @@ const getRedactedLabels = (): Record => ({ username: t`Username`, }); -const formatLabel = (key: string): string => { +export const formatLabel = (key: string): string => { const labels = getRedactedLabels(); if (key in labels) { return labels[key]; @@ -27,17 +55,11 @@ const formatLabel = (key: string): string => { .join(" "); }; -const RedactedBadge = ({ type }: { type: string }) => { +export const RedactedBadge = ({ type }: { type: string }) => { const label = formatLabel(type); return ( - + {label} @@ -63,7 +85,9 @@ export const parseRedactedText = (text: string): ReactNode[] | string => { if (match.index > lastIndex) { parts.push(text.slice(lastIndex, match.index)); } - parts.push(); + parts.push( + , + ); lastIndex = regex.lastIndex; } diff --git a/echo/frontend/src/components/form/MarkdownWYSIWYG/MarkdownWYSIWYG.tsx b/echo/frontend/src/components/form/MarkdownWYSIWYG/MarkdownWYSIWYG.tsx index 7d491868..5f0cfda3 100644 --- a/echo/frontend/src/components/form/MarkdownWYSIWYG/MarkdownWYSIWYG.tsx +++ b/echo/frontend/src/components/form/MarkdownWYSIWYG/MarkdownWYSIWYG.tsx @@ -14,9 +14,31 @@ import { toolbarPlugin, UndoRedo, } from "@mdxeditor/editor"; +import { useCallback, useMemo } from "react"; + import "./styles.css"; +import { + escapeRedactedTokens, + unescapeRedactedTokens, +} from "@/components/common/RedactedText"; + +export function MarkdownWYSIWYG({ + markdown, + onChange, + ...rest +}: MDXEditorProps) { + const safeMarkdown = useMemo( + () => escapeRedactedTokens(markdown ?? ""), + [markdown], + ); + + const handleChange = useCallback( + (value: string, initialMarkdownNormalize: boolean) => { + onChange?.(unescapeRedactedTokens(value), initialMarkdownNormalize); + }, + [onChange], + ); -export function MarkdownWYSIWYG(props: MDXEditorProps) { return ( ); } diff --git a/echo/frontend/src/components/participant/UserChunkMessage.tsx b/echo/frontend/src/components/participant/UserChunkMessage.tsx index 86f80af4..fcc32eaf 100644 --- a/echo/frontend/src/components/participant/UserChunkMessage.tsx +++ b/echo/frontend/src/components/participant/UserChunkMessage.tsx @@ -4,7 +4,6 @@ import { IconDotsVertical, IconTrash } from "@tabler/icons-react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useParams } from "react-router"; import { Markdown } from "@/components/common/Markdown"; -import { RedactedText } from "@/components/common/RedactedText"; import { toast } from "@/components/common/Toaster"; import { deleteParticipantConversationChunk } from "@/lib/api"; @@ -106,8 +105,6 @@ const UserChunkMessage = ({ {chunk.transcript == null ? ( - ) : chunk.transcript.includes("{chunk.transcript} ) : ( )} diff --git a/echo/frontend/src/locales/de-DE.po b/echo/frontend/src/locales/de-DE.po index 77a60d6b..5bad50c8 100644 --- a/echo/frontend/src/locales/de-DE.po +++ b/echo/frontend/src/locales/de-DE.po @@ -314,7 +314,7 @@ msgstr "{readingNow} liest gerade" msgid "library.conversations.still.processing" msgstr "{0} werden noch verarbeitet." -#: src/components/participant/UserChunkMessage.tsx:108 +#: src/components/participant/UserChunkMessage.tsx:107 msgid "*Transcription in progress.*" msgstr "*Transkription wird durchgeführt.*" @@ -480,7 +480,7 @@ msgstr "Kontext wird hinzugefügt:" msgid "select.all.modal.loading.title" msgstr "Unterhaltungen werden hinzugefügt" -#: src/components/common/RedactedText.tsx:8 +#: src/components/common/RedactedText.tsx:36 msgid "Address" msgstr "Adresse" @@ -950,7 +950,7 @@ msgstr "Abbrechen" msgid "Cannot add empty conversation" msgstr "Leeres Gespräch kann nicht hinzugefügt werden" -#: src/components/common/RedactedText.tsx:9 +#: src/components/common/RedactedText.tsx:37 msgid "Card" msgstr "Karte" @@ -1414,7 +1414,7 @@ msgstr "Projekt löschen" msgid "Delete Webhook" msgstr "Webhook löschen" -#: src/components/participant/UserChunkMessage.tsx:69 +#: src/components/participant/UserChunkMessage.tsx:68 msgid "Deleted successfully" msgstr "Erfolgreich gelöscht" @@ -1625,7 +1625,7 @@ msgstr "Bearbeitungsmodus" #: src/routes/auth/Login.tsx:251 #: src/routes/auth/Login.tsx:255 -#: src/components/common/RedactedText.tsx:10 +#: src/components/common/RedactedText.tsx:38 msgid "Email" msgstr "E-Mail" @@ -1922,7 +1922,7 @@ msgstr "Benutzerdefiniertes Thema konnte nicht erstellt werden" msgid "Failed to delete custom topic" msgstr "Benutzerdefiniertes Thema konnte nicht gelöscht werden" -#: src/components/participant/UserChunkMessage.tsx:46 +#: src/components/participant/UserChunkMessage.tsx:45 msgid "Failed to delete response" msgstr "Fehler beim Löschen der Antwort" @@ -2310,11 +2310,11 @@ msgstr "" msgid "participant.button.i.understand" msgstr "Ich verstehe" -#: src/components/common/RedactedText.tsx:11 +#: src/components/common/RedactedText.tsx:39 msgid "IBAN" msgstr "IBAN" -#: src/components/common/RedactedText.tsx:12 +#: src/components/common/RedactedText.tsx:40 msgid "ID" msgstr "ID" @@ -2523,7 +2523,7 @@ msgstr "Bibliothekserstellung läuft" #~ msgid "library.regenerate" #~ msgstr "Bibliothek neu generieren" -#: src/components/common/RedactedText.tsx:13 +#: src/components/common/RedactedText.tsx:41 msgid "License Plate" msgstr "Kennzeichen" @@ -2698,7 +2698,7 @@ msgstr "Gespräch zu einem anderen Projekt verschieben" #: src/components/project/ProjectBasicEdit.tsx:104 #: src/components/project/webhooks/WebhookSettingsCard.tsx:377 #: src/components/conversation/ConversationEdit.tsx:308 -#: src/components/common/RedactedText.tsx:14 +#: src/components/common/RedactedText.tsx:42 msgid "Name" msgstr "Name" @@ -3239,7 +3239,7 @@ msgstr "Vorlesen pausieren" msgid "Pending" msgstr "Ausstehend" -#: src/components/common/RedactedText.tsx:15 +#: src/components/common/RedactedText.tsx:43 msgid "Phone" msgstr "Telefon" @@ -4526,7 +4526,7 @@ msgstr "Diese E-Mail ist bereits in der Liste." msgid "participant.modal.refine.info.available.in" msgstr "Diese Funktion wird in {remainingTime} Sekunden verfügbar sein." -#: src/components/common/RedactedText.tsx:33 +#: src/components/common/RedactedText.tsx:61 msgid "This information is anonymized" msgstr "Diese Informationen sind anonymisiert" @@ -4981,7 +4981,7 @@ msgstr "PII Redaction verwenden" msgid "Use Shift + Enter to add a new line" msgstr "Verwenden Sie Shift + Enter, um eine neue Zeile hinzuzufügen" -#: src/components/common/RedactedText.tsx:16 +#: src/components/common/RedactedText.tsx:44 msgid "Username" msgstr "Benutzername" diff --git a/echo/frontend/src/locales/en-US.po b/echo/frontend/src/locales/en-US.po index 9eba595d..1678fb1c 100644 --- a/echo/frontend/src/locales/en-US.po +++ b/echo/frontend/src/locales/en-US.po @@ -500,7 +500,7 @@ msgstr "{readingNow} reading now" msgid "library.conversations.still.processing" msgstr "{unfinishedConversationsCount} still processing." -#: src/components/participant/UserChunkMessage.tsx:108 +#: src/components/participant/UserChunkMessage.tsx:107 msgid "*Transcription in progress.*" msgstr "*Transcription in progress.*" @@ -669,7 +669,7 @@ msgstr "Adding Context:" msgid "select.all.modal.loading.title" msgstr "Adding Conversations" -#: src/components/common/RedactedText.tsx:8 +#: src/components/common/RedactedText.tsx:36 msgid "Address" msgstr "Address" @@ -1167,7 +1167,7 @@ msgstr "Cancel" msgid "Cannot add empty conversation" msgstr "Cannot add empty conversation" -#: src/components/common/RedactedText.tsx:9 +#: src/components/common/RedactedText.tsx:37 msgid "Card" msgstr "Card" @@ -1632,7 +1632,7 @@ msgstr "Delete Project" msgid "Delete Webhook" msgstr "Delete Webhook" -#: src/components/participant/UserChunkMessage.tsx:69 +#: src/components/participant/UserChunkMessage.tsx:68 msgid "Deleted successfully" msgstr "Deleted successfully" @@ -1863,7 +1863,7 @@ msgstr "Editing mode" #: src/routes/auth/Login.tsx:251 #: src/routes/auth/Login.tsx:255 -#: src/components/common/RedactedText.tsx:10 +#: src/components/common/RedactedText.tsx:38 msgid "Email" msgstr "Email" @@ -2179,7 +2179,7 @@ msgstr "Failed to create custom topic" msgid "Failed to delete custom topic" msgstr "Failed to delete custom topic" -#: src/components/participant/UserChunkMessage.tsx:46 +#: src/components/participant/UserChunkMessage.tsx:45 msgid "Failed to delete response" msgstr "Failed to delete response" @@ -2588,11 +2588,11 @@ msgstr "" msgid "participant.button.i.understand" msgstr "I understand" -#: src/components/common/RedactedText.tsx:11 +#: src/components/common/RedactedText.tsx:39 msgid "IBAN" msgstr "IBAN" -#: src/components/common/RedactedText.tsx:12 +#: src/components/common/RedactedText.tsx:40 msgid "ID" msgstr "ID" @@ -2810,7 +2810,7 @@ msgstr "Library creation is in progress" #~ msgid "Library is currently being processed" #~ msgstr "Library is currently being processed" -#: src/components/common/RedactedText.tsx:13 +#: src/components/common/RedactedText.tsx:41 msgid "License Plate" msgstr "License Plate" @@ -2990,7 +2990,7 @@ msgstr "Move to Project" #: src/components/project/ProjectBasicEdit.tsx:104 #: src/components/project/webhooks/WebhookSettingsCard.tsx:377 #: src/components/conversation/ConversationEdit.tsx:308 -#: src/components/common/RedactedText.tsx:14 +#: src/components/common/RedactedText.tsx:42 msgid "Name" msgstr "Name" @@ -3446,7 +3446,7 @@ msgstr "Pause reading" msgid "Pending" msgstr "Pending" -#: src/components/common/RedactedText.tsx:15 +#: src/components/common/RedactedText.tsx:43 msgid "Phone" msgstr "Phone" @@ -4810,7 +4810,7 @@ msgstr "This email is already in the list." msgid "participant.modal.refine.info.available.in" msgstr "This feature will be available in {remainingTime} seconds." -#: src/components/common/RedactedText.tsx:33 +#: src/components/common/RedactedText.tsx:61 msgid "This information is anonymized" msgstr "This information is anonymized" @@ -5295,7 +5295,7 @@ msgstr "Use PII Redaction" msgid "Use Shift + Enter to add a new line" msgstr "Use Shift + Enter to add a new line" -#: src/components/common/RedactedText.tsx:16 +#: src/components/common/RedactedText.tsx:44 msgid "Username" msgstr "Username" diff --git a/echo/frontend/src/locales/es-ES.po b/echo/frontend/src/locales/es-ES.po index fc060d2c..93bc6dff 100644 --- a/echo/frontend/src/locales/es-ES.po +++ b/echo/frontend/src/locales/es-ES.po @@ -314,7 +314,7 @@ msgstr "{readingNow} leyendo ahora" msgid "library.conversations.still.processing" msgstr "{0} aún en proceso." -#: src/components/participant/UserChunkMessage.tsx:108 +#: src/components/participant/UserChunkMessage.tsx:107 msgid "*Transcription in progress.*" msgstr "*Transcripción en progreso.*" @@ -480,7 +480,7 @@ msgstr "Añadiendo Contexto:" msgid "select.all.modal.loading.title" msgstr "Añadiendo conversaciones" -#: src/components/common/RedactedText.tsx:8 +#: src/components/common/RedactedText.tsx:36 msgid "Address" msgstr "Dirección" @@ -953,7 +953,7 @@ msgstr "Cancelar" msgid "Cannot add empty conversation" msgstr "No se puede añadir una conversación vacía" -#: src/components/common/RedactedText.tsx:9 +#: src/components/common/RedactedText.tsx:37 msgid "Card" msgstr "Tarjeta" @@ -1417,7 +1417,7 @@ msgstr "Eliminar Proyecto" msgid "Delete Webhook" msgstr "Eliminar Webhook" -#: src/components/participant/UserChunkMessage.tsx:69 +#: src/components/participant/UserChunkMessage.tsx:68 msgid "Deleted successfully" msgstr "Eliminado con éxito" @@ -1628,7 +1628,7 @@ msgstr "Modo de edición" #: src/routes/auth/Login.tsx:251 #: src/routes/auth/Login.tsx:255 -#: src/components/common/RedactedText.tsx:10 +#: src/components/common/RedactedText.tsx:38 msgid "Email" msgstr "Correo electrónico" @@ -1925,7 +1925,7 @@ msgstr "Error al crear el tema personalizado" msgid "Failed to delete custom topic" msgstr "Error al eliminar el tema personalizado" -#: src/components/participant/UserChunkMessage.tsx:46 +#: src/components/participant/UserChunkMessage.tsx:45 msgid "Failed to delete response" msgstr "Error al eliminar la respuesta" @@ -2313,11 +2313,11 @@ msgstr "" msgid "participant.button.i.understand" msgstr "Entiendo" -#: src/components/common/RedactedText.tsx:11 +#: src/components/common/RedactedText.tsx:39 msgid "IBAN" msgstr "IBAN" -#: src/components/common/RedactedText.tsx:12 +#: src/components/common/RedactedText.tsx:40 msgid "ID" msgstr "ID" @@ -2526,7 +2526,7 @@ msgstr "La creación de la biblioteca está en progreso" #~ msgid "library.regenerate" #~ msgstr "Biblioteca regenerada" -#: src/components/common/RedactedText.tsx:13 +#: src/components/common/RedactedText.tsx:41 msgid "License Plate" msgstr "Matrícula" @@ -2701,7 +2701,7 @@ msgstr "Mover a Proyecto" #: src/components/project/ProjectBasicEdit.tsx:104 #: src/components/project/webhooks/WebhookSettingsCard.tsx:377 #: src/components/conversation/ConversationEdit.tsx:308 -#: src/components/common/RedactedText.tsx:14 +#: src/components/common/RedactedText.tsx:42 msgid "Name" msgstr "Nombre" @@ -3242,7 +3242,7 @@ msgstr "Pausar lectura" msgid "Pending" msgstr "Pendiente" -#: src/components/common/RedactedText.tsx:15 +#: src/components/common/RedactedText.tsx:43 msgid "Phone" msgstr "Teléfono" @@ -4532,7 +4532,7 @@ msgstr "Este correo electrónico ya está en la lista." msgid "participant.modal.refine.info.available.in" msgstr "Esta función estará disponible en {remainingTime} segundos." -#: src/components/common/RedactedText.tsx:33 +#: src/components/common/RedactedText.tsx:61 msgid "This information is anonymized" msgstr "Esta información está anónima" @@ -4985,7 +4985,7 @@ msgstr "Usar redaction de PII" msgid "Use Shift + Enter to add a new line" msgstr "Usa Shift + Enter para agregar una nueva línea" -#: src/components/common/RedactedText.tsx:16 +#: src/components/common/RedactedText.tsx:44 msgid "Username" msgstr "Nombre de usuario" diff --git a/echo/frontend/src/locales/fr-FR.po b/echo/frontend/src/locales/fr-FR.po index c6375560..8f1b54d9 100644 --- a/echo/frontend/src/locales/fr-FR.po +++ b/echo/frontend/src/locales/fr-FR.po @@ -329,7 +329,7 @@ msgstr "{readingNow} lit actuellement" msgid "library.conversations.still.processing" msgstr "{0} en cours de traitement." -#: src/components/participant/UserChunkMessage.tsx:108 +#: src/components/participant/UserChunkMessage.tsx:107 msgid "*Transcription in progress.*" msgstr "*Transcription en cours.*" @@ -495,7 +495,7 @@ msgstr "Ajout du contexte :" msgid "select.all.modal.loading.title" msgstr "Ajout de conversations" -#: src/components/common/RedactedText.tsx:8 +#: src/components/common/RedactedText.tsx:36 msgid "Address" msgstr "Adresse" @@ -968,7 +968,7 @@ msgstr "Annuler" msgid "Cannot add empty conversation" msgstr "Impossible d'ajouter une conversation vide" -#: src/components/common/RedactedText.tsx:9 +#: src/components/common/RedactedText.tsx:37 msgid "Card" msgstr "Carte" @@ -1432,7 +1432,7 @@ msgstr "Supprimer le projet" msgid "Delete Webhook" msgstr "Supprimer le webhook" -#: src/components/participant/UserChunkMessage.tsx:69 +#: src/components/participant/UserChunkMessage.tsx:68 msgid "Deleted successfully" msgstr "Supprimé avec succès" @@ -1643,7 +1643,7 @@ msgstr "Mode d'édition" #: src/routes/auth/Login.tsx:251 #: src/routes/auth/Login.tsx:255 -#: src/components/common/RedactedText.tsx:10 +#: src/components/common/RedactedText.tsx:38 msgid "Email" msgstr "E-mail" @@ -1940,7 +1940,7 @@ msgstr "Échec de la création du sujet personnalisé" msgid "Failed to delete custom topic" msgstr "Échec de la suppression du sujet personnalisé" -#: src/components/participant/UserChunkMessage.tsx:46 +#: src/components/participant/UserChunkMessage.tsx:45 msgid "Failed to delete response" msgstr "Échec de la suppression de la réponse" @@ -2328,11 +2328,11 @@ msgstr "" msgid "participant.button.i.understand" msgstr "Je comprends" -#: src/components/common/RedactedText.tsx:11 +#: src/components/common/RedactedText.tsx:39 msgid "IBAN" msgstr "IBAN" -#: src/components/common/RedactedText.tsx:12 +#: src/components/common/RedactedText.tsx:40 msgid "ID" msgstr "ID" @@ -2541,7 +2541,7 @@ msgstr "Création de la bibliothèque en cours" #~ msgid "library.regenerate" #~ msgstr "Bibliothèque régénérée" -#: src/components/common/RedactedText.tsx:13 +#: src/components/common/RedactedText.tsx:41 msgid "License Plate" msgstr "Plaque d'immatriculation" @@ -2716,7 +2716,7 @@ msgstr "Déplacer vers un projet" #: src/components/project/ProjectBasicEdit.tsx:104 #: src/components/project/webhooks/WebhookSettingsCard.tsx:377 #: src/components/conversation/ConversationEdit.tsx:308 -#: src/components/common/RedactedText.tsx:14 +#: src/components/common/RedactedText.tsx:42 msgid "Name" msgstr "Nom" @@ -3257,7 +3257,7 @@ msgstr "Mettre en pause la lecture" msgid "Pending" msgstr "En attente" -#: src/components/common/RedactedText.tsx:15 +#: src/components/common/RedactedText.tsx:43 msgid "Phone" msgstr "Téléphone" @@ -4547,7 +4547,7 @@ msgstr "Cette e-mail est déjà dans la liste." msgid "participant.modal.refine.info.available.in" msgstr "Cette fonctionnalité sera disponible dans {remainingTime} secondes." -#: src/components/common/RedactedText.tsx:33 +#: src/components/common/RedactedText.tsx:61 msgid "This information is anonymized" msgstr "Ces informations sont anonymisées" @@ -5000,7 +5000,7 @@ msgstr "Utiliser la rédaction PII" msgid "Use Shift + Enter to add a new line" msgstr "Utilisez Shift + Entrée pour ajouter une nouvelle ligne" -#: src/components/common/RedactedText.tsx:16 +#: src/components/common/RedactedText.tsx:44 msgid "Username" msgstr "Nom d'utilisateur" diff --git a/echo/frontend/src/locales/it-IT.po b/echo/frontend/src/locales/it-IT.po index 9eba595d..1678fb1c 100644 --- a/echo/frontend/src/locales/it-IT.po +++ b/echo/frontend/src/locales/it-IT.po @@ -500,7 +500,7 @@ msgstr "{readingNow} reading now" msgid "library.conversations.still.processing" msgstr "{unfinishedConversationsCount} still processing." -#: src/components/participant/UserChunkMessage.tsx:108 +#: src/components/participant/UserChunkMessage.tsx:107 msgid "*Transcription in progress.*" msgstr "*Transcription in progress.*" @@ -669,7 +669,7 @@ msgstr "Adding Context:" msgid "select.all.modal.loading.title" msgstr "Adding Conversations" -#: src/components/common/RedactedText.tsx:8 +#: src/components/common/RedactedText.tsx:36 msgid "Address" msgstr "Address" @@ -1167,7 +1167,7 @@ msgstr "Cancel" msgid "Cannot add empty conversation" msgstr "Cannot add empty conversation" -#: src/components/common/RedactedText.tsx:9 +#: src/components/common/RedactedText.tsx:37 msgid "Card" msgstr "Card" @@ -1632,7 +1632,7 @@ msgstr "Delete Project" msgid "Delete Webhook" msgstr "Delete Webhook" -#: src/components/participant/UserChunkMessage.tsx:69 +#: src/components/participant/UserChunkMessage.tsx:68 msgid "Deleted successfully" msgstr "Deleted successfully" @@ -1863,7 +1863,7 @@ msgstr "Editing mode" #: src/routes/auth/Login.tsx:251 #: src/routes/auth/Login.tsx:255 -#: src/components/common/RedactedText.tsx:10 +#: src/components/common/RedactedText.tsx:38 msgid "Email" msgstr "Email" @@ -2179,7 +2179,7 @@ msgstr "Failed to create custom topic" msgid "Failed to delete custom topic" msgstr "Failed to delete custom topic" -#: src/components/participant/UserChunkMessage.tsx:46 +#: src/components/participant/UserChunkMessage.tsx:45 msgid "Failed to delete response" msgstr "Failed to delete response" @@ -2588,11 +2588,11 @@ msgstr "" msgid "participant.button.i.understand" msgstr "I understand" -#: src/components/common/RedactedText.tsx:11 +#: src/components/common/RedactedText.tsx:39 msgid "IBAN" msgstr "IBAN" -#: src/components/common/RedactedText.tsx:12 +#: src/components/common/RedactedText.tsx:40 msgid "ID" msgstr "ID" @@ -2810,7 +2810,7 @@ msgstr "Library creation is in progress" #~ msgid "Library is currently being processed" #~ msgstr "Library is currently being processed" -#: src/components/common/RedactedText.tsx:13 +#: src/components/common/RedactedText.tsx:41 msgid "License Plate" msgstr "License Plate" @@ -2990,7 +2990,7 @@ msgstr "Move to Project" #: src/components/project/ProjectBasicEdit.tsx:104 #: src/components/project/webhooks/WebhookSettingsCard.tsx:377 #: src/components/conversation/ConversationEdit.tsx:308 -#: src/components/common/RedactedText.tsx:14 +#: src/components/common/RedactedText.tsx:42 msgid "Name" msgstr "Name" @@ -3446,7 +3446,7 @@ msgstr "Pause reading" msgid "Pending" msgstr "Pending" -#: src/components/common/RedactedText.tsx:15 +#: src/components/common/RedactedText.tsx:43 msgid "Phone" msgstr "Phone" @@ -4810,7 +4810,7 @@ msgstr "This email is already in the list." msgid "participant.modal.refine.info.available.in" msgstr "This feature will be available in {remainingTime} seconds." -#: src/components/common/RedactedText.tsx:33 +#: src/components/common/RedactedText.tsx:61 msgid "This information is anonymized" msgstr "This information is anonymized" @@ -5295,7 +5295,7 @@ msgstr "Use PII Redaction" msgid "Use Shift + Enter to add a new line" msgstr "Use Shift + Enter to add a new line" -#: src/components/common/RedactedText.tsx:16 +#: src/components/common/RedactedText.tsx:44 msgid "Username" msgstr "Username" diff --git a/echo/frontend/src/locales/nl-NL.po b/echo/frontend/src/locales/nl-NL.po index 1fe8376a..6003af96 100644 --- a/echo/frontend/src/locales/nl-NL.po +++ b/echo/frontend/src/locales/nl-NL.po @@ -320,7 +320,7 @@ msgstr "{0} worden nog verwerkt." #~ msgid "*Oh, we don't have a cookie statement because we don't use cookies! We eat them.*" #~ msgstr "*Oh, we hebben geen cookievermelding want we gebruiken geen cookies! We eten ze op.*" -#: src/components/participant/UserChunkMessage.tsx:108 +#: src/components/participant/UserChunkMessage.tsx:107 msgid "*Transcription in progress.*" msgstr "*Een moment a.u.b.*" @@ -495,7 +495,7 @@ msgstr "Context toevoegen:" msgid "select.all.modal.loading.title" msgstr "Gesprekken toevoegen" -#: src/components/common/RedactedText.tsx:8 +#: src/components/common/RedactedText.tsx:36 msgid "Address" msgstr "Adres" @@ -1007,7 +1007,7 @@ msgstr "Annuleren" msgid "Cannot add empty conversation" msgstr "Kan geen leeg gesprek toevoegen" -#: src/components/common/RedactedText.tsx:9 +#: src/components/common/RedactedText.tsx:37 msgid "Card" msgstr "Kaart" @@ -1480,7 +1480,7 @@ msgstr "Verwijder project" msgid "Delete Webhook" msgstr "Webhook verwijderen" -#: src/components/participant/UserChunkMessage.tsx:69 +#: src/components/participant/UserChunkMessage.tsx:68 msgid "Deleted successfully" msgstr "Verwijderd succesvol" @@ -1712,7 +1712,7 @@ msgstr "Bewerkmode" #: src/routes/auth/Login.tsx:251 #: src/routes/auth/Login.tsx:255 -#: src/components/common/RedactedText.tsx:10 +#: src/components/common/RedactedText.tsx:38 msgid "Email" msgstr "E-mail" @@ -2015,7 +2015,7 @@ msgstr "Fout bij het aanmaken van het aangepaste onderwerp" msgid "Failed to delete custom topic" msgstr "Fout bij het verwijderen van het aangepaste onderwerp" -#: src/components/participant/UserChunkMessage.tsx:46 +#: src/components/participant/UserChunkMessage.tsx:45 msgid "Failed to delete response" msgstr "Fout bij het verwijderen van de reactie" @@ -2418,11 +2418,11 @@ msgstr "" msgid "participant.button.i.understand" msgstr "Ik begrijp het" -#: src/components/common/RedactedText.tsx:11 +#: src/components/common/RedactedText.tsx:39 msgid "IBAN" msgstr "IBAN" -#: src/components/common/RedactedText.tsx:12 +#: src/components/common/RedactedText.tsx:40 msgid "ID" msgstr "ID" @@ -2643,7 +2643,7 @@ msgstr "Bibliotheek wordt aangemaakt" #~ msgid "library.regenerate" #~ msgstr "Bibliotheek opnieuw genereren" -#: src/components/common/RedactedText.tsx:13 +#: src/components/common/RedactedText.tsx:41 msgid "License Plate" msgstr "Kenteken" @@ -2818,7 +2818,7 @@ msgstr "Verplaats naar project" #: src/components/project/ProjectBasicEdit.tsx:104 #: src/components/project/webhooks/WebhookSettingsCard.tsx:377 #: src/components/conversation/ConversationEdit.tsx:308 -#: src/components/common/RedactedText.tsx:14 +#: src/components/common/RedactedText.tsx:42 msgid "Name" msgstr "Naam" @@ -3377,7 +3377,7 @@ msgstr "Pauzeer het voorlezen" msgid "Pending" msgstr "In afwachting" -#: src/components/common/RedactedText.tsx:15 +#: src/components/common/RedactedText.tsx:43 msgid "Phone" msgstr "Telefoon" @@ -4690,7 +4690,7 @@ msgstr "Deze e-mail is al in de lijst." msgid "participant.modal.refine.info.available.in" msgstr "Deze functie is beschikbaar over {remainingTime} seconden." -#: src/components/common/RedactedText.tsx:33 +#: src/components/common/RedactedText.tsx:61 msgid "This information is anonymized" msgstr "Deze informatie is anoniem" @@ -5167,7 +5167,7 @@ msgstr "PII redaction gebruiken" msgid "Use Shift + Enter to add a new line" msgstr "Gebruik Shift + Enter om een nieuwe regel toe te voegen" -#: src/components/common/RedactedText.tsx:16 +#: src/components/common/RedactedText.tsx:44 msgid "Username" msgstr "Gebruikersnaam" diff --git a/echo/server/dembrane/api/verify.py b/echo/server/dembrane/api/verify.py index 1e090b40..ab578319 100644 --- a/echo/server/dembrane/api/verify.py +++ b/echo/server/dembrane/api/verify.py @@ -604,6 +604,7 @@ async def _get_conversation_with_project(conversation_id: str, client: DirectusC "project_id.language", "project_id.name", "project_id.is_verify_enabled", + "project_id.anonymize_transcripts", ], } }, @@ -745,6 +746,7 @@ def _build_user_message_content( artifacts: List[dict], transcript_text: str, audio_summary: str, + is_anonymized: bool = False, ) -> str: project = conversation.get("project_id") or {} parts = [ @@ -757,7 +759,9 @@ def _build_user_message_content( parts.append(f"Participant name: {participant_name}") participant_email = conversation.get("participant_email") if participant_email: - parts.append(f"Participant email: {participant_email}") + parts.append( + f"Participant email: {'' if is_anonymized else participant_email}" + ) parts.append("") # spacer parts.append(_format_previous_artifacts(artifacts)) @@ -819,9 +823,11 @@ async def generate_verification_artifacts( client = directus conversation = await _get_conversation_with_project(body.conversation_id, client) - project_id = (conversation.get("project_id") or {}).get("id") + project = conversation.get("project_id") or {} + project_id = project.get("id") if not project_id: raise HTTPException(status_code=400, detail="Conversation is missing project information") + is_anonymized = bool(project.get("anonymize_transcripts", False)) topics = await _get_verification_topics_for_project(project_id, client) topic_map = {topic.key: topic for topic in topics if topic.key} @@ -857,7 +863,9 @@ async def generate_verification_artifacts( audio_chunks = _select_audio_chunks(chunks, last_artifact_time) audio_summary = _format_audio_summary(audio_chunks) - user_text = _build_user_message_content(conversation, artifacts, transcript_text, audio_summary) + user_text = _build_user_message_content( + conversation, artifacts, transcript_text, audio_summary, is_anonymized=is_anonymized + ) message_content = [{"type": "text", "text": user_text}] for chunk in audio_chunks: @@ -878,7 +886,7 @@ async def generate_verification_artifacts( except Exception as exc: logger.warning("Failed to attach audio chunk %s: %s", chunk_id, exc) - project_language = (conversation.get("project_id") or {}).get("language") or "en" + project_language = project.get("language") or "en" system_prompt = render_prompt( "generate_artifact", @@ -886,6 +894,7 @@ async def generate_verification_artifacts( { "prompt": target_topic.prompt, "language": project_language, + "pii_redaction": is_anonymized, }, ) @@ -977,7 +986,9 @@ async def update_verification_artifact( reference_timestamp = body.use_conversation.timestamp conversation = await _get_conversation_with_project(reference_conversation_id, client) - project_language = (conversation.get("project_id") or {}).get("language") or "en" + project = conversation.get("project_id") or {} + project_language = project.get("language") or "en" + is_anonymized = bool(project.get("anonymize_transcripts", False)) chunks = await _get_conversation_chunks(reference_conversation_id, client) @@ -1002,6 +1013,7 @@ async def update_verification_artifact( "outcome": artifact.get("content") or "", "feedback": feedback_text or "No textual feedback available.", "language": project_language, + "pii_redaction": is_anonymized, }, ) diff --git a/echo/server/dembrane/reply_utils.py b/echo/server/dembrane/reply_utils.py index 14e2e36d..da8a62db 100644 --- a/echo/server/dembrane/reply_utils.py +++ b/echo/server/dembrane/reply_utils.py @@ -146,6 +146,7 @@ async def generate_reply_for_conversation( "project_id.default_conversation_title", "project_id.default_conversation_description", "project_id.default_conversation_transcript_prompt", + "project_id.anonymize_transcripts", "tags.project_tag_id.text", "participant_name", "replies.id", @@ -172,6 +173,8 @@ async def generate_reply_for_conversation( if conversation["project_id"]["is_get_reply_enabled"] is False: raise ValueError(f"Echo is not enabled for project {conversation['project_id']['id']}") + is_anonymized = bool(conversation["project_id"].get("anonymize_transcripts", False)) + current_conversation = Conversation( id=conversation["id"], name=conversation["participant_name"], @@ -401,6 +404,7 @@ async def generate_reply_for_conversation( "GLOBAL_PROMPT": global_prompt, "OTHER_TRANSCRIPTS": formatted_adjacent_conversation, "MAIN_USER_TRANSCRIPT": formatted_current_conversation, + "pii_redaction": is_anonymized, }, ) diff --git a/echo/server/prompt_templates/generate_artifact.en.jinja b/echo/server/prompt_templates/generate_artifact.en.jinja index 7d1ce549..b6631574 100644 --- a/echo/server/prompt_templates/generate_artifact.en.jinja +++ b/echo/server/prompt_templates/generate_artifact.en.jinja @@ -14,4 +14,25 @@ Use "we/our" language for collective ownership Be concise but complete enough to stand alone Include context for future readers Acknowledge uncertainty when present -Make it feel worth signing—true, not perfect \ No newline at end of file +Make it feel worth signing—true, not perfect +{% if pii_redaction %} + +PII Redaction (MANDATORY): +This conversation is anonymized. You MUST redact all personally identifiable information from your output. +- Detect PII with NER/patterns: personal names, emails, phone numbers, addresses/postcodes, usernames/handles, unique IDs (passport/national ID), IBAN, credit cards, license plates. +- Locale hints (examples): NL phone (+31/0… patterns), NL IBAN (NL##), postcode "1234 AB", BSN 8–9 digits. +- Allow-list: if a detected PII token or exact multi-token phrase appears in keyterms, KEEP it as spoken (no redaction). +- Otherwise, ALWAYS replace the entire detected PII span with the exact lowercase angle-bracket placeholder: + PERSON → + EMAIL → + PHONE → + ADDRESS/POSTCODE → + USERNAME/HANDLE → + NATIONAL ID/PASSPORT → + IBAN → + CREDIT CARD → + LICENSE PLATE → +- Do not invent other angle-bracket tags. +- Preserve surrounding punctuation; after redaction, collapse extra spaces and fix stray commas/periods. +Never output real names, emails, phone numbers, or other PII. If the transcript already contains placeholders, preserve them exactly. +{% endif %} \ No newline at end of file diff --git a/echo/server/prompt_templates/get_reply_system.de.jinja b/echo/server/prompt_templates/get_reply_system.de.jinja index b34d6eb1..ef1f8218 100644 --- a/echo/server/prompt_templates/get_reply_system.de.jinja +++ b/echo/server/prompt_templates/get_reply_system.de.jinja @@ -28,3 +28,24 @@ Ihre endgültige Antwort muss innerhalb von 1-3 Sätzen sein Verwenden Sie "wir/unser" Sprache für kollektives Eigentum Passen Sie sich der Sprache des Transkripts an Erkennen Sie Unsicherheit an, wenn vorhanden +{% if pii_redaction %} + +PII-Redaktion (PFLICHT): +Dieses Gespräch ist anonymisiert. Sie MÜSSEN alle personenbezogenen Daten aus Ihrer Ausgabe entfernen. +- Erkennen Sie PII mit NER/Mustern: Personennamen, E-Mail-Adressen, Telefonnummern, Adressen/Postleitzahlen, Benutzernamen, eindeutige IDs (Reisepass/Personalausweis), IBAN, Kreditkarten, Kennzeichen. +- Locale-Hinweise (Beispiele): NL Telefon (+31/0… Muster), NL IBAN (NL##), Postleitzahl "1234 AB", BSN 8–9 Ziffern. +- Erlaubnisliste: Wenn ein erkanntes PII-Token oder eine exakte Multi-Token-Phrase in den Keyterms vorkommt, BEHALTEN Sie es wie gesprochen bei (keine Redaktion). +- Ersetzen Sie andernfalls IMMER die gesamte erkannte PII-Spanne durch den exakten Platzhalter in spitzen Klammern (Kleinbuchstaben): + Personenname → + E-Mail → + Telefon → + Adresse/Postleitzahl → + Benutzername → + Ausweis/Reisepass → + IBAN → + Kreditkarte → + Kennzeichen → +- Erfinden Sie keine anderen Tags mit spitzen Klammern. +- Bewahren Sie umgebende Satzzeichen; entfernen Sie nach der Redaktion zusätzliche Leerzeichen und korrigieren Sie lose Kommas/Punkte. +Geben Sie niemals echte Namen, E-Mail-Adressen, Telefonnummern oder andere PII aus. Wenn das Transkript bereits Platzhalter enthält, behalten Sie diese exakt bei. +{% endif %} diff --git a/echo/server/prompt_templates/get_reply_system.en.jinja b/echo/server/prompt_templates/get_reply_system.en.jinja index 02917de6..0c936b19 100644 --- a/echo/server/prompt_templates/get_reply_system.en.jinja +++ b/echo/server/prompt_templates/get_reply_system.en.jinja @@ -27,4 +27,25 @@ Your final response must be within 1-3 sentences Use "we/our" language for collective ownership Match the transcript's language -Acknowledge uncertainty when present \ No newline at end of file +Acknowledge uncertainty when present +{% if pii_redaction %} + +PII Redaction (MANDATORY): +This conversation is anonymized. You MUST redact all personally identifiable information from your output. +- Detect PII with NER/patterns: personal names, emails, phone numbers, addresses/postcodes, usernames/handles, unique IDs (passport/national ID), IBAN, credit cards, license plates. +- Locale hints (examples): NL phone (+31/0… patterns), NL IBAN (NL##), postcode "1234 AB", BSN 8–9 digits. +- Allow-list: if a detected PII token or exact multi-token phrase appears in keyterms, KEEP it as spoken (no redaction). +- Otherwise, ALWAYS replace the entire detected PII span with the exact lowercase angle-bracket placeholder: + PERSON → + EMAIL → + PHONE → + ADDRESS/POSTCODE → + USERNAME/HANDLE → + NATIONAL ID/PASSPORT → + IBAN → + CREDIT CARD → + LICENSE PLATE → +- Do not invent other angle-bracket tags. +- Preserve surrounding punctuation; after redaction, collapse extra spaces and fix stray commas/periods. +Never output real names, emails, phone numbers, or other PII. If the transcript already contains placeholders, preserve them exactly. +{% endif %} \ No newline at end of file diff --git a/echo/server/prompt_templates/get_reply_system.es.jinja b/echo/server/prompt_templates/get_reply_system.es.jinja index 165c66d9..400224a1 100644 --- a/echo/server/prompt_templates/get_reply_system.es.jinja +++ b/echo/server/prompt_templates/get_reply_system.es.jinja @@ -28,3 +28,24 @@ Tu respuesta final debe ser de 1-3 oraciones Usa el lenguaje "nosotros/nuestro" para la propiedad colectiva Adapta tu respuesta al idioma de la transcripción Reconoce la incertidumbre cuando esté presente +{% if pii_redaction %} + +Redacción de PII (OBLIGATORIO): +Esta conversación está anonimizada. DEBES eliminar toda información de identificación personal de tu respuesta. +- Detecta PII con NER/patrones: nombres de personas, correos electrónicos, números de teléfono, direcciones/códigos postales, nombres de usuario, IDs únicos (pasaporte/documento de identidad), IBAN, tarjetas de crédito, matrículas. +- Indicaciones de locale (ejemplos): teléfono NL (+31/0… patrones), IBAN NL (NL##), código postal "1234 AB", BSN 8–9 dígitos. +- Lista de permitidos: si un token PII detectado o frase multi-token exacta aparece en los keyterms, MANTENLO tal cual (sin redacción). +- De lo contrario, reemplaza SIEMPRE todo el rango PII detectado con el marcador exacto en minúsculas entre corchetes angulares: + Nombre de persona → + Correo electrónico → + Teléfono → + Dirección/código postal → + Nombre de usuario → + Documento de identidad/pasaporte → + IBAN → + Tarjeta de crédito → + Matrícula → +- No inventes otras etiquetas con corchetes angulares. +- Preserva la puntuación circundante; después de la redacción, elimina espacios extra y corrige comas/puntos sueltos. +Nunca muestres nombres reales, correos electrónicos, números de teléfono u otra PII. Si la transcripción ya contiene marcadores , consérvalos exactamente. +{% endif %} diff --git a/echo/server/prompt_templates/get_reply_system.fr.jinja b/echo/server/prompt_templates/get_reply_system.fr.jinja index f59fdc8f..06821749 100644 --- a/echo/server/prompt_templates/get_reply_system.fr.jinja +++ b/echo/server/prompt_templates/get_reply_system.fr.jinja @@ -28,3 +28,24 @@ Votre réponse finale doit être de 1-3 phrases Utilisez le langage "nous/notre" pour la propriété collective Adaptez-vous à la langue de la transcription Reconnaissez l'incertitude lorsqu'elle est présente +{% if pii_redaction %} + +Rédaction PII (OBLIGATOIRE) : +Cette conversation est anonymisée. Vous DEVEZ supprimer toutes les informations personnellement identifiables de votre réponse. +- Détectez les PII avec NER/modèles : noms de personnes, e-mails, numéros de téléphone, adresses/codes postaux, noms d'utilisateur, identifiants uniques (passeport/carte d'identité), IBAN, cartes de crédit, plaques d'immatriculation. +- Indices de locale (exemples) : téléphone NL (+31/0… modèles), IBAN NL (NL##), code postal "1234 AB", BSN 8–9 chiffres. +- Liste d'autorisation : si un token PII détecté ou une phrase multi-token exacte apparaît dans les keyterms, CONSERVEZ-le tel quel (pas de rédaction). +- Sinon, remplacez TOUJOURS l'intégralité de la plage PII détectée par le marqueur exact en minuscules entre crochets angulaires : + Nom de personne → + E-mail → + Téléphone → + Adresse/code postal → + Nom d'utilisateur → + Pièce d'identité/passeport → + IBAN → + Carte de crédit → + Plaque d'immatriculation → +- N'inventez pas d'autres balises entre crochets angulaires. +- Préservez la ponctuation environnante ; après la rédaction, supprimez les espaces superflus et corrigez les virgules/points isolés. +Ne jamais afficher de vrais noms, e-mails, numéros de téléphone ou autres PII. Si la transcription contient déjà des marqueurs , conservez-les exactement. +{% endif %} diff --git a/echo/server/prompt_templates/get_reply_system.it.jinja b/echo/server/prompt_templates/get_reply_system.it.jinja index 47321910..9f8e00d3 100644 --- a/echo/server/prompt_templates/get_reply_system.it.jinja +++ b/echo/server/prompt_templates/get_reply_system.it.jinja @@ -28,3 +28,24 @@ La tua risposta finale deve essere di 1-3 frasi Usa il linguaggio "noi/nostro" per la proprietà collettiva Adatta la lingua della trascrizione Riconosci l'incertezza quando presente +{% if pii_redaction %} + +Redazione PII (OBBLIGATORIO): +Questa conversazione è anonimizzata. DEVI rimuovere tutte le informazioni di identificazione personale dal tuo output. +- Rileva PII con NER/modelli: nomi di persone, e-mail, numeri di telefono, indirizzi/codici postali, nomi utente, ID univoci (passaporto/carta d'identità), IBAN, carte di credito, targhe. +- Indicazioni di locale (esempi): telefono NL (+31/0… modelli), IBAN NL (NL##), codice postale "1234 AB", BSN 8–9 cifre. +- Lista consentiti: se un token PII rilevato o una frase multi-token esatta appare nei keyterms, MANTIENILO così com'è (nessuna redazione). +- Altrimenti, sostituisci SEMPRE l'intero intervallo PII rilevato con il segnaposto esatto in minuscolo tra parentesi angolari: + Nome persona → + E-mail → + Telefono → + Indirizzo/codice postale → + Nome utente → + Documento d'identità/passaporto → + IBAN → + Carta di credito → + Targa → +- Non inventare altri tag con parentesi angolari. +- Preserva la punteggiatura circostante; dopo la redazione, elimina gli spazi extra e correggi virgole/punti isolati. +Non mostrare mai nomi reali, e-mail, numeri di telefono o altri PII. Se la trascrizione contiene già segnaposto , conservali esattamente. +{% endif %} diff --git a/echo/server/prompt_templates/get_reply_system.nl.jinja b/echo/server/prompt_templates/get_reply_system.nl.jinja index ff7dbf1f..06e0b880 100644 --- a/echo/server/prompt_templates/get_reply_system.nl.jinja +++ b/echo/server/prompt_templates/get_reply_system.nl.jinja @@ -28,3 +28,24 @@ Uw uiteindelijke antwoord moet binnen 1-3 zinnen zijn Gebruik "wij/onze" taal voor collectief eigenaarschap Stem af op de taal van het transcript Erken onzekerheid wanneer aanwezig +{% if pii_redaction %} + +PII-redactie (VERPLICHT): +Dit gesprek is geanonimiseerd. Je MOET alle persoonlijk identificeerbare informatie uit je antwoord verwijderen. +- Detecteer PII met NER/patronen: persoonsnamen, e-mailadressen, telefoonnummers, adressen/postcodes, gebruikersnamen, unieke ID's (paspoort/nationaal ID), IBAN, creditcards, kentekens. +- Locale-hints (voorbeelden): NL telefoon (+31/0… patronen), NL IBAN (NL##), postcode "1234 AB", BSN 8–9 cijfers. +- Toestemmingslijst: als een gedetecteerd PII-token of exacte multi-token-frase voorkomt in keyterms, BEHOUD het zoals uitgesproken (geen redactie). +- Vervang anders ALTIJD de volledige gedetecteerde PII-reeks door de exacte plaatsaanduiding in kleine letters met punthaken: + Persoonsnaam → + E-mail → + Telefoon → + Adres/postcode → + Gebruikersnaam → + Nationaal ID/paspoort → + IBAN → + Creditcard → + Kenteken → +- Verzin geen andere tags met punthaken. +- Behoud omringende leestekens; verwijder na redactie extra spaties en herstel losse komma's/punten. +Geef nooit echte namen, e-mailadressen, telefoonnummers of andere PII weer. Als het transcript al plaatsaanduidingen bevat, behoud ze dan exact. +{% endif %} diff --git a/echo/server/prompt_templates/revise_artifact.en.jinja b/echo/server/prompt_templates/revise_artifact.en.jinja index ff4e4107..2a328dff 100644 --- a/echo/server/prompt_templates/revise_artifact.en.jinja +++ b/echo/server/prompt_templates/revise_artifact.en.jinja @@ -23,4 +23,25 @@ Address whatever participants need: add missing details, clarify ambiguities, ex State what something is, not what it isn't (avoid "X is not Y" unless essential) Maintain coherent structure even when adding depth or detail Maintain the original tone -Remove any references to the feedback process itself \ No newline at end of file +Remove any references to the feedback process itself +{% if pii_redaction %} + +PII Redaction (MANDATORY): +This conversation is anonymized. You MUST redact all personally identifiable information from your output. +- Detect PII with NER/patterns: personal names, emails, phone numbers, addresses/postcodes, usernames/handles, unique IDs (passport/national ID), IBAN, credit cards, license plates. +- Locale hints (examples): NL phone (+31/0… patterns), NL IBAN (NL##), postcode "1234 AB", BSN 8–9 digits. +- Allow-list: if a detected PII token or exact multi-token phrase appears in keyterms, KEEP it as spoken (no redaction). +- Otherwise, ALWAYS replace the entire detected PII span with the exact lowercase angle-bracket placeholder: + PERSON → + EMAIL → + PHONE → + ADDRESS/POSTCODE → + USERNAME/HANDLE → + NATIONAL ID/PASSPORT → + IBAN → + CREDIT CARD → + LICENSE PLATE → +- Do not invent other angle-bracket tags. +- Preserve surrounding punctuation; after redaction, collapse extra spaces and fix stray commas/periods. +Never output real names, emails, phone numbers, or other PII. If the transcript already contains placeholders, preserve them exactly. +{% endif %} \ No newline at end of file