J4 — mention d'information aux visiteurs dans l'assistant
§8.3 des CGU : la mention doit être accessible depuis l'assistant lui-même, là où la collecte a lieu. Panneau dépliable dans l'en-tête, borné en hauteur et défilant pour ne pas chasser le fil de conversation hors de l'écran. Texte repris mot pour mot de DOCS/mention-information-visiteurs.md, comme la version Dart de mymuseum-visitapp : les trois doivent rester alignés, et une correction se fait dans le document d'abord. FR/NL/EN avec repli sur l'anglais, alors que le reste de cette interface parle six langues : traduire une mention de protection des données sans relecture humaine serait pire que la servir en anglais. npm run build vert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
572327a651
commit
cb7f045b0b
@ -53,10 +53,65 @@ const UI: Record<string, Record<string, string>> = {
|
||||
ES: 'La guía descansa por hoy. Vuelva mañana.',
|
||||
IT: 'La guida riposa per oggi. Torna domani.',
|
||||
},
|
||||
privacy: {
|
||||
FR: 'Vos questions au guide',
|
||||
NL: 'Uw vragen aan de gids',
|
||||
EN: 'Your questions to the guide',
|
||||
DE: 'Ihre Fragen an den Guide',
|
||||
ES: 'Sus preguntas a la guía',
|
||||
IT: 'Le tue domande alla guida',
|
||||
},
|
||||
}
|
||||
|
||||
const label = (key: keyof typeof UI, lang: string) => UI[key][lang] ?? UI[key].FR
|
||||
|
||||
/// Mention d'information aux visiteurs (§8.3 des CGU) — miroir de
|
||||
/// `mymuseum-visitapp/lib/Components/VisitorPrivacyNotice.dart`, lui-même repris mot pour
|
||||
/// mot de `DOCS/mention-information-visiteurs.md`. **Les trois doivent rester alignés.**
|
||||
///
|
||||
/// ⚠️ **Trois langues, repli sur l'anglais — c'est un choix, pas un oubli.** Le reste de
|
||||
/// cette interface parle six langues, mais traduire une mention de protection des données
|
||||
/// sans relecture humaine serait pire que la servir en anglais : une nuance perdue sur
|
||||
/// « nous n'enregistrons pas votre adresse IP » n'est pas une coquille d'interface. Les
|
||||
/// autres langues attendent une traduction relue, à demander avec la relecture juridique.
|
||||
const PRIVACY_NOTICE: Record<string, string> = {
|
||||
FR:
|
||||
"Lorsque vous posez une question à notre guide, votre question et la réponse qui vous est " +
|
||||
"donnée sont enregistrées. Cela nous sert à repérer ce que nos visiteurs cherchent sans le " +
|
||||
"trouver, et à compléter nos contenus en conséquence.\n\n" +
|
||||
"Nous n'enregistrons ni votre nom, ni votre compte, ni votre adresse IP. Un identifiant de " +
|
||||
"session tiré au hasard permet seulement de relier entre elles les questions d'une même " +
|
||||
"conversation ; il disparaît avec elle.\n\n" +
|
||||
"Votre question est transmise à notre fournisseur d'intelligence artificielle (Google) pour " +
|
||||
"produire la réponse. Le texte de vos questions est supprimé au bout de 90 jours ; seuls des " +
|
||||
"regroupements par sujet, sans le texte de vos questions, sont conservés au-delà.\n\n" +
|
||||
"Le champ de question est libre : nous vous invitons à ne pas y saisir d'informations " +
|
||||
"personnelles.",
|
||||
NL:
|
||||
"Wanneer u onze gids een vraag stelt, worden uw vraag en het gegeven antwoord opgeslagen. Zo " +
|
||||
"zien we wat onze bezoekers zoeken zonder het te vinden, en vullen we onze inhoud aan.\n\n" +
|
||||
"Wij registreren noch uw naam, noch een account, noch uw IP-adres. Een willekeurig " +
|
||||
"gegenereerde sessie-identificatie dient enkel om de vragen van eenzelfde gesprek aan elkaar " +
|
||||
"te koppelen; ze verdwijnt samen met dat gesprek.\n\n" +
|
||||
"Uw vraag wordt doorgestuurd naar onze aanbieder van kunstmatige intelligentie (Google) om " +
|
||||
"het antwoord op te stellen. De tekst van uw vragen wordt na 90 dagen verwijderd; daarna " +
|
||||
"blijven enkel groeperingen per onderwerp bewaard, zonder de tekst van uw vragen.\n\n" +
|
||||
"Het vraagveld is vrij in te vullen: wij raden u aan er geen persoonsgegevens in te typen.",
|
||||
EN:
|
||||
"When you ask our guide a question, your question and the answer you are given are recorded. " +
|
||||
"This helps us see what our visitors look for without finding it, and improve our content " +
|
||||
"accordingly.\n\n" +
|
||||
"We record neither your name, nor an account, nor your IP address. A randomly generated " +
|
||||
"session identifier only links together the questions of a single conversation; it " +
|
||||
"disappears with it.\n\n" +
|
||||
"Your question is sent to our artificial intelligence provider (Google) to produce the " +
|
||||
"answer. The text of your questions is deleted after 90 days; beyond that, only groupings by " +
|
||||
"topic are kept, without the text of your questions.\n\n" +
|
||||
"The question field is free text: we invite you not to enter personal information in it.",
|
||||
}
|
||||
|
||||
const privacyNotice = (lang: string) => PRIVACY_NOTICE[lang] ?? PRIVACY_NOTICE.EN
|
||||
|
||||
/// L'API de reconnaissance vocale n'est pas standardisée partout : on ne montre le
|
||||
/// micro que si le navigateur la propose réellement.
|
||||
function getSpeechRecognition(): any {
|
||||
@ -77,6 +132,7 @@ export default function AssistantBubble({
|
||||
const [pending, setPending] = useState(false)
|
||||
const [listening, setListening] = useState(false)
|
||||
const [speechReady, setSpeechReady] = useState(false)
|
||||
const [noticeOpen, setNoticeOpen] = useState(false)
|
||||
|
||||
const threadRef = useRef<HTMLDivElement>(null)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
@ -233,6 +289,18 @@ export default function AssistantBubble({
|
||||
<strong>{label('title', language)}</strong>
|
||||
{venueName && <span>{venueName}</span>}
|
||||
</span>
|
||||
{/* §8.3 des CGU : la mention doit être accessible depuis l'assistant lui-même,
|
||||
c'est-à-dire là où la collecte a lieu. */}
|
||||
<button
|
||||
type="button"
|
||||
className="mim-assistant-icon-btn"
|
||||
onClick={() => setNoticeOpen((v) => !v)}
|
||||
aria-label={label('privacy', language)}
|
||||
aria-expanded={noticeOpen}
|
||||
title={label('privacy', language)}
|
||||
>
|
||||
<IconPrivacy />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="mim-assistant-icon-btn"
|
||||
@ -243,6 +311,15 @@ export default function AssistantBubble({
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{noticeOpen && (
|
||||
<section className="mim-assistant-notice">
|
||||
<strong>{label('privacy', language)}</strong>
|
||||
{privacyNotice(language).split('\n\n').map((paragraph, i) => (
|
||||
<p key={i}>{paragraph}</p>
|
||||
))}
|
||||
</section>
|
||||
)}
|
||||
|
||||
<div className="mim-assistant-thread" ref={threadRef} aria-live="polite">
|
||||
{turns.length === 0 && (
|
||||
<div className="mim-assistant-opener">
|
||||
@ -394,6 +471,12 @@ const IconChat = () => (
|
||||
const IconClose = () => (
|
||||
<svg viewBox="0 0 24 24" {...stroke} strokeWidth={2.1} aria-hidden="true"><path d="M18 6 6 18M6 6l12 12" /></svg>
|
||||
)
|
||||
const IconPrivacy = () => (
|
||||
<svg viewBox="0 0 24 24" {...stroke} aria-hidden="true">
|
||||
<path d="M12 3 4 6v6c0 5 3.4 8.4 8 9 4.6-.6 8-4 8-9V6l-8-3z" />
|
||||
<path d="M12 11v4M12 8.2v.1" />
|
||||
</svg>
|
||||
)
|
||||
const IconPin = () => (
|
||||
<svg viewBox="0 0 24 24" {...stroke} aria-hidden="true">
|
||||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" /><circle cx="12" cy="10" r="3" />
|
||||
|
||||
@ -83,6 +83,30 @@
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
flex: none;
|
||||
}
|
||||
/* Mention d'information visiteurs (§8.3 des CGU). Bornée en hauteur et défilante :
|
||||
le texte est long et ne doit pas chasser le fil de conversation hors de l'écran. */
|
||||
.mim-assistant-notice {
|
||||
flex: none;
|
||||
max-height: 45%;
|
||||
overflow-y: auto;
|
||||
padding: .85rem 1rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
background: var(--color-surface-2, rgba(0, 0, 0, .03));
|
||||
font-size: .82rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.mim-assistant-notice strong {
|
||||
display: block;
|
||||
margin-bottom: .5rem;
|
||||
font-size: .9rem;
|
||||
}
|
||||
.mim-assistant-notice p {
|
||||
margin: 0 0 .6rem;
|
||||
}
|
||||
.mim-assistant-notice p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.mim-assistant-avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user