This commit is contained in:
@@ -1,5 +1,22 @@
|
||||
# Changelog
|
||||
|
||||
## 1.7.0 - 2026-07-22
|
||||
|
||||
- Rebuilt the generator as a dependency-driven workflow: fields are shown only when the selected content and legal context make them relevant.
|
||||
- Added explicit AI Act role (`deployer`, `provider`, `both`, `unsure`) and professional/personal use-context inputs.
|
||||
- Added optional output date handling for the Article 50 application date and the Commission FAQ's non-retroactivity guidance.
|
||||
- Deepfake questions now appear only for AI-involved image/audio/video; public-interest text questions appear only for AI-involved text; the creative-work modifier appears only for deepfakes.
|
||||
- Replaced ambiguous legal checkboxes with conditional required `yes` / `no` / `unsure` assessments and a pending state that prevents unanswered questions from being interpreted as “no duty”.
|
||||
- Human review fields are hidden for components without AI involvement. Article mode now includes audio and video and starts components at no AI use instead of pre-populating AI involvement.
|
||||
- Editorial-responsibility fields are shown and required only when a public-interest-text exception is actually being assessed after substantive review.
|
||||
- Added separate author/byline metadata so authorship is not conflated with editorial legal responsibility.
|
||||
- Added a distinct complaint/feedback contact as optional best-practice metadata, explicitly not presented as a general Article 50 requirement.
|
||||
- Added dynamic plausibility warnings for formal-only review, missing editorial responsibility, provider/deployer mismatch, personal use, pre-application outputs and incomplete complaint contacts.
|
||||
- Added consequence-oriented Article 50 guidance for first-exposure disclosure and provider-side machine-readable marking.
|
||||
- Added the Article 50(4) exception for qualifying legally authorised criminal-law use as an explicit, conditionally visible assessment field.
|
||||
- Added a cautious Article 99(4) enforcement/penalty-frame notice and clarified that any actual infringement or fine is determined by the competent authority.
|
||||
- Declaration Schema 1.3 adds `author`, `complaintsContact`, and extended `legalContext` (`actorRole`, `useContext`, `outputDate`, `deepfakeAssessment`, `publicInterestAssessment`, `creativeWorkAssessment`, `lawEnforcementAuthorization`).
|
||||
|
||||
## 1.6.3 - 2026-07-21
|
||||
|
||||
- Hardened EU AI Act guidance against the Commission Article 50 guidelines published on 20 July 2026.
|
||||
|
||||
@@ -21,4 +21,4 @@ build:
|
||||
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o bin/ai-disclosure ./cmd/server
|
||||
|
||||
docker-build:
|
||||
docker build -t ai-disclosure-standard:1.6.3-local .
|
||||
docker build -t ai-disclosure-standard:1.7.0-local .
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# AI Disclosure Standard 1.6.3
|
||||
# AI Disclosure Standard 1.7.0
|
||||
|
||||
Ein zustandsloser Go-Dienst für sichtbare und maschinenlesbare Erklärungen zur KI-Nutzung in Artikeln, Webseiten und einzelnen Inhaltsbestandteilen.
|
||||
|
||||
@@ -269,8 +269,10 @@ Nicht enthalten sind:
|
||||
|
||||
Diese Funktionen werden nur in der eigenständigen Universal License Platform betrieben.
|
||||
|
||||
### Art.-50-Kontext (Schema 1.2)
|
||||
### Art.-50-Kontext (Schema 1.3)
|
||||
|
||||
Optional kann `legalContext` den rechtlich relevanten Veröffentlichungskontext als Selbsteinordnung dokumentieren: `deepfake`, `publicInterestText`, `artisticCreativeSatiricalFictional` oder `otherVoluntary`. Der Generator nutzt diese Angaben für eine vorsichtige, nicht bindende Einschätzung und für eine eindeutigere Erstkennzeichnung. Ein Deepfake ist dabei nicht jedes KI-generierte Bild. Die Einordnung muss durch den Publisher anhand des konkreten Inhalts und Veröffentlichungskontexts erfolgen.
|
||||
Der Generator trennt seit Schema 1.3 bewusst zwischen **Inhalt/KI-Nutzung**, **Art.-50-Selbsteinordnung**, **Veröffentlichungs-/Verantwortlichkeitsangaben** und **Ausgabe**. Abhängige Eingabefelder werden nur eingeblendet, wenn sie zu den zuvor angegebenen Tatsachen passen. Rolle und Nutzungskontext bilden dabei den ersten Filter; deployerspezifische Deepfake-/Public-Interest-Fragen erscheinen nicht bei rein persönlicher nicht-beruflicher Nutzung oder einer ausschließlich providerseitigen Rolle. So erscheint die Deepfake-Prüfung nur bei KI-beteiligten Bild-, Audio- oder Videoinhalten, die Public-Interest-Prüfung nur bei KI-beteiligtem Text und die redaktionelle Verantwortung nur dann, wenn sie für die mögliche Ausnahme bei Public-Interest-Texten tatsächlich relevant werden kann.
|
||||
|
||||
Bei `publicInterestText` berücksichtigt die Einschätzung, ob eine substanzielle inhaltliche Prüfung (`editorial` oder `expert`) und eine ausdrücklich benannte `editorialResponsibility` dokumentiert sind. Bei einer als wahrscheinlich kennzeichnungspflichtig eingeordneten Nutzung erzeugt der Badge eine unmittelbar verständliche Beschriftung statt nur „Artikeltransparenz“. Im `emoji`-Modus ergänzt der generierte Einbettungscode bei solchen Fällen zusätzlich sichtbaren Text.
|
||||
`legalContext` kann `categories` (`deepfake`, `publicInterestText`, `artisticCreativeSatiricalFictional`, `otherVoluntary`) sowie `actorRole`, `useContext`, `outputDate`, `deepfakeAssessment`, `publicInterestAssessment`, `creativeWorkAssessment` und `lawEnforcementAuthorization` dokumentieren. Die inhaltlichen Prüfungen verwenden bewusst `yes` / `no` / `unsure`, damit „nicht angeklickt“ nicht mit „nein“ verwechselt wird. Die Angaben sind eine vorsichtige Selbsteinordnung und keine automatische Rechtsentscheidung. Der Generator berücksichtigt insbesondere Provider-/Deployer-Rolle, rein persönliche nicht-berufliche Nutzung, den Anwendungsbeginn am 2. August 2026, die besondere Strafverfolgungs-Ausnahme in Art. 50 Abs. 4 sowie bei Public-Interest-Texten die Kombination aus substantieller menschlicher Prüfung/redaktioneller Kontrolle und ausdrücklich benannter redaktioneller Verantwortung.
|
||||
|
||||
Autor/Byline (`author`) und redaktionelle Verantwortung (`editorialResponsibility`) sind getrennte Metadaten. Eine freiwillige Beschwerde-/Rückmeldestelle (`complaintsContact`) kann als Best Practice angegeben werden, wird aber ausdrücklich nicht als allgemeine Pflicht aus Art. 50 dargestellt. Der Generator zeigt außerdem Plausibilitäts- und Rechtshinweise sowie die möglichen Folgen einer Kennzeichnungspflicht; Details stehen in [`docs/EU-AI-ACT-COMPLIANCE.md`](docs/EU-AI-ACT-COMPLIANCE.md).
|
||||
|
||||
@@ -25,7 +25,7 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: app
|
||||
image: ghcr.io/REPLACE_ME/ai-disclosure-standard:1.6.3
|
||||
image: ghcr.io/REPLACE_ME/ai-disclosure-standard:1.7.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
version: "3.9"
|
||||
services:
|
||||
app:
|
||||
image: ghcr.io/REPLACE_ME/ai-disclosure-standard:1.6.3
|
||||
image: ghcr.io/REPLACE_ME/ai-disclosure-standard:1.7.0
|
||||
environment:
|
||||
BASE_URL: https://ai.example.org
|
||||
PUBLIC_NAME: AI Usage Disclosure
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Mehrsprachige Hintergrundseite
|
||||
|
||||
Version 1.6.3 stellt unter `/background` eine eigenständige Informationsseite zur KI-Kennzeichnung und zu Artikel 50 des EU AI Act bereit.
|
||||
Version 1.7.0 stellt unter `/background` eine eigenständige Informationsseite zur KI-Kennzeichnung und zu Artikel 50 des EU AI Act bereit.
|
||||
|
||||
## Routen
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# EU AI Act: Compliance-Grenzen und Betreiber-Checkliste
|
||||
|
||||
**Rechtsstand: 21. Juli 2026**
|
||||
**Rechtsstand: 22. Juli 2026**
|
||||
|
||||
Diese Datei beschreibt, welche Teile des EU AI Act (Verordnung (EU) 2024/1689) dieses Projekt technisch unterstützen kann und welche Pflichten außerhalb seines Funktionsumfangs liegen. Sie ersetzt keine Prüfung des konkreten Einsatzes.
|
||||
|
||||
@@ -66,12 +66,47 @@ Die Kommission stellt klar:
|
||||
|
||||
Aus diesem Grund setzt der Generator seit dieser Härtung **keine menschliche oder redaktionelle Prüfung mehr automatisch voraus**. Der Nutzer muss den tatsächlich durchgeführten Prozess ausdrücklich auswählen.
|
||||
|
||||
Die Felder `editorialResponsibility.name` und `editorialResponsibility.url` dienen nur der Dokumentation. Ihre Angabe beweist nicht automatisch, dass die gesetzlichen Voraussetzungen vorliegen.
|
||||
Die Felder `editorialResponsibility.role`, `editorialResponsibility.name` und `editorialResponsibility.url` dienen nur der Dokumentation. Der Generator berücksichtigt eine mögliche Ausnahme nur, wenn mindestens Rolle und verantwortliche Person/Organisation ausdrücklich angegeben sind. Auch diese Dokumentation beweist nicht automatisch, dass die gesetzlichen Voraussetzungen tatsächlich vorliegen.
|
||||
|
||||
## 5. Artikel 50 Absatz 2: JSON-LD ist kein Ersatz für Provider-Marking
|
||||
**Autor/Byline und redaktionelle Verantwortung sind getrennt.** `author` beschreibt die Urheberschaft bzw. Veröffentlichungs-Byline. `editorialResponsibility` beschreibt dagegen diejenige natürliche oder juristische Person, die letztlich die rechtliche redaktionelle Verantwortung für die Veröffentlichung trägt. Beides kann zusammenfallen, muss es aber nicht.
|
||||
|
||||
## 5. Besondere Ausnahme für gesetzlich autorisierte Strafverfolgungsnutzung
|
||||
|
||||
Artikel 50 Absatz 4 enthält für Deepfakes und Public-Interest-Texte eine besondere Ausnahme, soweit die konkrete Nutzung **gesetzlich zur Aufdeckung, Verhütung, Ermittlung oder Verfolgung von Straftaten autorisiert** ist.
|
||||
|
||||
Schema 1.3 kann diese Selbsteinordnung in `legalContext.lawEnforcementAuthorization` (`yes`, `no`, `unsure`) dokumentieren. Das Feld erscheint im Generator nur, wenn zuvor eine Deepfake- oder Public-Interest-Konstellation ausgewählt wurde. Ein bloßer Behördenstatus genügt nicht als Grundlage für „yes“; die konkrete gesetzliche Autorisierung des konkreten Einsatzes sollte intern nachvollziehbar dokumentiert sein. Bei `unsure` warnt der Generator davor, die Ausnahme ohne weitere Prüfung zugrunde zu legen.
|
||||
|
||||
Offizielle Quelle:
|
||||
|
||||
- https://ai-act-service-desk.ec.europa.eu/en/ai-act/article-50
|
||||
|
||||
## 6. Beschwerde-/Rückmeldestelle: Best Practice, nicht Art.-50-Pflicht
|
||||
|
||||
Eine eigene interne Beschwerde- oder Rückmeldestelle für jeden gekennzeichneten Inhalt ist **keine allgemeine Pflicht aus Artikel 50**. Das Projekt bietet `complaintsContact` deshalb ausdrücklich als Best-Practice-Metadatum an und trennt es von rechtlich erforderlichen Feldern.
|
||||
|
||||
Daneben enthält der AI Act in Artikel 85 ein eigenständiges Recht, bei einer Marktüberwachungsbehörde Beschwerde über einen Verstoß gegen den AI Act einzulegen. Eine freiwillige interne Anlaufstelle des Publishers ersetzt dieses gesetzliche Beschwerderecht nicht. Zusätzlich können Medien-, Verbraucher-, Plattform-, Datenschutz- oder andere Fachregeln eigene Kontakt- oder Beschwerdepflichten begründen.
|
||||
|
||||
Offizielle Quellen:
|
||||
|
||||
- https://ai-act-service-desk.ec.europa.eu/en/ai-act/article-85
|
||||
- https://ai-act-service-desk.ec.europa.eu/en/ai-act/article-50
|
||||
|
||||
## 7. Mögliche Konsequenzen bei Verstößen gegen Artikel 50
|
||||
|
||||
Artikel 99 Absatz 4 ordnet Verstöße gegen die Transparenzpflichten des Artikels 50 grundsätzlich einem Bußgeldrahmen von **bis zu 15 Mio. EUR** oder – bei Unternehmen – **bis zu 3 % des weltweiten Jahresgesamtumsatzes des vorangegangenen Geschäftsjahres** zu. Für KMU einschließlich Start-ups enthält Artikel 99 Absatz 6 eine besondere Deckelungsregel; außerdem müssen Art, Schwere, Dauer, Verantwortungsgrad und weitere Umstände des Einzelfalls berücksichtigt werden.
|
||||
|
||||
Der Generator zeigt diesen Rahmen als Risikohinweis, aber **nicht als Prognose einer konkreten Geldbuße**. Ob ein Verstoß vorliegt, welche Maßnahmen angemessen sind und ob bzw. in welcher Höhe eine Geldbuße verhängt wird, ist Sache der zuständigen Behörden bzw. Gerichte.
|
||||
|
||||
Offizielle Quelle:
|
||||
|
||||
- https://ai-act-service-desk.ec.europa.eu/en/ai-act/article-99
|
||||
|
||||
## 8. Artikel 50 Absatz 2: JSON-LD ist kein Ersatz für Provider-Marking
|
||||
|
||||
Anbieter generativer KI-Systeme müssen in den erfassten Fällen dafür sorgen, dass generierte oder manipulierte Inhalte maschinenlesbar markiert und als künstlich generiert oder manipuliert erkennbar sind. Nach Artikel 50 Absatz 2 müssen die technischen Lösungen wirksam, interoperabel, robust und zuverlässig sein, soweit dies technisch machbar ist.
|
||||
|
||||
Artikel 50 Absatz 2 nimmt Systeme aus, die lediglich Standard-Bearbeitungsfunktionen ausführen oder die vom Betreiber bereitgestellten Eingabedaten bzw. deren Semantik nicht wesentlich verändern. Ob diese Ausnahme tatsächlich greift, hängt von der konkreten Funktion und Veränderungswirkung ab; der sichtbare Generator kann diese technische Anbieterfrage nicht automatisch entscheiden.
|
||||
|
||||
Das von diesem Projekt erzeugte **JSON-LD ist ergänzende Dokumentation**. Es ist nicht als Ersatz für die technische Markierung gedacht, die der Anbieter des generativen KI-Systems in oder an der Ausgabe implementieren muss.
|
||||
|
||||
Wer selbst Anbieter eines generativen KI-Systems im Sinne des AI Act ist, muss deshalb zusätzlich eine geeignete Provider-Marking-Lösung umsetzen und dokumentieren. Der von der Kommission positiv bewertete Code of Practice kann hierfür als freiwilliger Compliance-Rahmen genutzt werden.
|
||||
@@ -82,7 +117,7 @@ Offizielle Quellen:
|
||||
- https://digital-strategy.ec.europa.eu/en/policies/code-practice-ai-generated-content
|
||||
- https://digital-strategy.ec.europa.eu/en/library/commission-opinion-assessment-code-practice-transparency-ai-generated-content
|
||||
|
||||
## 6. Artikel 4: AI Literacy
|
||||
## 9. Artikel 4: AI Literacy
|
||||
|
||||
Artikel 4 gilt bereits seit **2. Februar 2025**. Anbieter und Betreiber von KI-Systemen müssen nach besten Kräften Maßnahmen treffen, um ein ausreichendes Maß an AI Literacy bei Mitarbeitenden und sonstigen Personen sicherzustellen, die in ihrem Auftrag mit Betrieb oder Nutzung von KI-Systemen befasst sind.
|
||||
|
||||
@@ -98,7 +133,7 @@ Offizielle Quelle:
|
||||
|
||||
- https://ai-act-service-desk.ec.europa.eu/en/ai-act/article-4
|
||||
|
||||
## 7. Risiko- und Rollenprüfung vor Produktivnutzung
|
||||
## 10. Risiko- und Rollenprüfung vor Produktivnutzung
|
||||
|
||||
Vor dem Einsatz sollte die Organisation dokumentieren:
|
||||
|
||||
@@ -117,7 +152,7 @@ Offizielle Einstiegsquellen:
|
||||
- https://ai-act-service-desk.ec.europa.eu/en/ai-act/article-6
|
||||
- https://ai-act-service-desk.ec.europa.eu/en/ai-act/annex-3
|
||||
|
||||
## 8. Zeitlicher Stand
|
||||
## 11. Zeitlicher Stand
|
||||
|
||||
Nach Artikel 113 gilt der AI Act grundsätzlich ab 2. August 2026; einzelne Teile gelten bereits früher. Insbesondere gelten Kapitel I und II, einschließlich Artikel 4 und der Verbote des Artikels 5, seit 2. Februar 2025.
|
||||
|
||||
@@ -129,7 +164,7 @@ Offizielle Quellen:
|
||||
- https://ai-act-service-desk.ec.europa.eu/en/ai-act/timeline/timeline-implementation-eu-ai-act
|
||||
- https://digital-strategy.ec.europa.eu/en/faqs/transparency-obligations-under-article-50-ai-act
|
||||
|
||||
## 9. Praktische Mindestregel für dieses Projekt
|
||||
## 12. Praktische Mindestregel für dieses Projekt
|
||||
|
||||
Wenn ein Inhalt **möglicherweise** unter Artikel 50 Absatz 4 fällt, ist die rechtlich defensivere Konfiguration:
|
||||
|
||||
@@ -143,15 +178,52 @@ Wenn ein Inhalt **möglicherweise** unter Artikel 50 Absatz 4 fällt, ist die re
|
||||
|
||||
Bei Zweifeln über die Ausnahme wegen menschlicher Prüfung ist eine **zusätzliche Offenlegung regelmäßig die risikoärmere technische Entscheidung**, solange dadurch keine anderen rechtlichen Pflichten verletzt werden.
|
||||
|
||||
## Strukturierter rechtlicher Kontext in Schema 1.2
|
||||
## 13. Strukturierter rechtlicher Kontext in Schema 1.3
|
||||
|
||||
Die Deklaration kann optional `legalContext.categories` enthalten. Unterstützt werden `deepfake`, `publicInterestText`, `artisticCreativeSatiricalFictional` und `otherVoluntary`. Diese Angaben sind ausdrücklich als Selbsteinordnung konzipiert und ersetzen keine rechtliche Subsumtion.
|
||||
Schema 1.3 erweitert die rechtliche Selbsteinordnung und trennt sie von allgemeinen Veröffentlichungsmetadaten.
|
||||
|
||||
Der Generator trennt damit zwei Ebenen:
|
||||
`legalContext` unterstützt:
|
||||
|
||||
1. **Erstkennzeichnung am Inhalt:** Bei einer als voraussichtlich offenlegungspflichtig eingeordneten Nutzung wird eine verständliche Beschriftung wie „KI-generierter / manipulierter Inhalt“ oder „KI-generierte / bearbeitete Inhalte“ erzeugt.
|
||||
2. **Zweite Ebene:** Die `/declaration`-Seite dokumentiert KI-Anteil, Tätigkeiten, menschliche Prüfung, Nachweisgrundlage, rechtlichen Kontext und gegebenenfalls redaktionelle Verantwortung.
|
||||
- `categories`: `deepfake`, `publicInterestText`, `artisticCreativeSatiricalFictional`, `otherVoluntary`;
|
||||
- `actorRole`: `deployer`, `provider`, `both`, `unsure`;
|
||||
- `useContext`: `professional`, `personalNonProfessional`, `unsure`;
|
||||
- `outputDate`: Datum des betroffenen KI-Outputs für die zeitliche Einordnung;
|
||||
- `deepfakeAssessment`: `yes`, `no`, `unsure` für die Deepfake-Einordnung, wenn KI-beteiligte Bild-/Audio-/Videoinhalte vorliegen;
|
||||
- `publicInterestAssessment`: `yes`, `no`, `unsure` für die Public-Interest-Text-Einordnung, wenn KI-beteiligter Text vorliegt;
|
||||
- `creativeWorkAssessment`: `yes`, `no`, `unsure` für die kreative/satirische/fiktionale Sonderform, wenn zuvor ein Deepfake bejaht wurde;
|
||||
- `lawEnforcementAuthorization`: `yes`, `no`, `unsure` für die besondere Ausnahme des Art. 50 Abs. 4.
|
||||
|
||||
Für `publicInterestText` wird eine mögliche Ausnahme nur als Möglichkeit angezeigt, wenn sowohl eine substanzielle Prüfung (`editorial` oder `expert`) als auch eine ausdrücklich benannte redaktionelle Verantwortung dokumentiert sind. Formale Prüfung (`basic`) reicht dafür nicht. Bei `deepfake` bleibt die Einschätzung auf Offenlegung gerichtet; die zusätzliche Kategorie `artisticCreativeSatiricalFictional` weist auf die begrenzte, angemessene Offenlegungsform für evident kreative Werke hin.
|
||||
Daneben stehen getrennt:
|
||||
|
||||
Das System formuliert bewusst „spricht vieles dafür“ beziehungsweise „kann in Betracht kommen“ und gibt keine verbindliche Rechtsentscheidung aus.
|
||||
- `author`: Autor/in bzw. Byline und optionale Profil-URL;
|
||||
- `editorialResponsibility`: Rolle, verantwortliche Person/Organisation und optionale Nachweis-/Impressums-URL;
|
||||
- `complaintsContact`: freiwillige Beschwerde-/Feedback-Anlaufstelle mit Name sowie E-Mail und/oder URL.
|
||||
|
||||
### Abhängigkeitslogik im Generator
|
||||
|
||||
Der Generator zeigt Felder nur in sachlich passenden Konstellationen:
|
||||
|
||||
1. **Keine KI-Beteiligung:** rechtlicher Art.-50-Kontext, Review- und Tätigkeitsfelder werden ausgeblendet bzw. nicht übertragen.
|
||||
2. **Rolle und Nutzungskontext zuerst:** bei KI-Beteiligung werden zunächst AI-Act-Rolle und beruflicher/persönlicher Nutzungskontext verlangt. Bei rein persönlicher nicht-beruflicher Nutzung oder ausschließlich `provider`-seitiger Rolle werden die deployerspezifischen Inhaltsfragen nicht eingeblendet.
|
||||
3. **KI-Bild/Audio/Video:** erst danach wird eine verpflichtende `yes`/`no`/`unsure`-Deepfake-Prüfung sichtbar; nach `yes`/`unsure` wird zunächst eine mögliche Strafverfolgungs-Ausnahme geprüft. Die kreative/satirische/fiktionale Sonderform erscheint bei bestätigtem Deepfake erst, wenn diese Ausnahme nicht als einschlägig angegeben wurde.
|
||||
4. **KI-Text:** erst danach wird eine verpflichtende `yes`/`no`/`unsure`-Public-Interest-Prüfung sichtbar.
|
||||
5. **Deepfake/Public-Interest = `yes` oder `unsure`:** die Frage nach gesetzlich autorisierter Strafverfolgungsnutzung wird sichtbar.
|
||||
6. **Public-Interest + substanzielle Prüfung:** Felder zur redaktionellen Verantwortung werden eingeblendet, sofern nicht bereits die Strafverfolgungs-Ausnahme angegeben wurde und die Nutzung nicht rein persönlich bzw. ausschließlich providerseitig ist.
|
||||
7. **Autor/Byline:** erscheint nur bei KI-Beteiligung in artikelförmiger bzw. textbezogener Nutzung; eine Profil-URL erst nach Eingabe eines Namens.
|
||||
8. **Beschwerde-/Rückmeldestelle:** erscheint nur in rechtlich sensiblen Deepfake-/Public-Interest-Konstellationen und ist als Best Practice gekennzeichnet.
|
||||
9. **Unvollständige Pflichtfragen:** solange eine aktuell erforderliche Selbsteinordnung leer ist, gibt die Vorschau bewusst noch kein „Pflicht / keine Pflicht“-Ergebnis aus.
|
||||
|
||||
### Plausibilitäts- und Rechtswarnungen
|
||||
|
||||
Der Generator warnt unter anderem bei:
|
||||
|
||||
- nur formaler (`basic`) Prüfung in einer Public-Interest-Konstellation;
|
||||
- substantieller Prüfung ohne vollständig benannte redaktionelle Verantwortung;
|
||||
- Provider-Rolle, weil ein Veröffentlichungs-Badge Art. 50 Abs. 2 nicht ersetzt;
|
||||
- rein persönlicher, nicht beruflicher Nutzung;
|
||||
- Output-Datum vor dem 2. August 2026;
|
||||
- unklarer Strafverfolgungs-Autorisierung;
|
||||
- widersprüchlicher Kombination von Deepfake/Public-Interest und den strukturierten Inhaltsbestandteilen;
|
||||
- unvollständiger freiwilliger Beschwerde-/Rückmeldestelle.
|
||||
|
||||
Das System formuliert bewusst vorsichtig („spricht vieles dafür“, „kann in Betracht kommen“) und gibt keine verbindliche Rechtsentscheidung aus. Bei Grenzfällen sollte die tatsächliche Rechtslage des konkreten Einsatzes individuell geprüft werden.
|
||||
|
||||
@@ -16,7 +16,7 @@ Initialization is performed in `internal/app/server.go`. The product ID and embe
|
||||
```go
|
||||
licenses := licenseclient.New(ctx, licenseclient.Config{
|
||||
Product: "ai-disclosure-standard",
|
||||
ClientVersion: "1.6.3",
|
||||
ClientVersion: "1.7.0",
|
||||
Token: cfg.LicenseToken,
|
||||
TrustStore: trustStore,
|
||||
BaseURL: cfg.BaseURL,
|
||||
|
||||
@@ -119,7 +119,7 @@ Anfrage:
|
||||
"baseUrl": "https://ai.example.org",
|
||||
"host": "ai.example.org",
|
||||
"instanceId": "production-eu-1",
|
||||
"clientVersion": "1.6.3"
|
||||
"clientVersion": "1.7.0"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
const (
|
||||
ProductID = "ai-disclosure-standard"
|
||||
ProductVersion = "1.6.3"
|
||||
ProductVersion = "1.7.0"
|
||||
FeatureCustomText = "custom_text"
|
||||
FeatureCustomBadge = "custom_badge"
|
||||
FeatureWhiteLabel = "white_label"
|
||||
|
||||
+212
-13
@@ -692,6 +692,9 @@ func declarationFacts(d declaration.Declaration, locale i18n.Locale) []fact {
|
||||
if d.Subject != "" {
|
||||
facts = append(facts, fact{Label: locale.Text["fact_subject"], Value: d.Subject, Link: d.Subject})
|
||||
}
|
||||
if d.Author != nil && d.Author.Name != "" {
|
||||
facts = append(facts, fact{Label: locale.Text["fact_author"], Value: d.Author.Name, Link: d.Author.URL})
|
||||
}
|
||||
if d.EditorialResponsibility != nil && d.EditorialResponsibility.Name != "" {
|
||||
facts = append(facts, fact{Label: locale.Text["fact_responsibility"], Value: d.EditorialResponsibility.Name, Link: d.EditorialResponsibility.URL})
|
||||
if d.EditorialResponsibility.Role != "" {
|
||||
@@ -702,15 +705,60 @@ func declarationFacts(d declaration.Declaration, locale i18n.Locale) []fact {
|
||||
facts = append(facts, fact{Label: locale.Text["fact_responsibility_type"], Value: role})
|
||||
}
|
||||
}
|
||||
if d.LegalContext != nil && len(d.LegalContext.Categories) > 0 {
|
||||
labels := make([]string, 0, len(d.LegalContext.Categories))
|
||||
for _, category := range d.LegalContext.Categories {
|
||||
if label := legalCategoryLabel(locale, category); label != "" {
|
||||
labels = append(labels, label)
|
||||
if d.ComplaintsContact != nil && d.ComplaintsContact.Name != "" {
|
||||
link := d.ComplaintsContact.URL
|
||||
if link == "" && d.ComplaintsContact.Email != "" {
|
||||
link = "mailto:" + d.ComplaintsContact.Email
|
||||
}
|
||||
facts = append(facts, fact{Label: locale.Text["fact_complaints_contact"], Value: d.ComplaintsContact.Name, Link: link})
|
||||
if d.ComplaintsContact.Email != "" {
|
||||
facts = append(facts, fact{Label: locale.Text["fact_complaints_email"], Value: d.ComplaintsContact.Email, Link: "mailto:" + d.ComplaintsContact.Email})
|
||||
}
|
||||
}
|
||||
if d.LegalContext != nil {
|
||||
if len(d.LegalContext.Categories) > 0 {
|
||||
labels := make([]string, 0, len(d.LegalContext.Categories))
|
||||
for _, category := range d.LegalContext.Categories {
|
||||
if label := legalCategoryLabel(locale, category); label != "" {
|
||||
labels = append(labels, label)
|
||||
}
|
||||
}
|
||||
if len(labels) > 0 {
|
||||
facts = append(facts, fact{Label: locale.Text["fact_legal_context"], Value: strings.Join(labels, "; ")})
|
||||
}
|
||||
}
|
||||
if len(labels) > 0 {
|
||||
facts = append(facts, fact{Label: locale.Text["fact_legal_context"], Value: strings.Join(labels, "; ")})
|
||||
if d.LegalContext.Role != "" {
|
||||
facts = append(facts, fact{Label: locale.Text["fact_legal_role"], Value: locale.Text["legal_role_"+d.LegalContext.Role]})
|
||||
}
|
||||
if d.LegalContext.UseContext != "" {
|
||||
facts = append(facts, fact{Label: locale.Text["fact_use_context"], Value: locale.Text["use_context_"+d.LegalContext.UseContext]})
|
||||
}
|
||||
if d.LegalContext.OutputDate != "" {
|
||||
facts = append(facts, fact{Label: locale.Text["fact_output_date"], Value: d.LegalContext.OutputDate})
|
||||
}
|
||||
for _, item := range []struct {
|
||||
label string
|
||||
value string
|
||||
}{
|
||||
{locale.Text["fact_deepfake_assessment"], d.LegalContext.DeepfakeAssessment},
|
||||
{locale.Text["fact_public_interest_assessment"], d.LegalContext.PublicInterestAssessment},
|
||||
{locale.Text["fact_creative_work_assessment"], d.LegalContext.CreativeWorkAssessment},
|
||||
} {
|
||||
if item.value == "" {
|
||||
continue
|
||||
}
|
||||
translated := locale.Text["assessment_"+item.value]
|
||||
if translated == "" {
|
||||
translated = item.value
|
||||
}
|
||||
facts = append(facts, fact{Label: item.label, Value: translated})
|
||||
}
|
||||
if d.LegalContext.LawEnforcementAuthorization != "" {
|
||||
value := locale.Text["law_enforcement_"+d.LegalContext.LawEnforcementAuthorization]
|
||||
if value == "" {
|
||||
value = d.LegalContext.LawEnforcementAuthorization
|
||||
}
|
||||
facts = append(facts, fact{Label: locale.Text["fact_law_enforcement_authorization"], Value: value})
|
||||
}
|
||||
}
|
||||
if d.DeclaredAt != "" {
|
||||
@@ -799,11 +847,11 @@ func declarationSummary(d declaration.Declaration, locale i18n.Locale) []string
|
||||
closing := map[string][]string{
|
||||
"de": {
|
||||
"Sämtliche Angaben werden unter einem gemeinsamen Link veröffentlicht und stehen zusätzlich als maschinenlesbares JSON-LD zur Verfügung.",
|
||||
"Eine redaktionelle Verantwortung im Sinne von Art. 50 Abs. 4 wird nur dann als angegeben behandelt, wenn sie in den Metadaten ausdrücklich benannt ist.",
|
||||
"Eine redaktionelle Verantwortung im Sinne von Art. 50 Abs. 4 wird nur dann als angegeben behandelt, wenn Rolle und verantwortliche Person oder Organisation in den Metadaten ausdrücklich benannt sind.",
|
||||
},
|
||||
"en": {
|
||||
"All information is published under a single link and is also available as machine-readable JSON-LD.",
|
||||
"Editorial responsibility for the purposes of Article 50(4) is treated as stated only where it is expressly named in the metadata.",
|
||||
"Editorial responsibility for the purposes of Article 50(4) is treated as stated only where both the role and the responsible person or organisation are expressly named in the metadata.",
|
||||
},
|
||||
"fr": {
|
||||
"Toutes les informations sont publiées sous un lien unique et sont également disponibles au format JSON-LD lisible par machine.",
|
||||
@@ -850,6 +898,15 @@ func legalCategorySet(d declaration.Declaration) map[string]bool {
|
||||
for _, category := range d.LegalContext.Categories {
|
||||
out[category] = true
|
||||
}
|
||||
if d.LegalContext.DeepfakeAssessment == "yes" {
|
||||
out["deepfake"] = true
|
||||
}
|
||||
if d.LegalContext.PublicInterestAssessment == "yes" {
|
||||
out["publicInterestText"] = true
|
||||
}
|
||||
if d.LegalContext.CreativeWorkAssessment == "yes" {
|
||||
out["artisticCreativeSatiricalFictional"] = true
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -877,17 +934,38 @@ func hasSubstantiveTextReview(d declaration.Declaration) bool {
|
||||
}
|
||||
|
||||
func hasExplicitEditorialResponsibility(d declaration.Declaration) bool {
|
||||
return d.EditorialResponsibility != nil && strings.TrimSpace(d.EditorialResponsibility.Name) != ""
|
||||
return d.EditorialResponsibility != nil && strings.TrimSpace(d.EditorialResponsibility.Name) != "" && strings.TrimSpace(d.EditorialResponsibility.Role) != ""
|
||||
}
|
||||
|
||||
func assessLegalContext(d declaration.Declaration, locale i18n.Locale) legalAssessment {
|
||||
categories := legalCategorySet(d)
|
||||
if len(categories) == 0 {
|
||||
if d.LegalContext != nil {
|
||||
if d.LegalContext.UseContext == "personalNonProfessional" {
|
||||
return legalAssessment{Text: locale.Text["legal_assessment_personal"], Level: "info"}
|
||||
}
|
||||
if d.LegalContext.OutputDate != "" {
|
||||
if outputDate, err := time.Parse("2006-01-02", d.LegalContext.OutputDate); err == nil && outputDate.Before(time.Date(2026, 8, 2, 0, 0, 0, 0, time.UTC)) {
|
||||
return legalAssessment{Text: locale.Text["legal_assessment_pre_application"], Level: "info"}
|
||||
}
|
||||
}
|
||||
if d.LegalContext.Role == "provider" {
|
||||
return legalAssessment{Text: locale.Text["legal_assessment_provider"], Level: "attention"}
|
||||
}
|
||||
}
|
||||
deepfakeUnsure := d.LegalContext != nil && d.LegalContext.DeepfakeAssessment == "unsure"
|
||||
publicInterestUnsure := d.LegalContext != nil && d.LegalContext.PublicInterestAssessment == "unsure"
|
||||
if len(categories) == 0 && !deepfakeUnsure && !publicInterestUnsure {
|
||||
return legalAssessment{Text: locale.Text["legal_assessment_none"], Level: "info"}
|
||||
}
|
||||
if categories["otherVoluntary"] {
|
||||
return legalAssessment{Text: locale.Text["legal_assessment_voluntary"], Level: "info"}
|
||||
}
|
||||
if d.LegalContext != nil && d.LegalContext.LawEnforcementAuthorization == "yes" && (categories["deepfake"] || categories["publicInterestText"] || deepfakeUnsure || publicInterestUnsure) {
|
||||
return legalAssessment{Text: locale.Text["legal_assessment_law_enforcement"], Level: "info"}
|
||||
}
|
||||
if deepfakeUnsure {
|
||||
return legalAssessment{RequiresDisclosure: true, BadgeMessage: locale.Text["badge_legal_disclosure"], Text: locale.Text["legal_assessment_deepfake_unsure"], Level: "attention"}
|
||||
}
|
||||
if categories["deepfake"] {
|
||||
text := locale.Text["legal_assessment_deepfake"]
|
||||
if categories["artisticCreativeSatiricalFictional"] {
|
||||
@@ -895,6 +973,9 @@ func assessLegalContext(d declaration.Declaration, locale i18n.Locale) legalAsse
|
||||
}
|
||||
return legalAssessment{RequiresDisclosure: true, BadgeMessage: locale.Text["badge_legal_disclosure"], Text: text, Level: "attention"}
|
||||
}
|
||||
if publicInterestUnsure {
|
||||
return legalAssessment{RequiresDisclosure: true, BadgeMessage: locale.Text["badge_public_interest"], Text: locale.Text["legal_assessment_public_unsure"], Level: "attention"}
|
||||
}
|
||||
if categories["publicInterestText"] {
|
||||
if hasSubstantiveTextReview(d) && hasExplicitEditorialResponsibility(d) {
|
||||
return legalAssessment{Text: locale.Text["legal_assessment_public_exemption"], Level: "info"}
|
||||
@@ -1254,7 +1335,7 @@ const declarationSchema = `{
|
||||
"const": "AIUsageDeclaration"
|
||||
},
|
||||
"schemaVersion": {
|
||||
"const": "1.2"
|
||||
"const": "1.3"
|
||||
},
|
||||
"subject": {
|
||||
"type": "string",
|
||||
@@ -1283,6 +1364,23 @@ const declarationSchema = `{
|
||||
"$ref": "#/$defs/component"
|
||||
}
|
||||
},
|
||||
"author": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"maxLength": 200
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"editorialResponsibility": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
@@ -1308,6 +1406,40 @@ const declarationSchema = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"complaintsContact": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"maxLength": 200
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"maxLength": 320
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
}
|
||||
},
|
||||
"anyOf": [
|
||||
{
|
||||
"required": [
|
||||
"email"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"url"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"assurance": {
|
||||
"enum": [
|
||||
"selfDeclared",
|
||||
@@ -1361,6 +1493,61 @@ const declarationSchema = `{
|
||||
"otherVoluntary"
|
||||
]
|
||||
}
|
||||
},
|
||||
"actorRole": {
|
||||
"enum": [
|
||||
"deployer",
|
||||
"provider",
|
||||
"both",
|
||||
"unsure"
|
||||
]
|
||||
},
|
||||
"useContext": {
|
||||
"enum": [
|
||||
"professional",
|
||||
"personalNonProfessional",
|
||||
"unsure"
|
||||
]
|
||||
},
|
||||
"outputDate": {
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
"lawEnforcementAuthorization": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"yes",
|
||||
"no",
|
||||
"unsure"
|
||||
],
|
||||
"description": "Self-declared assessment whether the use is authorised by law to detect, prevent, investigate or prosecute criminal offences for the Article 50(4) exception."
|
||||
},
|
||||
"deepfakeAssessment": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"yes",
|
||||
"no",
|
||||
"unsure"
|
||||
],
|
||||
"description": "Self-assessment whether the AI-involved image, audio or video qualifies as a deepfake for Article 50(4)."
|
||||
},
|
||||
"publicInterestAssessment": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"yes",
|
||||
"no",
|
||||
"unsure"
|
||||
],
|
||||
"description": "Self-assessment whether AI-involved text is published to inform the public on a matter of public interest for Article 50(4)."
|
||||
},
|
||||
"creativeWorkAssessment": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"yes",
|
||||
"no",
|
||||
"unsure"
|
||||
],
|
||||
"description": "Self-assessment whether a deepfake forms part of an evidently artistic, creative, satirical, fictional or analogous work or programme."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1427,15 +1614,27 @@ const jsonLDContext = `{
|
||||
"declaredAt": {"@id": "https://schema.org/dateCreated", "@type": "https://www.w3.org/2001/XMLSchema#dateTime"},
|
||||
"language": "https://schema.org/inLanguage",
|
||||
"components": "__BASE_URL__/vocab/components",
|
||||
"author": "https://schema.org/author",
|
||||
"name": "https://schema.org/name",
|
||||
"url": {"@id": "https://schema.org/url", "@type": "@id"},
|
||||
"email": "https://schema.org/email",
|
||||
"aiExtent": "__BASE_URL__/vocab/aiExtent",
|
||||
"activities": "__BASE_URL__/vocab/activities",
|
||||
"humanReview": "__BASE_URL__/vocab/humanReview",
|
||||
"assurance": "__BASE_URL__/vocab/assurance",
|
||||
"presentation": "__BASE_URL__/vocab/presentation",
|
||||
"editorialResponsibility": "https://schema.org/accountablePerson",
|
||||
"complaintsContact": "https://schema.org/contactPoint",
|
||||
"assumed": "__BASE_URL__/vocab/assumed",
|
||||
"role": "__BASE_URL__/vocab/responsibilityRole",
|
||||
"legalContext": "__BASE_URL__/vocab/legalContext",
|
||||
"categories": "__BASE_URL__/vocab/legalCategories"
|
||||
"categories": "__BASE_URL__/vocab/legalCategories",
|
||||
"actorRole": "__BASE_URL__/vocab/aiActActorRole",
|
||||
"useContext": "__BASE_URL__/vocab/useContext",
|
||||
"outputDate": {"@id": "__BASE_URL__/vocab/outputDate", "@type": "https://www.w3.org/2001/XMLSchema#date"},
|
||||
"deepfakeAssessment": "__BASE_URL__/vocab/deepfakeAssessment",
|
||||
"publicInterestAssessment": "__BASE_URL__/vocab/publicInterestAssessment",
|
||||
"creativeWorkAssessment": "__BASE_URL__/vocab/creativeWorkAssessment",
|
||||
"lawEnforcementAuthorization": "__BASE_URL__/vocab/lawEnforcementAuthorization"
|
||||
}
|
||||
}`
|
||||
|
||||
+112
-1
@@ -550,7 +550,7 @@ func TestPublicInterestBadgeUsesExplicitDisclosureWithoutExemption(t *testing.T)
|
||||
}
|
||||
|
||||
func TestPublicInterestBadgeCanRemainVoluntaryWithSubstantiveReviewAndResponsibility(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, "/v1/badge.svg?mode=article&textExtent=partial&textReview=expert&legalContext=publicInterestText&responsible=Beispielredaktion&lang=de", nil)
|
||||
r := httptest.NewRequest(http.MethodGet, "/v1/badge.svg?mode=article&textExtent=partial&textReview=expert&legalContext=publicInterestText&responsibleRole=publisher&responsible=Beispielredaktion&lang=de", nil)
|
||||
w := httptest.NewRecorder()
|
||||
testHandler(t).ServeHTTP(w, r)
|
||||
if w.Code != http.StatusOK {
|
||||
@@ -575,3 +575,114 @@ func TestDeepfakeDeclarationShowsCautiousAssessment(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratorExposesDependencyDrivenLegalWorkflow(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, "/?lang=de", nil)
|
||||
w := httptest.NewRecorder()
|
||||
testHandler(t).ServeHTTP(w, r)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
body := w.Body.String()
|
||||
for _, expected := range []string{
|
||||
`id="legal-role"`,
|
||||
`id="use-context"`,
|
||||
`id="output-date"`,
|
||||
`id="law-enforcement-authorization"`,
|
||||
`id="legal-deepfake"`,
|
||||
`id="legal-public-interest"`,
|
||||
`id="legal-creative"`,
|
||||
`id="author-fields"`,
|
||||
`id="complaint-fields"`,
|
||||
`id="responsibility-fields"`,
|
||||
`id="article-audio"`,
|
||||
`id="article-video"`,
|
||||
"Für diese Einordnung erforderlich",
|
||||
"Best Practice",
|
||||
"keine allgemeine Pflicht",
|
||||
"Mögliche rechtliche Konsequenzen",
|
||||
"15 Mio. EUR",
|
||||
} {
|
||||
if !strings.Contains(body, expected) {
|
||||
t.Fatalf("generator missing dependency-driven field %q", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtendedDeclarationShowsPublicationAndAccountabilityMetadata(t *testing.T) {
|
||||
path := "/declaration?mode=article&textExtent=partial&textReview=expert&legalContext=publicInterestText&legalRole=deployer&useContext=professional&outputDate=2026-08-03&responsibleRole=publisher&responsible=Beispielredaktion&author=Ada%20Beispiel&authorUrl=https%3A%2F%2Fexample.org%2Fada&complaintName=Ombudsstelle&complaintEmail=feedback%40example.org&lang=de"
|
||||
r := httptest.NewRequest(http.MethodGet, path, nil)
|
||||
w := httptest.NewRecorder()
|
||||
testHandler(t).ServeHTTP(w, r)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
body := w.Body.String()
|
||||
for _, expected := range []string{
|
||||
"Ada Beispiel",
|
||||
"Ombudsstelle",
|
||||
"feedback@example.org",
|
||||
"AI-Act-Rolle (Selbsteinordnung)",
|
||||
"Nutzungskontext (Selbsteinordnung)",
|
||||
"2026-08-03",
|
||||
"Schema 1.3",
|
||||
} {
|
||||
if !strings.Contains(body, expected) {
|
||||
t.Fatalf("declaration missing %q", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderOnlyRoleDoesNotTreatPublicationBadgeAsProviderCompliance(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, "/declaration?mode=article&imageExtent=full&imageReview=none&legalContext=deepfake&legalRole=provider&useContext=professional&lang=de", nil)
|
||||
w := httptest.NewRecorder()
|
||||
testHandler(t).ServeHTTP(w, r)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
if !strings.Contains(w.Body.String(), "gesonderte Anbieterprüfung erforderlich") {
|
||||
t.Fatalf("provider limitation missing: %s", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPreApplicationOutputUsesNonRetroactivityGuidance(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, "/declaration?mode=article&imageExtent=full&imageReview=none&legalContext=deepfake&legalRole=deployer&useContext=professional&outputDate=2026-08-01&lang=de", nil)
|
||||
w := httptest.NewRecorder()
|
||||
testHandler(t).ServeHTTP(w, r)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
if !strings.Contains(w.Body.String(), "nicht rückwirkend gekennzeichnet") {
|
||||
t.Fatalf("pre-application guidance missing: %s", w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLawEnforcementAuthorisationUsesArticle50ExceptionGuidance(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, "/declaration?mode=article&imageExtent=full&imageReview=none&legalContext=deepfake&legalRole=deployer&useContext=professional&lawEnforcementAuthorization=yes&lang=de", nil)
|
||||
w := httptest.NewRecorder()
|
||||
testHandler(t).ServeHTTP(w, r)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
body := w.Body.String()
|
||||
for _, expected := range []string{"besondere Ausnahme", "Strafverfolgungs-Autorisierung (Selbsteinordnung)", "gesetzlich autorisiert"} {
|
||||
if !strings.Contains(body, expected) {
|
||||
t.Fatalf("law-enforcement guidance missing %q: %s", expected, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnclearDeepfakeAssessmentUsesCautiousDisclosureGuidance(t *testing.T) {
|
||||
r := httptest.NewRequest(http.MethodGet, "/declaration?mode=article&imageExtent=full&imageReview=none&deepfakeAssessment=unsure&legalRole=deployer&useContext=professional&lawEnforcementAuthorization=no&lang=de", nil)
|
||||
w := httptest.NewRecorder()
|
||||
testHandler(t).ServeHTTP(w, r)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status %d: %s", w.Code, w.Body.String())
|
||||
}
|
||||
body := w.Body.String()
|
||||
for _, expected := range []string{"Deepfake-Einordnung", "Unklar / muss geprüft werden", "kann anhand der Angaben nicht ausgeschlossen werden"} {
|
||||
if !strings.Contains(body, expected) {
|
||||
t.Fatalf("unclear deepfake guidance missing %q: %s", expected, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package declaration
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sort"
|
||||
@@ -13,8 +14,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
SchemaVersion = "1.2"
|
||||
PreviousSchemaVersion = "1.1"
|
||||
SchemaVersion = "1.3"
|
||||
PreviousSchemaVersion = "1.2"
|
||||
OlderSchemaVersion = "1.1"
|
||||
LegacySchemaVersion = "1.0"
|
||||
)
|
||||
|
||||
@@ -39,7 +41,25 @@ type Responsibility struct {
|
||||
}
|
||||
|
||||
type LegalContext struct {
|
||||
Categories []string `json:"categories,omitempty"`
|
||||
Categories []string `json:"categories,omitempty"`
|
||||
Role string `json:"actorRole,omitempty"`
|
||||
UseContext string `json:"useContext,omitempty"`
|
||||
OutputDate string `json:"outputDate,omitempty"`
|
||||
DeepfakeAssessment string `json:"deepfakeAssessment,omitempty"`
|
||||
PublicInterestAssessment string `json:"publicInterestAssessment,omitempty"`
|
||||
CreativeWorkAssessment string `json:"creativeWorkAssessment,omitempty"`
|
||||
LawEnforcementAuthorization string `json:"lawEnforcementAuthorization,omitempty"`
|
||||
}
|
||||
|
||||
type Author struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
type ContactPoint struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
type Presentation struct {
|
||||
@@ -59,7 +79,9 @@ type Declaration struct {
|
||||
DeclaredAt string `json:"declaredAt,omitempty"`
|
||||
Language string `json:"language"`
|
||||
Components map[string]Component `json:"components"`
|
||||
Author *Author `json:"author,omitempty"`
|
||||
EditorialResponsibility *Responsibility `json:"editorialResponsibility,omitempty"`
|
||||
ComplaintsContact *ContactPoint `json:"complaintsContact,omitempty"`
|
||||
LegalContext *LegalContext `json:"legalContext,omitempty"`
|
||||
Assurance string `json:"assurance"`
|
||||
Presentation *Presentation `json:"presentation,omitempty"`
|
||||
@@ -99,6 +121,10 @@ var validActivities = map[string]bool{
|
||||
}
|
||||
var validComponents = map[string]bool{"text": true, "coverImage": true, "image": true, "research": true, "translation": true, "audio": true, "video": true, "code": true, "other": true}
|
||||
var validLegalCategories = map[string]bool{"deepfake": true, "publicInterestText": true, "artisticCreativeSatiricalFictional": true, "otherVoluntary": true}
|
||||
var validLegalRoles = map[string]bool{"deployer": true, "provider": true, "both": true, "unsure": true}
|
||||
var validUseContexts = map[string]bool{"professional": true, "personalNonProfessional": true, "unsure": true}
|
||||
var validLawEnforcementAuthorizations = map[string]bool{"yes": true, "no": true, "unsure": true}
|
||||
var validLegalAssessments = map[string]bool{"yes": true, "no": true, "unsure": true}
|
||||
var validResponsibilityRoles = map[string]bool{"publisher": true, "other": true}
|
||||
|
||||
func NewFromQuery(values url.Values, contextURL string) (Declaration, error) {
|
||||
@@ -201,15 +227,42 @@ func NewFromQueryWithOptions(values url.Values, contextURL string, options Parse
|
||||
Components: components,
|
||||
Assurance: assurance,
|
||||
}
|
||||
authorName := clean(values.Get("author"), 200)
|
||||
authorURL := clean(values.Get("authorUrl"), 2048)
|
||||
if authorName != "" || authorURL != "" {
|
||||
d.Author = &Author{Name: authorName, URL: authorURL}
|
||||
}
|
||||
responsibleName := clean(values.Get("responsible"), 200)
|
||||
responsibleURL := clean(values.Get("responsibleUrl"), 2048)
|
||||
responsibleRole := clean(values.Get("responsibleRole"), 24)
|
||||
if responsibleName != "" || responsibleURL != "" || responsibleRole != "" {
|
||||
d.EditorialResponsibility = &Responsibility{Assumed: responsibleName != "", Role: responsibleRole, Name: responsibleName, URL: responsibleURL}
|
||||
}
|
||||
complaintName := clean(values.Get("complaintName"), 200)
|
||||
complaintEmail := clean(values.Get("complaintEmail"), 320)
|
||||
complaintURL := clean(values.Get("complaintUrl"), 2048)
|
||||
if complaintName != "" || complaintEmail != "" || complaintURL != "" {
|
||||
d.ComplaintsContact = &ContactPoint{Name: complaintName, Email: complaintEmail, URL: complaintURL}
|
||||
}
|
||||
legalCategories := splitCSV(values.Get("legalContext"))
|
||||
if len(legalCategories) > 0 {
|
||||
d.LegalContext = &LegalContext{Categories: legalCategories}
|
||||
legalRole := clean(values.Get("legalRole"), 24)
|
||||
useContext := clean(values.Get("useContext"), 32)
|
||||
outputDate := clean(values.Get("outputDate"), 10)
|
||||
deepfakeAssessment := clean(values.Get("deepfakeAssessment"), 16)
|
||||
publicInterestAssessment := clean(values.Get("publicInterestAssessment"), 16)
|
||||
creativeWorkAssessment := clean(values.Get("creativeWorkAssessment"), 16)
|
||||
lawEnforcementAuthorization := clean(values.Get("lawEnforcementAuthorization"), 16)
|
||||
if deepfakeAssessment == "yes" {
|
||||
legalCategories = appendUniqueSorted(legalCategories, "deepfake")
|
||||
}
|
||||
if publicInterestAssessment == "yes" {
|
||||
legalCategories = appendUniqueSorted(legalCategories, "publicInterestText")
|
||||
}
|
||||
if creativeWorkAssessment == "yes" {
|
||||
legalCategories = appendUniqueSorted(legalCategories, "artisticCreativeSatiricalFictional")
|
||||
}
|
||||
if len(legalCategories) > 0 || legalRole != "" || useContext != "" || outputDate != "" || deepfakeAssessment != "" || publicInterestAssessment != "" || creativeWorkAssessment != "" || lawEnforcementAuthorization != "" {
|
||||
d.LegalContext = &LegalContext{Categories: legalCategories, Role: legalRole, UseContext: useContext, OutputDate: outputDate, DeepfakeAssessment: deepfakeAssessment, PublicInterestAssessment: publicInterestAssessment, CreativeWorkAssessment: creativeWorkAssessment, LawEnforcementAuthorization: lawEnforcementAuthorization}
|
||||
}
|
||||
|
||||
presentation := &Presentation{
|
||||
@@ -238,15 +291,25 @@ func Validate(d Declaration) error {
|
||||
switch d.SchemaVersion {
|
||||
case SchemaVersion:
|
||||
case PreviousSchemaVersion:
|
||||
if d.Author != nil || d.ComplaintsContact != nil || (d.LegalContext != nil && (d.LegalContext.Role != "" || d.LegalContext.UseContext != "" || d.LegalContext.OutputDate != "" || d.LegalContext.DeepfakeAssessment != "" || d.LegalContext.PublicInterestAssessment != "" || d.LegalContext.CreativeWorkAssessment != "" || d.LegalContext.LawEnforcementAuthorization != "")) {
|
||||
problems = append(problems, "author, complaintsContact and extended legalContext require schemaVersion 1.3")
|
||||
}
|
||||
case OlderSchemaVersion:
|
||||
if d.Author != nil || d.ComplaintsContact != nil {
|
||||
problems = append(problems, "author and complaintsContact require schemaVersion 1.3")
|
||||
}
|
||||
if d.LegalContext != nil {
|
||||
problems = append(problems, "legalContext requires schemaVersion 1.2")
|
||||
problems = append(problems, "legalContext requires schemaVersion 1.2 or later")
|
||||
}
|
||||
case LegacySchemaVersion:
|
||||
if d.Presentation != nil {
|
||||
problems = append(problems, "presentation requires schemaVersion 1.1 or later")
|
||||
}
|
||||
if d.Author != nil || d.ComplaintsContact != nil {
|
||||
problems = append(problems, "author and complaintsContact require schemaVersion 1.3")
|
||||
}
|
||||
if d.LegalContext != nil {
|
||||
problems = append(problems, "legalContext requires schemaVersion 1.2")
|
||||
problems = append(problems, "legalContext requires schemaVersion 1.2 or later")
|
||||
}
|
||||
if d.Language != "de" && d.Language != "en" {
|
||||
problems = append(problems, "schemaVersion 1.0 supports only de and en")
|
||||
@@ -276,6 +339,34 @@ func Validate(d Declaration) error {
|
||||
problems = append(problems, "declaredAt must be RFC3339")
|
||||
}
|
||||
}
|
||||
if d.Author != nil {
|
||||
if strings.TrimSpace(d.Author.Name) == "" {
|
||||
problems = append(problems, "author.name is required when author is present")
|
||||
}
|
||||
if d.Author.URL != "" {
|
||||
if u, err := url.ParseRequestURI(d.Author.URL); err != nil || u.Scheme == "" || u.Host == "" {
|
||||
problems = append(problems, "author.url must be an absolute URL")
|
||||
}
|
||||
}
|
||||
}
|
||||
if d.ComplaintsContact != nil {
|
||||
if strings.TrimSpace(d.ComplaintsContact.Name) == "" {
|
||||
problems = append(problems, "complaintsContact.name is required when complaintsContact is present")
|
||||
}
|
||||
if d.ComplaintsContact.Email == "" && d.ComplaintsContact.URL == "" {
|
||||
problems = append(problems, "complaintsContact requires email or url")
|
||||
}
|
||||
if d.ComplaintsContact.Email != "" {
|
||||
if addr, err := mail.ParseAddress(d.ComplaintsContact.Email); err != nil || addr.Address != d.ComplaintsContact.Email {
|
||||
problems = append(problems, "complaintsContact.email must be a valid email address")
|
||||
}
|
||||
}
|
||||
if d.ComplaintsContact.URL != "" {
|
||||
if u, err := url.ParseRequestURI(d.ComplaintsContact.URL); err != nil || u.Scheme == "" || u.Host == "" {
|
||||
problems = append(problems, "complaintsContact.url must be an absolute URL")
|
||||
}
|
||||
}
|
||||
}
|
||||
if d.EditorialResponsibility != nil {
|
||||
if strings.TrimSpace(d.EditorialResponsibility.Name) == "" {
|
||||
problems = append(problems, "editorialResponsibility.name is required when editorialResponsibility is present")
|
||||
@@ -290,6 +381,29 @@ func Validate(d Declaration) error {
|
||||
}
|
||||
}
|
||||
if d.LegalContext != nil {
|
||||
if d.LegalContext.Role != "" && !validLegalRoles[d.LegalContext.Role] {
|
||||
problems = append(problems, "legalContext.role must be deployer, provider, both or unsure")
|
||||
}
|
||||
if d.LegalContext.UseContext != "" && !validUseContexts[d.LegalContext.UseContext] {
|
||||
problems = append(problems, "legalContext.useContext must be professional, personalNonProfessional or unsure")
|
||||
}
|
||||
if d.LegalContext.OutputDate != "" {
|
||||
if _, err := time.Parse("2006-01-02", d.LegalContext.OutputDate); err != nil {
|
||||
problems = append(problems, "legalContext.outputDate must be YYYY-MM-DD")
|
||||
}
|
||||
}
|
||||
for field, value := range map[string]string{
|
||||
"deepfakeAssessment": d.LegalContext.DeepfakeAssessment,
|
||||
"publicInterestAssessment": d.LegalContext.PublicInterestAssessment,
|
||||
"creativeWorkAssessment": d.LegalContext.CreativeWorkAssessment,
|
||||
} {
|
||||
if value != "" && !validLegalAssessments[value] {
|
||||
problems = append(problems, "legalContext."+field+" must be yes, no or unsure")
|
||||
}
|
||||
}
|
||||
if d.LegalContext.LawEnforcementAuthorization != "" && !validLawEnforcementAuthorizations[d.LegalContext.LawEnforcementAuthorization] {
|
||||
problems = append(problems, "legalContext.lawEnforcementAuthorization must be yes, no or unsure")
|
||||
}
|
||||
seenLegal := map[string]bool{}
|
||||
for _, category := range d.LegalContext.Categories {
|
||||
if !validLegalCategories[category] {
|
||||
@@ -303,6 +417,18 @@ func Validate(d Declaration) error {
|
||||
if seenLegal["otherVoluntary"] && len(seenLegal) > 1 {
|
||||
problems = append(problems, "otherVoluntary cannot be combined with other legalContext categories")
|
||||
}
|
||||
if d.LegalContext.DeepfakeAssessment == "no" && seenLegal["deepfake"] {
|
||||
problems = append(problems, "deepfakeAssessment=no conflicts with deepfake legalContext category")
|
||||
}
|
||||
if d.LegalContext.PublicInterestAssessment == "no" && seenLegal["publicInterestText"] {
|
||||
problems = append(problems, "publicInterestAssessment=no conflicts with publicInterestText legalContext category")
|
||||
}
|
||||
if d.LegalContext.CreativeWorkAssessment == "no" && seenLegal["artisticCreativeSatiricalFictional"] {
|
||||
problems = append(problems, "creativeWorkAssessment=no conflicts with artisticCreativeSatiricalFictional legalContext category")
|
||||
}
|
||||
if d.LegalContext.CreativeWorkAssessment != "" && d.LegalContext.DeepfakeAssessment != "yes" && !seenLegal["deepfake"] {
|
||||
problems = append(problems, "creativeWorkAssessment is only applicable to a deepfake assessment")
|
||||
}
|
||||
if seenLegal["publicInterestText"] {
|
||||
text, ok := d.Components["text"]
|
||||
if !ok || text.AIExtent == "none" {
|
||||
@@ -388,6 +514,17 @@ func clean(s string, max int) string {
|
||||
|
||||
func runeLen(s string) int { return len([]rune(s)) }
|
||||
|
||||
func appendUniqueSorted(values []string, value string) []string {
|
||||
for _, existing := range values {
|
||||
if existing == value {
|
||||
return values
|
||||
}
|
||||
}
|
||||
values = append(values, value)
|
||||
sort.Strings(values)
|
||||
return values
|
||||
}
|
||||
|
||||
func splitCSV(s string) []string {
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return nil
|
||||
|
||||
@@ -155,10 +155,81 @@ func TestLegalContextAndResponsibilityAreSerialized(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d.SchemaVersion != "1.2" || d.LegalContext == nil || len(d.LegalContext.Categories) != 1 {
|
||||
if d.SchemaVersion != "1.3" || d.LegalContext == nil || len(d.LegalContext.Categories) != 1 {
|
||||
t.Fatalf("unexpected legal context: %#v", d)
|
||||
}
|
||||
if d.EditorialResponsibility == nil || !d.EditorialResponsibility.Assumed || d.EditorialResponsibility.Role != "publisher" {
|
||||
t.Fatalf("responsibility not explicitly assumed: %#v", d.EditorialResponsibility)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtendedPublicationAndLegalContextAreSerialized(t *testing.T) {
|
||||
d, err := NewFromQuery(url.Values{
|
||||
"preset": {"full"}, "lang": {"de"},
|
||||
"author": {"Ada Beispiel"}, "authorUrl": {"https://example.org/ada"},
|
||||
"complaintName": {"Ombudsstelle"}, "complaintEmail": {"feedback@example.org"},
|
||||
"legalRole": {"deployer"}, "useContext": {"professional"}, "outputDate": {"2026-08-03"},
|
||||
"publicInterestAssessment": {"yes"}, "lawEnforcementAuthorization": {"no"},
|
||||
}, "https://example.org/context/v1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d.Author == nil || d.Author.Name != "Ada Beispiel" || d.Author.URL == "" {
|
||||
t.Fatalf("author not serialized: %#v", d.Author)
|
||||
}
|
||||
if d.ComplaintsContact == nil || d.ComplaintsContact.Email != "feedback@example.org" {
|
||||
t.Fatalf("complaints contact not serialized: %#v", d.ComplaintsContact)
|
||||
}
|
||||
if d.LegalContext == nil || d.LegalContext.Role != "deployer" || d.LegalContext.UseContext != "professional" || d.LegalContext.OutputDate != "2026-08-03" || d.LegalContext.PublicInterestAssessment != "yes" || d.LegalContext.LawEnforcementAuthorization != "no" {
|
||||
t.Fatalf("extended legal context not serialized: %#v", d.LegalContext)
|
||||
}
|
||||
if len(d.LegalContext.Categories) != 1 || d.LegalContext.Categories[0] != "publicInterestText" {
|
||||
t.Fatalf("tri-state yes should derive legal category: %#v", d.LegalContext.Categories)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComplaintsContactRequiresContactMethod(t *testing.T) {
|
||||
_, err := NewFromQuery(url.Values{
|
||||
"preset": {"research"}, "complaintName": {"Ombudsstelle"},
|
||||
}, "https://example.org/context/v1")
|
||||
if err == nil || !strings.Contains(err.Error(), "complaintsContact requires email or url") {
|
||||
t.Fatalf("expected complaints contact validation error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLegalContextValidatesRoleUseAndDate(t *testing.T) {
|
||||
_, err := NewFromQuery(url.Values{
|
||||
"preset": {"research"}, "legalRole": {"invalid"}, "useContext": {"professional"}, "outputDate": {"2026-08-03"},
|
||||
}, "https://example.org/context/v1")
|
||||
if err == nil || !strings.Contains(err.Error(), "legalContext.role") {
|
||||
t.Fatalf("expected legal role validation error, got %v", err)
|
||||
}
|
||||
_, err = NewFromQuery(url.Values{
|
||||
"preset": {"research"}, "legalRole": {"deployer"}, "useContext": {"professional"}, "outputDate": {"03.08.2026"},
|
||||
}, "https://example.org/context/v1")
|
||||
if err == nil || !strings.Contains(err.Error(), "legalContext.outputDate") {
|
||||
t.Fatalf("expected legal output date validation error, got %v", err)
|
||||
}
|
||||
_, err = NewFromQuery(url.Values{
|
||||
"preset": {"research"}, "legalRole": {"deployer"}, "useContext": {"professional"}, "lawEnforcementAuthorization": {"maybe"},
|
||||
}, "https://example.org/context/v1")
|
||||
if err == nil || !strings.Contains(err.Error(), "legalContext.lawEnforcementAuthorization") {
|
||||
t.Fatalf("expected law-enforcement authorisation validation error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLegalAssessmentTriStateValidation(t *testing.T) {
|
||||
_, err := NewFromQuery(url.Values{
|
||||
"mode": {"article"}, "imageExtent": {"full"}, "deepfakeAssessment": {"maybe"},
|
||||
}, "https://example.org/context/v1")
|
||||
if err == nil || !strings.Contains(err.Error(), "deepfakeAssessment must be yes, no or unsure") {
|
||||
t.Fatalf("expected tri-state validation error, got %v", err)
|
||||
}
|
||||
|
||||
_, err = NewFromQuery(url.Values{
|
||||
"mode": {"article"}, "imageExtent": {"full"}, "legalContext": {"deepfake"}, "deepfakeAssessment": {"no"},
|
||||
}, "https://example.org/context/v1")
|
||||
if err == nil || !strings.Contains(err.Error(), "conflicts with deepfake legalContext category") {
|
||||
t.Fatalf("expected category/assessment conflict, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
+216
-1
@@ -37,7 +37,7 @@ var catalogs = map[string]Locale{
|
||||
"field_preset": "Preset", "option_custom": "Benutzerdefinierte Struktur", "field_component": "Komponente", "field_extent": "KI-Anteil", "field_review": "Menschliche Prüfung",
|
||||
"field_activities": "Tätigkeiten", "activities_help": "Kommagetrennte Standardwerte, zum Beispiel research, summarisation oder translation.",
|
||||
"review_help": "Nur den tatsächlich durchgeführten Prüfprozess angeben. Eine formale Prüfung (z. B. Rechtschreibung) ist keine substanzielle menschliche Prüfung oder redaktionelle Kontrolle im Sinne von Art. 50 Abs. 4 AI Act.",
|
||||
"field_subject": "URL des gekennzeichneten Inhalts", "legal_context_legend": "Rechtlicher Kontext (Selbsteinordnung)", "legal_context_help": "Diese Angaben helfen bei der Art.-50-Einordnung. Sie sind keine automatische Rechtsentscheidung. Mehrere Kategorien können zutreffen.", "legal_deepfake": "Deepfake / realitätsähnliche KI-Manipulation", "legal_deepfake_help": "Nur auswählen, wenn Bild, Audio oder Video bestehenden oder plausibel existierenden Personen, Objekten, Orten, Entitäten oder Ereignissen ähnelt und fälschlich authentisch oder wahr erscheinen kann.", "legal_public_interest": "KI-generierter oder manipulierter Text zu einer Angelegenheit von öffentlichem Interesse", "legal_public_interest_help": "Zum Beispiel Politik, öffentliche Verwaltung, Grundrechte, Sicherheit, Gesundheit, Umwelt, Verbrauchersicherheit oder relevante wirtschaftliche, wissenschaftliche oder kulturelle Entwicklungen.", "legal_creative": "Evident künstlerisch / kreativ / satirisch / fiktional", "legal_creative_help": "Bei Deepfakes in solchen Werken kann die Offenlegung in einer angemessenen Form erfolgen, die Darstellung oder Genuss des Werks nicht beeinträchtigt.", "legal_other_voluntary": "Andere / freiwillige Transparenz", "legal_assessment_heading": "Vorsichtige Art.-50-Einschätzung", "legal_assessment_none": "Keine besondere Art.-50-Kategorie wurde angegeben. Die Kennzeichnung wird als freiwillige Transparenz behandelt.", "legal_assessment_voluntary": "Die Angaben sprechen derzeit für eine freiwillige Transparenzkennzeichnung; daraus folgt keine Aussage, dass sonstige AI-Act-Pflichten nicht gelten.", "legal_assessment_deepfake": "Auf Grundlage der Selbsteinordnung als Deepfake spricht vieles dafür, dass eine klare und unterscheidbare Offenlegung spätestens bei der ersten Exposition erforderlich ist.", "legal_assessment_deepfake_creative": "Der Inhalt ist als Deepfake und zugleich als evident künstlerisch, kreativ, satirisch oder fiktional eingeordnet. Eine Offenlegung bleibt grundsätzlich relevant, kann aber in angemessener Weise erfolgen, die Darstellung oder Genuss des Werks nicht beeinträchtigt.", "legal_assessment_public_required": "Auf Grundlage der Angaben spricht vieles dafür, dass der KI-generierte oder manipulierte Text zu einer Angelegenheit von öffentlichem Interesse klar offengelegt werden sollte. Eine ausreichende substanzielle menschliche Prüfung/redaktionelle Kontrolle zusammen mit ausdrücklich übernommener redaktioneller Verantwortung ist nicht vollständig dokumentiert.", "legal_assessment_public_exemption": "Die Angaben dokumentieren eine substanzielle menschliche Prüfung oder redaktionelle Kontrolle sowie eine ausdrücklich benannte redaktionelle Verantwortung. Daher kann die Ausnahme für bestimmte Texte von öffentlichem Interesse nach Art. 50 Abs. 4 in Betracht kommen. Eine freiwillige Transparenzkennzeichnung bleibt möglich.", "legal_assessment_inconsistent": "Die gewählte rechtliche Kategorie passt nicht vollständig zu den strukturierten Inhaltsangaben. Bitte prüfe KI-Anteil und Inhaltsbestandteile.", "badge_legal_disclosure": "KI-generierter / manipulierter Inhalt", "badge_public_interest": "KI-generierte / bearbeitete Inhalte", "fact_legal_context": "Rechtlicher Kontext (Selbsteinordnung)", "field_responsibility_role": "Art der redaktionellen Verantwortung", "responsibility_none": "Nicht angegeben", "responsibility_publisher": "Veröffentlichende Person/Organisation übernimmt die redaktionelle Verantwortung", "responsibility_other": "Andere verantwortliche Stelle", "field_responsible": "Redaktionell verantwortliche Person/Organisation (optional)", "responsible_placeholder": "Name oder Organisation", "field_responsible_url": "Nachweis/Impressum zur redaktionellen Verantwortung (optional)", "field_language": "Ausgabesprache", "field_theme": "Darstellung", "theme_mono": "Monochrom", "theme_color": "Farbig",
|
||||
"field_subject": "URL des gekennzeichneten Inhalts", "legal_context_legend": "Rechtlicher Kontext (Selbsteinordnung)", "legal_context_help": "Diese Angaben helfen bei der Art.-50-Einordnung. Sie sind keine automatische Rechtsentscheidung. Mehrere Kategorien können zutreffen.", "legal_deepfake": "Deepfake / realitätsähnliche KI-Manipulation", "legal_deepfake_help": "Nur auswählen, wenn Bild, Audio oder Video bestehenden oder plausibel existierenden Personen, Objekten, Orten, Entitäten oder Ereignissen ähnelt und fälschlich authentisch oder wahr erscheinen kann.", "legal_public_interest": "KI-generierter oder manipulierter Text zu einer Angelegenheit von öffentlichem Interesse", "legal_public_interest_help": "Zum Beispiel Politik, öffentliche Verwaltung, Grundrechte, Sicherheit, Gesundheit, Umwelt, Verbrauchersicherheit oder relevante wirtschaftliche, wissenschaftliche oder kulturelle Entwicklungen.", "legal_creative": "Evident künstlerisch / kreativ / satirisch / fiktional", "legal_creative_help": "Bei Deepfakes in solchen Werken kann die Offenlegung in einer angemessenen Form erfolgen, die Darstellung oder Genuss des Werks nicht beeinträchtigt.", "legal_other_voluntary": "Andere / freiwillige Transparenz", "legal_assessment_heading": "Vorsichtige Art.-50-Einschätzung", "legal_assessment_none": "Für die angegebenen Inhalte wurde keine inhaltsspezifische Offenlegungskategorie aus Art. 50 Abs. 4 erkannt. Das ist keine Aussage dazu, ob andere Pflichten aus Art. 50 oder sonstigem Recht greifen; die Kennzeichnung wird hier als freiwillige Transparenz behandelt.", "legal_assessment_voluntary": "Die Angaben sprechen derzeit für eine freiwillige Transparenzkennzeichnung; daraus folgt keine Aussage, dass sonstige AI-Act-Pflichten nicht gelten.", "legal_assessment_deepfake": "Auf Grundlage der Selbsteinordnung als Deepfake spricht vieles dafür, dass eine klare und unterscheidbare Offenlegung spätestens bei der ersten Exposition erforderlich ist.", "legal_assessment_deepfake_creative": "Der Inhalt ist als Deepfake und zugleich als evident künstlerisch, kreativ, satirisch oder fiktional eingeordnet. Eine Offenlegung bleibt grundsätzlich relevant, kann aber in angemessener Weise erfolgen, die Darstellung oder Genuss des Werks nicht beeinträchtigt.", "legal_assessment_public_required": "Auf Grundlage der Angaben spricht vieles dafür, dass der KI-generierte oder manipulierte Text zu einer Angelegenheit von öffentlichem Interesse klar offengelegt werden sollte. Eine ausreichende substanzielle menschliche Prüfung/redaktionelle Kontrolle zusammen mit ausdrücklich übernommener redaktioneller Verantwortung ist nicht vollständig dokumentiert.", "legal_assessment_public_exemption": "Die Angaben dokumentieren eine substanzielle menschliche Prüfung oder redaktionelle Kontrolle sowie eine ausdrücklich benannte redaktionelle Verantwortung. Daher kann die Ausnahme für bestimmte Texte von öffentlichem Interesse nach Art. 50 Abs. 4 in Betracht kommen. Eine freiwillige Transparenzkennzeichnung bleibt möglich.", "legal_assessment_inconsistent": "Die gewählte rechtliche Kategorie passt nicht vollständig zu den strukturierten Inhaltsangaben. Bitte prüfe KI-Anteil und Inhaltsbestandteile.", "badge_legal_disclosure": "KI-generierter / manipulierter Inhalt", "badge_public_interest": "KI-generierte / bearbeitete Inhalte", "fact_legal_context": "Rechtlicher Kontext (Selbsteinordnung)", "field_responsibility_role": "Art der redaktionellen Verantwortung", "responsibility_none": "Nicht angegeben", "responsibility_publisher": "Veröffentlichende Person/Organisation übernimmt die redaktionelle Verantwortung", "responsibility_other": "Andere verantwortliche Stelle", "field_responsible": "Redaktionell verantwortliche Person/Organisation", "responsible_placeholder": "Name oder Organisation", "field_responsible_url": "Nachweis/Impressum zur redaktionellen Verantwortung", "field_language": "Ausgabesprache", "field_theme": "Darstellung", "theme_mono": "Monochrom", "theme_color": "Farbig",
|
||||
"preview": "Vorschau", "copy_html": "HTML kopieren", "copied": "Kopiert",
|
||||
"pro_eyebrow": "Pro-Anpassung", "pro_title": "Eigene Texte und Badge-Designs", "pro_enabled": "Diese Instanz besitzt eine gültige Pro-Lizenz. Individuelle Texte und Farben können verwendet werden.",
|
||||
"pro_locked": "Individuelle Titel, Beschreibungstexte, Badge-Beschriftungen und Farben sind in der Pro-Ausgabe verfügbar.",
|
||||
@@ -143,6 +143,221 @@ func init() {
|
||||
applyArticleTranslations()
|
||||
applyAssuranceTranslations()
|
||||
applyThemeTranslations()
|
||||
applyLegalGeneratorTranslations()
|
||||
}
|
||||
|
||||
func applyLegalGeneratorTranslations() {
|
||||
en := map[string]string{
|
||||
"generator_step_content": "1. Content and AI use",
|
||||
"generator_step_legal": "2. Article 50 scope check",
|
||||
"generator_step_accountability": "3. Publication and accountability",
|
||||
"generator_step_output": "4. Output",
|
||||
"field_required": "Required for this assessment",
|
||||
"field_optional": "Optional",
|
||||
"field_best_practice": "Best practice",
|
||||
"assessment_yes": "Yes",
|
||||
"assessment_no": "No",
|
||||
"assessment_unsure": "Unclear / needs assessment",
|
||||
"field_legal_role": "Your role under the AI Act",
|
||||
"legal_role_deployer": "Deployer – I use an AI system under my authority",
|
||||
"legal_role_provider": "Provider – I provide / place the AI system on the market",
|
||||
"legal_role_both": "Both provider and deployer",
|
||||
"legal_role_unsure": "Unclear / needs assessment",
|
||||
"legal_role_help": "Article 50 assigns different duties to providers and deployers. This generator mainly documents publication-side disclosures; provider-side machine marking is separate. Article 50(2) also contains a limited exception for standard editing assistance or outputs that do not substantially alter the deployer's input data or semantics.",
|
||||
"field_use_context": "Use context",
|
||||
"use_context_professional": "Professional / organisational use",
|
||||
"use_context_personalNonProfessional": "Purely personal, non-professional use",
|
||||
"use_context_unsure": "Unclear / mixed use",
|
||||
"use_context_help": "A person using AI only for a personal, non-professional activity is generally not a deployer under the AI Act. Economic, business, occupational or freelance use can be professional.",
|
||||
"field_output_date": "Date the affected AI output was generated or manipulated",
|
||||
"output_date_help": "Article 50 applies from 2 August 2026. The Commission FAQ states that content generated before that date does not have to be labelled retroactively under Article 50.",
|
||||
"legal_scope_intro": "The following questions are shown only where they fit the selected content. They support a cautious self-assessment and do not constitute legal advice.",
|
||||
"legal_context_legend": "Legal context (self-assessment)",
|
||||
"legal_context_help": "These details support a cautious Article 50 assessment and are not an automated legal decision.",
|
||||
"legal_deepfake": "Deepfake / realistic AI manipulation",
|
||||
"legal_deepfake_help": "Select only where image, audio or video resembles existing or plausible persons, objects, places, entities or events and could falsely appear authentic or truthful.",
|
||||
"legal_public_interest": "AI-generated or manipulated text informing the public on a matter of public interest",
|
||||
"legal_public_interest_help": "Examples can include politics, public administration, fundamental rights, safety, health, the environment, consumer safety and significant economic, scientific or cultural developments.",
|
||||
"legal_creative": "Evidently artistic / creative / satirical / fictional",
|
||||
"legal_creative_help": "For deepfakes in such works, disclosure may be made appropriately without hampering display or enjoyment of the work.",
|
||||
"law_enforcement_title": "Legally authorised criminal-law use",
|
||||
"field_law_enforcement_authorization": "Is this use authorised by law to detect, prevent, investigate or prosecute criminal offences?",
|
||||
"law_enforcement_no": "No",
|
||||
"law_enforcement_yes": "Yes – legally authorised",
|
||||
"law_enforcement_unsure": "Unclear / needs legal review",
|
||||
"law_enforcement_help": "Article 50(4) contains a specific exception for qualifying uses authorised by law for criminal-law purposes. Do not select ‘yes’ merely because the publisher is a public authority; an actual legal authorisation for the specific use is required.",
|
||||
"legal_other_voluntary": "Other / voluntary transparency",
|
||||
"legal_assessment_heading": "Cautious Article 50 assessment",
|
||||
"legal_assessment_pending": "The Article 50 assessment is not complete yet. Answer the currently required scope questions; until then, the generator does not state that a disclosure duty exists or does not exist.",
|
||||
"legal_assessment_none": "No content-specific disclosure category under Article 50(4) was identified from the supplied information. This does not determine whether other Article 50 or other legal duties apply; the label is treated here as voluntary transparency.",
|
||||
"legal_assessment_voluntary": "The supplied information currently points to voluntary transparency; this does not mean that other AI Act duties are inapplicable.",
|
||||
"legal_assessment_deepfake": "Based on the self-classification as a deepfake, there are strong indications that a clear and distinguishable disclosure is required no later than first exposure.",
|
||||
"legal_assessment_deepfake_creative": "The content is classified as both a deepfake and evidently artistic, creative, satirical or fictional. Disclosure remains relevant but may be made appropriately without hampering display or enjoyment of the work.",
|
||||
"legal_assessment_public_required": "Based on the supplied information, there are strong indications that the AI-generated or manipulated public-interest text should be clearly disclosed. Substantive human review/editorial control together with expressly assumed editorial responsibility is not fully documented.",
|
||||
"legal_assessment_public_exemption": "The supplied information documents substantive human review/editorial control and expressly named editorial responsibility. The Article 50(4) exception for certain public-interest text may therefore be relevant. Voluntary transparency remains possible.",
|
||||
"legal_assessment_inconsistent": "The selected legal category does not fully match the structured content data. Check the AI contribution and content components.",
|
||||
"legal_assessment_deepfake_unsure": "It is unclear whether the AI-involved media qualifies as a deepfake. A content-specific Article 50(4) duty cannot be ruled out from the supplied facts. Until the classification is resolved, a clear disclosure is the more cautious publication setting.",
|
||||
"legal_assessment_public_unsure": "It is unclear whether the AI-involved text is published to inform the public on a matter of public interest. A content-specific Article 50(4) duty cannot be ruled out from the supplied facts. Until the classification is resolved, a clear disclosure is the more cautious publication setting.",
|
||||
"badge_legal_disclosure": "AI-generated / manipulated content",
|
||||
"badge_public_interest": "AI-generated / edited content",
|
||||
"fact_legal_context": "Legal context (self-assessment)",
|
||||
"field_responsibility_role": "Type of editorial responsibility",
|
||||
"responsibility_none": "Not specified",
|
||||
"responsibility_publisher": "Publishing person/organisation assumes editorial responsibility",
|
||||
"responsibility_other": "Another responsible party",
|
||||
"field_responsible": "Editorially responsible person/organisation",
|
||||
"responsible_placeholder": "Name or organisation",
|
||||
"field_responsible_url": "Evidence / imprint for editorial responsibility",
|
||||
"legal_deepfake_not_relevant": "Deepfake assessment is shown only for AI-involved image, audio or video content.",
|
||||
"legal_public_not_relevant": "Public-interest text assessment is shown only where the text component contains AI-generated or manipulated content.",
|
||||
"editorial_exception_title": "Possible public-interest text exception",
|
||||
"editorial_exception_help": "For the Article 50(4) exception, both human review/editorial control and a natural or legal person holding editorial responsibility for publication must be present. Superficial or purely formal checks are not enough.",
|
||||
"responsibility_required_help": "Shown because you selected public-interest text and documented substantive review. Name the person or legal entity that ultimately holds editorial legal responsibility for publication if you want the exception to be considered.",
|
||||
"field_author": "Author / byline",
|
||||
"field_author_url": "Author profile / reference URL",
|
||||
"author_help": "The author is not automatically the same as the person or organisation holding editorial legal responsibility. This field is publication metadata, not a general Article 50 requirement.",
|
||||
"accountability_intro": "These fields are separated from Article 50 requirements. They improve traceability without implying that every publication is legally required to provide them.",
|
||||
"complaints_title": "Complaint / feedback contact",
|
||||
"complaints_help": "Best practice for transparent publications. Article 50 does not create a general duty to name an internal complaint office for each labelled item. This voluntary contact also does not replace the separate right under Article 85 AI Act to lodge a complaint with a market surveillance authority; sectoral, media, consumer or platform rules may add further obligations.",
|
||||
"field_complaint_name": "Contact point / organisation",
|
||||
"field_complaint_email": "Complaint / feedback email",
|
||||
"field_complaint_url": "Complaint / feedback URL",
|
||||
"validation_heading": "Plausibility and legal-context notes",
|
||||
"validation_formal_review": "Only a basic/formal review is documented. According to the Commission guidance, spelling, grammar or other superficial checks are not sufficient human review/editorial control for the public-interest text exception.",
|
||||
"validation_missing_responsibility": "Substantive review is selected, but editorial responsibility is not fully named. The public-interest text exception should not be assumed on these facts.",
|
||||
"validation_provider": "Provider role selected: a visible badge or JSON-LD declaration does not replace the provider-side machine-readable marking duty for in-scope generative AI outputs.",
|
||||
"validation_role_unsure": "The AI Act actor role is unresolved. Deployer-side disclosure duties cannot safely be ruled out until it is clear whether the system is used under your authority in a professional/organisational context.",
|
||||
"validation_use_context_unsure": "The use context is unresolved or mixed. Treat professional, business, occupational or freelance use separately from purely personal non-professional use before relying on the personal-use exclusion.",
|
||||
"validation_personal": "Purely personal, non-professional use is generally outside the AI Act deployer definition. A voluntary transparency label may still be useful and other law can still apply.",
|
||||
"validation_pre_application": "The entered output date is before 2 August 2026. The Commission FAQ says such content does not need to be labelled retroactively under Article 50; other legal duties remain unaffected.",
|
||||
"validation_public_no_ai": "Public-interest text is selected, but no AI involvement in text is documented. Check the content data or remove this legal category.",
|
||||
"validation_deepfake_no_media": "Deepfake is selected, but no AI-involved image, audio or video component is documented. Check the content data or remove this legal category.",
|
||||
"validation_deepfake_unsure": "Deepfake classification is unresolved. Consider resemblance, whether the simulated subject exists or plausibly existed, false appearance of authenticity/truthfulness, deployment context and audience expectations before relying on a 'no' conclusion.",
|
||||
"validation_public_unsure": "The public-interest classification is unresolved. Check whether the text is published, intended to inform the public, and concerns a matter of public interest before relying on a 'no' conclusion.",
|
||||
"validation_creative_unsure": "The creative/satirical/fictional classification is unresolved. This affects the manner of deepfake disclosure, not whether the deepfake transparency obligation exists in principle.",
|
||||
"validation_complaint_incomplete": "Complete the complaint / feedback contact with a name and at least one valid email address or URL.",
|
||||
"consequence_disclosure": "Likely consequence: use a clear, distinguishable disclosure no later than first exposure to the affected content; accessibility requirements also apply.",
|
||||
"consequence_provider": "Provider-side consequence: separately assess Article 50(2) machine-readable marking, including whether the limited standard-editing / non-substantial-alteration exception actually applies. This generator is supplementary documentation only.",
|
||||
"consequence_voluntary": "No content-specific Article 50(4) duty is established by the supplied answers. Voluntary transparency remains possible and other AI Act or sector-specific duties may still apply.",
|
||||
"legal_assessment_personal": "The supplied use context is purely personal and non-professional. Such use is generally outside the AI Act deployer definition, so this generator treats the label as voluntary for Article 50 purposes. Other law may still apply.",
|
||||
"legal_assessment_pre_application": "The supplied output date is before 2 August 2026. The Commission FAQ states that content generated before that date does not need to be labelled retroactively under Article 50. Voluntary transparency and other legal duties remain possible.",
|
||||
"legal_assessment_provider": "The supplied role is provider-only. This publication badge cannot establish compliance with provider-side Article 50 duties, especially machine-readable marking of in-scope generated or manipulated outputs. A separate provider assessment is required, including the limited Article 50(2) exception for standard editing assistance or non-substantial alteration of the deployer's input data or semantics.",
|
||||
"legal_assessment_law_enforcement": "A qualifying legal authorisation for criminal-law purposes is stated. Article 50(4) provides a specific exception for that use, so this generator does not treat the content-specific deployer disclosure as required on that basis. The authorisation should be documented and other duties remain unaffected.",
|
||||
"validation_law_enforcement_unsure": "The criminal-law authorisation is unclear. Do not rely on the Article 50(4) exception until the specific legal basis and scope have been verified.",
|
||||
"legal_enforcement_title": "Possible legal consequences",
|
||||
"legal_enforcement_help": "From 2 August 2026, infringements of Article 50 are within the Article 99(4) penalty framework: administrative fines may reach EUR 15 million or, for an undertaking, 3% of total worldwide annual turnover for the preceding financial year (subject to the AI Act’s SME rule and the circumstances of the individual case). The competent authority determines whether an infringement exists and any measure or fine.",
|
||||
"legal_disclaimer": "Decision aid, not legal advice: the result is based only on the supplied facts and the current Article 50 text / Commission guidance. Borderline cases require an individual legal assessment.",
|
||||
"fact_author": "Author",
|
||||
"fact_complaints_contact": "Complaint / feedback contact",
|
||||
"fact_complaints_email": "Complaint / feedback email",
|
||||
"fact_legal_role": "AI Act role (self-assessment)",
|
||||
"fact_use_context": "Use context (self-assessment)",
|
||||
"fact_output_date": "AI output date",
|
||||
"fact_deepfake_assessment": "Deepfake assessment",
|
||||
"fact_public_interest_assessment": "Public-interest text assessment",
|
||||
"fact_creative_work_assessment": "Creative/satirical/fictional work assessment",
|
||||
"fact_law_enforcement_authorization": "Criminal-law authorisation (self-assessment)",
|
||||
}
|
||||
de := map[string]string{
|
||||
"generator_step_content": "1. Inhalt und KI-Nutzung",
|
||||
"generator_step_legal": "2. Anwendungsbereich von Art. 50 prüfen",
|
||||
"generator_step_accountability": "3. Veröffentlichung und Verantwortlichkeit",
|
||||
"generator_step_output": "4. Ausgabe",
|
||||
"field_required": "Für diese Einordnung erforderlich",
|
||||
"select_please_choose": "Bitte auswählen",
|
||||
"field_optional": "Optional",
|
||||
"field_best_practice": "Best Practice",
|
||||
"assessment_yes": "Ja",
|
||||
"assessment_no": "Nein",
|
||||
"assessment_unsure": "Unklar / muss geprüft werden",
|
||||
"field_legal_role": "Deine Rolle nach dem AI Act",
|
||||
"legal_role_deployer": "Deployer / Betreiber – ich setze ein KI-System unter eigener Verantwortung ein",
|
||||
"legal_role_provider": "Provider / Anbieter – ich biete das KI-System an oder bringe es in Verkehr",
|
||||
"legal_role_both": "Sowohl Provider als auch Deployer",
|
||||
"legal_role_unsure": "Unklar / muss geprüft werden",
|
||||
"legal_role_help": "Art. 50 weist Providern und Deployern unterschiedliche Pflichten zu. Dieser Generator dokumentiert vor allem veröffentlichungsbezogene Offenlegungen; die maschinenlesbare Anbieterkennzeichnung ist eigenständig. Art. 50 Abs. 2 enthält außerdem eine begrenzte Ausnahme für Standard-Bearbeitungsfunktionen bzw. Ausgaben, die Eingabedaten oder deren Semantik nicht wesentlich verändern.",
|
||||
"field_use_context": "Nutzungskontext",
|
||||
"use_context_professional": "Berufliche / organisatorische Nutzung",
|
||||
"use_context_personalNonProfessional": "Rein persönliche, nicht berufliche Nutzung",
|
||||
"use_context_unsure": "Unklar / gemischte Nutzung",
|
||||
"use_context_help": "Wer KI ausschließlich persönlich und nicht beruflich nutzt, ist grundsätzlich kein Deployer im Sinne des AI Act. Regelmäßiger wirtschaftlicher, geschäftlicher, beruflicher oder freiberuflicher Einsatz kann dagegen beruflich sein.",
|
||||
"field_output_date": "Datum der Erzeugung oder Manipulation des betroffenen KI-Outputs",
|
||||
"output_date_help": "Art. 50 gilt ab 2. August 2026. Nach der FAQ der Kommission müssen vor diesem Datum erzeugte Inhalte nach Art. 50 nicht rückwirkend gekennzeichnet werden.",
|
||||
"legal_scope_intro": "Die folgenden Fragen erscheinen nur, wenn sie zu den gewählten Inhaltsbestandteilen passen. Sie unterstützen eine vorsichtige Selbsteinordnung und sind keine Rechtsberatung.",
|
||||
"legal_deepfake_not_relevant": "Die Deepfake-Prüfung wird nur bei KI-beteiligten Bild-, Audio- oder Videoinhalten eingeblendet.",
|
||||
"legal_public_not_relevant": "Die Prüfung für Texte von öffentlichem Interesse wird nur eingeblendet, wenn beim Text eine KI-Beteiligung angegeben ist.",
|
||||
"law_enforcement_title": "Gesetzlich autorisierte Strafverfolgungsnutzung",
|
||||
"field_law_enforcement_authorization": "Ist diese Nutzung gesetzlich zur Aufdeckung, Verhütung, Ermittlung oder Verfolgung von Straftaten autorisiert?",
|
||||
"law_enforcement_no": "Nein",
|
||||
"law_enforcement_yes": "Ja – gesetzlich autorisiert",
|
||||
"law_enforcement_unsure": "Unklar / rechtlich zu prüfen",
|
||||
"law_enforcement_help": "Art. 50 Abs. 4 enthält hierfür eine besondere Ausnahme. „Ja“ sollte nicht allein deshalb gewählt werden, weil eine Behörde veröffentlicht; erforderlich ist eine konkrete gesetzliche Autorisierung der betreffenden Nutzung.",
|
||||
"editorial_exception_title": "Mögliche Ausnahme bei Texten von öffentlichem Interesse",
|
||||
"editorial_exception_help": "Für die Ausnahme nach Art. 50 Abs. 4 müssen sowohl menschliche Prüfung/redaktionelle Kontrolle als auch die rechtliche redaktionelle Verantwortung einer natürlichen oder juristischen Person für die Veröffentlichung vorliegen. Oberflächliche oder rein formale Prüfungen genügen nicht.",
|
||||
"responsibility_required_help": "Wird eingeblendet, weil ein Public-Interest-Text und eine substanzielle Prüfung angegeben wurden. Benenne die natürliche oder juristische Person, die letztlich die rechtliche redaktionelle Verantwortung für die Veröffentlichung trägt, wenn die Ausnahme berücksichtigt werden soll.",
|
||||
"field_author": "Autor/in / Byline",
|
||||
"field_author_url": "Autorprofil / Referenz-URL",
|
||||
"author_help": "Autor/in und rechtlich redaktionell verantwortliche Person/Organisation sind nicht automatisch identisch. Das ist eine Veröffentlichungsangabe und keine allgemeine Pflicht aus Art. 50.",
|
||||
"accountability_intro": "Diese Angaben sind bewusst von Art.-50-Pflichten getrennt. Sie verbessern die Nachvollziehbarkeit, ohne zu behaupten, dass jede Veröffentlichung sie zwingend enthalten muss.",
|
||||
"complaints_title": "Beschwerde- / Rückmeldestelle",
|
||||
"complaints_help": "Best Practice für transparente Veröffentlichungen. Art. 50 begründet keine allgemeine Pflicht, bei jedem gekennzeichneten Inhalt eine interne Beschwerdestelle zu benennen. Dieser freiwillige Kontakt ersetzt auch nicht das gesonderte Beschwerderecht nach Art. 85 AI Act gegenüber einer Marktüberwachungsbehörde; Medien-, Verbraucher-, Plattform- oder sonstiges Fachrecht kann zusätzliche Pflichten vorsehen.",
|
||||
"field_complaint_name": "Anlaufstelle / Organisation",
|
||||
"field_complaint_email": "Beschwerde- / Feedback-E-Mail",
|
||||
"field_complaint_url": "Beschwerde- / Feedback-URL",
|
||||
"validation_heading": "Plausibilitäts- und Rechtshinweise",
|
||||
"validation_formal_review": "Es ist nur eine grundlegende/formale Prüfung dokumentiert. Nach den Kommissionsleitlinien reichen Rechtschreibung, Grammatik oder andere oberflächliche Prüfungen für die Public-Interest-Ausnahme nicht als menschliche Prüfung/redaktionelle Kontrolle aus.",
|
||||
"validation_missing_responsibility": "Eine substanzielle Prüfung ist ausgewählt, die redaktionelle Verantwortung aber nicht vollständig benannt. Die Ausnahme für Public-Interest-Texte sollte auf dieser Grundlage nicht unterstellt werden.",
|
||||
"validation_provider": "Provider-Rolle ausgewählt: Ein sichtbares Badge oder JSON-LD ersetzt nicht die anbieterseitige Pflicht zur maschinenlesbaren Markierung einschlägiger generativer KI-Ausgaben.",
|
||||
"validation_role_unsure": "Die AI-Act-Rolle ist noch unklar. Deployer-seitige Offenlegungspflichten sollten nicht ausgeschlossen werden, bevor geklärt ist, ob das KI-System im beruflichen/organisatorischen Kontext unter eigener Verantwortung eingesetzt wird.",
|
||||
"validation_use_context_unsure": "Der Nutzungskontext ist unklar oder gemischt. Berufliche, geschäftliche, freiberufliche oder sonst wirtschaftliche Nutzung sollte von rein persönlicher, nicht beruflicher Nutzung getrennt betrachtet werden, bevor auf die persönliche Nutzungsausnahme vertraut wird.",
|
||||
"validation_personal": "Rein persönliche, nicht berufliche Nutzung fällt grundsätzlich nicht unter den Deployer-Begriff des AI Act. Eine freiwillige Transparenzkennzeichnung kann dennoch sinnvoll sein; anderes Recht kann weiterhin gelten.",
|
||||
"validation_pre_application": "Das angegebene Output-Datum liegt vor dem 2. August 2026. Nach der Kommissions-FAQ müssen solche Inhalte nach Art. 50 nicht rückwirkend gekennzeichnet werden; andere Rechtspflichten bleiben unberührt.",
|
||||
"validation_public_no_ai": "Public-Interest-Text ist ausgewählt, aber für den Text ist keine KI-Beteiligung dokumentiert. Bitte Inhaltsangaben prüfen oder die Kategorie entfernen.",
|
||||
"validation_deepfake_no_media": "Deepfake ist ausgewählt, aber es ist kein KI-beteiligtes Bild, Audio oder Video dokumentiert. Bitte Inhaltsangaben prüfen oder die Kategorie entfernen.",
|
||||
"validation_deepfake_unsure": "Die Deepfake-Einordnung ist offen. Vor einem „Nein“ sollten insbesondere Ähnlichkeit, reales oder plausibel reales Vorbild, der falsche Eindruck von Authentizität/Wahrheit sowie Veröffentlichungskontext und Publikumserwartung geprüft werden.",
|
||||
"validation_public_unsure": "Die Public-Interest-Einordnung ist offen. Vor einem „Nein“ sollte geprüft werden, ob der Text veröffentlicht wird, die Öffentlichkeit informieren soll und eine Angelegenheit von öffentlichem Interesse betrifft.",
|
||||
"validation_creative_unsure": "Die Einordnung als evident künstlerisch/kreativ/satirisch/fiktional ist offen. Sie betrifft bei Deepfakes die Art der Offenlegung, nicht die grundsätzliche Transparenzpflicht als solche.",
|
||||
"validation_complaint_incomplete": "Bitte die Beschwerde- / Rückmeldestelle mit Name und mindestens einer gültigen E-Mail-Adresse oder URL vervollständigen.",
|
||||
"consequence_disclosure": "Voraussichtliche Konsequenz: Einen klaren, unterscheidbaren Hinweis spätestens bei der ersten Exposition zum betroffenen Inhalt anzeigen; Barrierefreiheitsanforderungen sind ebenfalls zu beachten.",
|
||||
"consequence_provider": "Anbieter-Konsequenz: Die eigenständigen Anforderungen aus Art. 50 Abs. 2 an maschinenlesbare Markierungen prüfen und dabei gesondert bewerten, ob die enge Ausnahme für Standard-Bearbeitung bzw. nicht wesentliche Änderungen tatsächlich greift. Dieser Generator ist nur ergänzende Dokumentation.",
|
||||
"consequence_voluntary": "Aus den Angaben ergibt sich keine inhaltsspezifische Pflicht nach Art. 50 Abs. 4. Freiwillige Transparenz bleibt möglich; andere AI-Act- oder Fachpflichten können dennoch gelten.",
|
||||
"legal_assessment_pending": "Die Art.-50-Einordnung ist noch nicht vollständig. Beantworte die aktuell als erforderlich markierten Fragen; bis dahin trifft der Generator keine Aussage dazu, ob eine Kennzeichnungspflicht besteht oder nicht besteht.",
|
||||
"legal_assessment_personal": "Als Nutzungskontext wurde eine rein persönliche, nicht berufliche Nutzung angegeben. Diese fällt grundsätzlich nicht unter den Deployer-Begriff des AI Act; für Art. 50 behandelt der Generator die Kennzeichnung daher als freiwillig. Anderes Recht kann trotzdem gelten.",
|
||||
"legal_assessment_pre_application": "Das angegebene Output-Datum liegt vor dem 2. August 2026. Nach der FAQ der Kommission müssen vor diesem Datum erzeugte Inhalte nach Art. 50 nicht rückwirkend gekennzeichnet werden. Freiwillige Transparenz und andere Rechtspflichten bleiben möglich.",
|
||||
"legal_assessment_provider": "Es wurde ausschließlich die Provider-/Anbieterrolle angegeben. Dieses Veröffentlichungs-Badge kann die anbieterseitigen Art.-50-Pflichten, insbesondere die maschinenlesbare Markierung einschlägiger generierter oder manipulierter Outputs, nicht erfüllen. Dafür ist eine gesonderte Anbieterprüfung erforderlich, einschließlich der engen Ausnahme nach Art. 50 Abs. 2 für Standard-Bearbeitung bzw. nicht wesentliche Änderungen von Eingabedaten oder deren Semantik.",
|
||||
"legal_assessment_law_enforcement": "Es wurde eine einschlägige gesetzliche Autorisierung für Strafverfolgungszwecke angegeben. Art. 50 Abs. 4 enthält dafür eine besondere Ausnahme; der Generator behandelt die inhaltsspezifische Deployer-Offenlegung auf dieser Grundlage daher nicht als erforderlich. Die Rechtsgrundlage sollte dokumentiert werden; andere Pflichten bleiben unberührt.",
|
||||
"legal_assessment_deepfake_unsure": "Es ist unklar, ob der KI-beteiligte Medieninhalt als Deepfake einzuordnen ist. Eine inhaltsspezifische Pflicht nach Art. 50 Abs. 4 kann anhand der Angaben nicht ausgeschlossen werden. Bis zur Klärung ist eine klare Offenlegung die vorsichtigere Veröffentlichungseinstellung.",
|
||||
"legal_assessment_public_unsure": "Es ist unklar, ob der KI-beteiligte Text veröffentlicht wird, um die Öffentlichkeit über eine Angelegenheit von öffentlichem Interesse zu informieren. Eine inhaltsspezifische Pflicht nach Art. 50 Abs. 4 kann anhand der Angaben nicht ausgeschlossen werden. Bis zur Klärung ist eine klare Offenlegung die vorsichtigere Veröffentlichungseinstellung.",
|
||||
"validation_law_enforcement_unsure": "Die gesetzliche Autorisierung für Strafverfolgungszwecke ist unklar. Auf die Ausnahme nach Art. 50 Abs. 4 sollte erst vertraut werden, wenn konkrete Rechtsgrundlage und Anwendungsbereich geprüft sind.",
|
||||
"legal_enforcement_title": "Mögliche rechtliche Konsequenzen",
|
||||
"legal_enforcement_help": "Ab 2. August 2026 fallen Verstöße gegen Art. 50 grundsätzlich in den Bußgeldrahmen des Art. 99 Abs. 4: bis zu 15 Mio. EUR oder bei Unternehmen bis zu 3 % des weltweiten Jahresgesamtumsatzes des vorangegangenen Geschäftsjahres (unter Beachtung der besonderen KMU-Regel und der Umstände des Einzelfalls). Ob ein Verstoß vorliegt und welche Maßnahme oder Geldbuße verhängt wird, entscheidet die zuständige Behörde.",
|
||||
"legal_disclaimer": "Entscheidungshilfe, keine Rechtsberatung: Die Einschätzung beruht ausschließlich auf den eingegebenen Tatsachen sowie dem aktuellen Art. 50 und den Kommissionsleitlinien. Grenzfälle benötigen eine individuelle rechtliche Prüfung.",
|
||||
"fact_author": "Autor/in",
|
||||
"fact_complaints_contact": "Beschwerde- / Rückmeldestelle",
|
||||
"fact_complaints_email": "Beschwerde- / Feedback-E-Mail",
|
||||
"fact_legal_role": "AI-Act-Rolle (Selbsteinordnung)",
|
||||
"fact_use_context": "Nutzungskontext (Selbsteinordnung)",
|
||||
"fact_output_date": "Datum des KI-Outputs",
|
||||
"fact_deepfake_assessment": "Deepfake-Einordnung",
|
||||
"fact_public_interest_assessment": "Public-Interest-Text-Einordnung",
|
||||
"fact_creative_work_assessment": "Kreativ-/Satire-/Fiktions-Einordnung",
|
||||
"fact_law_enforcement_authorization": "Strafverfolgungs-Autorisierung (Selbsteinordnung)",
|
||||
}
|
||||
for code := range catalogs {
|
||||
if code == "de" {
|
||||
continue
|
||||
}
|
||||
l := catalogs[code]
|
||||
for k, v := range en {
|
||||
l.Text[k] = v
|
||||
}
|
||||
catalogs[code] = l
|
||||
}
|
||||
l := catalogs["de"]
|
||||
for k, v := range de {
|
||||
l.Text[k] = v
|
||||
}
|
||||
catalogs["de"] = l
|
||||
}
|
||||
|
||||
func applyThemeTranslations() {
|
||||
|
||||
@@ -183,13 +183,13 @@ func Build(lang, productName, baseURL, salesURL, contactURL string, prices Price
|
||||
# Adjust BASE_URL and PUBLIC_NAME in .env
|
||||
docker compose up -d --build
|
||||
curl -fsS http://localhost:8080/readyz`},
|
||||
{ID: "docker", Title: copy.InstallTitles[1], Summary: copy.InstallSummaries[1], Code: `docker build -t ai-disclosure-standard:1.6.3 .
|
||||
{ID: "docker", Title: copy.InstallTitles[1], Summary: copy.InstallSummaries[1], Code: `docker build -t ai-disclosure-standard:1.7.0 .
|
||||
docker run -d --name ai-disclosure \
|
||||
-p 8080:8080 \
|
||||
-e BASE_URL=https://ai.example.org \
|
||||
-e PUBLIC_NAME="AI Usage Disclosure" \
|
||||
--read-only --tmpfs /tmp \
|
||||
ai-disclosure-standard:1.6.3`},
|
||||
ai-disclosure-standard:1.7.0`},
|
||||
{ID: "kubernetes", Title: copy.InstallTitles[2], Summary: copy.InstallSummaries[2], Code: `# Image, Domain und TLS-Secret in deploy/kubernetes.yaml ersetzen
|
||||
kubectl create secret generic ai-disclosure-license \
|
||||
--from-literal=token='...'
|
||||
|
||||
+108
-8
@@ -1,7 +1,7 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: AI Usage Disclosure API
|
||||
version: 1.6.3
|
||||
version: 1.7.0
|
||||
description: Stateless multilingual badge, declaration and validation API with optional licensed presentation capabilities.
|
||||
servers:
|
||||
- url: https://ai.example.org
|
||||
@@ -32,6 +32,13 @@ paths:
|
||||
- {$ref: '#/components/parameters/language'}
|
||||
- {name: preset, in: query, schema: {type: string, enum: [no-ai, research, summary, full]}}
|
||||
- {$ref: '#/components/parameters/legalContext'}
|
||||
- {$ref: '#/components/parameters/legalRole'}
|
||||
- {$ref: '#/components/parameters/useContext'}
|
||||
- {$ref: '#/components/parameters/outputDate'}
|
||||
- {$ref: '#/components/parameters/deepfakeAssessment'}
|
||||
- {$ref: '#/components/parameters/publicInterestAssessment'}
|
||||
- {$ref: '#/components/parameters/creativeWorkAssessment'}
|
||||
- {$ref: '#/components/parameters/lawEnforcementAuthorization'}
|
||||
- {$ref: '#/components/parameters/theme'}
|
||||
- {$ref: '#/components/parameters/style'}
|
||||
- {name: link, in: query, schema: {type: string}}
|
||||
@@ -56,10 +63,21 @@ paths:
|
||||
- {$ref: '#/components/parameters/activities'}
|
||||
- {$ref: '#/components/parameters/review'}
|
||||
- {$ref: '#/components/parameters/legalContext'}
|
||||
- {name: responsibleRole, in: query, schema: {type: string, enum: [publisher, other]}}
|
||||
- {name: responsible, in: query, schema: {type: string, maxLength: 200}}
|
||||
- {name: responsibleUrl, in: query, schema: {type: string, format: uri}}
|
||||
- {$ref: '#/components/parameters/legalContext'}
|
||||
- {$ref: '#/components/parameters/legalRole'}
|
||||
- {$ref: '#/components/parameters/useContext'}
|
||||
- {$ref: '#/components/parameters/outputDate'}
|
||||
- {$ref: '#/components/parameters/deepfakeAssessment'}
|
||||
- {$ref: '#/components/parameters/publicInterestAssessment'}
|
||||
- {$ref: '#/components/parameters/creativeWorkAssessment'}
|
||||
- {$ref: '#/components/parameters/lawEnforcementAuthorization'}
|
||||
- {$ref: '#/components/parameters/author'}
|
||||
- {$ref: '#/components/parameters/authorUrl'}
|
||||
- {$ref: '#/components/parameters/responsibleRole'}
|
||||
- {$ref: '#/components/parameters/responsible'}
|
||||
- {$ref: '#/components/parameters/responsibleUrl'}
|
||||
- {$ref: '#/components/parameters/complaintName'}
|
||||
- {$ref: '#/components/parameters/complaintEmail'}
|
||||
- {$ref: '#/components/parameters/complaintUrl'}
|
||||
- {$ref: '#/components/parameters/assurance'}
|
||||
- {$ref: '#/components/parameters/customTitle'}
|
||||
- {$ref: '#/components/parameters/customDescription'}
|
||||
@@ -81,9 +99,22 @@ paths:
|
||||
- {$ref: '#/components/parameters/activities'}
|
||||
- {$ref: '#/components/parameters/review'}
|
||||
- {name: subject, in: query, schema: {type: string, format: uri}}
|
||||
- {name: responsibleRole, in: query, schema: {type: string, enum: [publisher, other]}}
|
||||
- {name: responsible, in: query, schema: {type: string, maxLength: 200}}
|
||||
- {name: responsibleUrl, in: query, schema: {type: string, format: uri}}
|
||||
- {$ref: '#/components/parameters/legalContext'}
|
||||
- {$ref: '#/components/parameters/legalRole'}
|
||||
- {$ref: '#/components/parameters/useContext'}
|
||||
- {$ref: '#/components/parameters/outputDate'}
|
||||
- {$ref: '#/components/parameters/deepfakeAssessment'}
|
||||
- {$ref: '#/components/parameters/publicInterestAssessment'}
|
||||
- {$ref: '#/components/parameters/creativeWorkAssessment'}
|
||||
- {$ref: '#/components/parameters/lawEnforcementAuthorization'}
|
||||
- {$ref: '#/components/parameters/author'}
|
||||
- {$ref: '#/components/parameters/authorUrl'}
|
||||
- {$ref: '#/components/parameters/responsibleRole'}
|
||||
- {$ref: '#/components/parameters/responsible'}
|
||||
- {$ref: '#/components/parameters/responsibleUrl'}
|
||||
- {$ref: '#/components/parameters/complaintName'}
|
||||
- {$ref: '#/components/parameters/complaintEmail'}
|
||||
- {$ref: '#/components/parameters/complaintUrl'}
|
||||
- {$ref: '#/components/parameters/assurance'}
|
||||
- {$ref: '#/components/parameters/customTitle'}
|
||||
- {$ref: '#/components/parameters/customDescription'}
|
||||
@@ -176,6 +207,75 @@ components:
|
||||
in: query
|
||||
description: Comma-separated self-classification for Article 50 context. This is supporting metadata, not an automatic legal determination.
|
||||
schema: {type: string, example: publicInterestText}
|
||||
legalRole:
|
||||
name: legalRole
|
||||
in: query
|
||||
description: Self-assessed AI Act actor role. Provider duties and deployer duties are distinct.
|
||||
schema: {type: string, enum: [deployer, provider, both, unsure]}
|
||||
useContext:
|
||||
name: useContext
|
||||
in: query
|
||||
description: Professional/organisational use or purely personal non-professional use for cautious scope assessment.
|
||||
schema: {type: string, enum: [professional, personalNonProfessional, unsure]}
|
||||
outputDate:
|
||||
name: outputDate
|
||||
in: query
|
||||
description: Date the affected output was generated or manipulated; used only for application-date guidance.
|
||||
schema: {type: string, format: date}
|
||||
deepfakeAssessment:
|
||||
name: deepfakeAssessment
|
||||
in: query
|
||||
description: Required-by-workflow yes/no/unsure self-assessment for potentially realistic AI-generated or manipulated image, audio or video content. A yes value is also reflected in legalContext.categories.
|
||||
schema: {type: string, enum: [yes, no, unsure]}
|
||||
publicInterestAssessment:
|
||||
name: publicInterestAssessment
|
||||
in: query
|
||||
description: Required-by-workflow yes/no/unsure self-assessment whether AI-generated or manipulated text is published to inform the public on a matter of public interest. A yes value is also reflected in legalContext.categories.
|
||||
schema: {type: string, enum: [yes, no, unsure]}
|
||||
creativeWorkAssessment:
|
||||
name: creativeWorkAssessment
|
||||
in: query
|
||||
description: Conditional yes/no/unsure self-assessment for the evidently artistic, creative, satirical or fictional deepfake modifier.
|
||||
schema: {type: string, enum: [yes, no, unsure]}
|
||||
lawEnforcementAuthorization:
|
||||
name: lawEnforcementAuthorization
|
||||
in: query
|
||||
description: Self-assessment whether the Article 50(4) criminal-law authorisation exception applies to the specific use.
|
||||
schema: {type: string, enum: [yes, no, unsure]}
|
||||
author:
|
||||
name: author
|
||||
in: query
|
||||
description: Optional author/byline metadata; distinct from editorial legal responsibility.
|
||||
schema: {type: string, maxLength: 200}
|
||||
authorUrl:
|
||||
name: authorUrl
|
||||
in: query
|
||||
schema: {type: string, format: uri}
|
||||
responsibleRole:
|
||||
name: responsibleRole
|
||||
in: query
|
||||
schema: {type: string, enum: [publisher, other]}
|
||||
responsible:
|
||||
name: responsible
|
||||
in: query
|
||||
schema: {type: string, maxLength: 200}
|
||||
responsibleUrl:
|
||||
name: responsibleUrl
|
||||
in: query
|
||||
schema: {type: string, format: uri}
|
||||
complaintName:
|
||||
name: complaintName
|
||||
in: query
|
||||
description: Optional best-practice complaint/feedback contact; not presented as a general Article 50 requirement.
|
||||
schema: {type: string, maxLength: 200}
|
||||
complaintEmail:
|
||||
name: complaintEmail
|
||||
in: query
|
||||
schema: {type: string, format: email}
|
||||
complaintUrl:
|
||||
name: complaintUrl
|
||||
in: query
|
||||
schema: {type: string, format: uri}
|
||||
assurance:
|
||||
name: assurance
|
||||
in: query
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"const": "AIUsageDeclaration"
|
||||
},
|
||||
"schemaVersion": {
|
||||
"const": "1.2"
|
||||
"const": "1.3"
|
||||
},
|
||||
"subject": {
|
||||
"type": "string",
|
||||
@@ -49,6 +49,23 @@
|
||||
"$ref": "#/$defs/component"
|
||||
}
|
||||
},
|
||||
"author": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"maxLength": 200
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
}
|
||||
}
|
||||
},
|
||||
"editorialResponsibility": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
@@ -74,6 +91,40 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"complaintsContact": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"maxLength": 200
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"maxLength": 320
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"format": "uri"
|
||||
}
|
||||
},
|
||||
"anyOf": [
|
||||
{
|
||||
"required": [
|
||||
"email"
|
||||
]
|
||||
},
|
||||
{
|
||||
"required": [
|
||||
"url"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"assurance": {
|
||||
"enum": [
|
||||
"selfDeclared",
|
||||
@@ -127,6 +178,61 @@
|
||||
"otherVoluntary"
|
||||
]
|
||||
}
|
||||
},
|
||||
"actorRole": {
|
||||
"enum": [
|
||||
"deployer",
|
||||
"provider",
|
||||
"both",
|
||||
"unsure"
|
||||
]
|
||||
},
|
||||
"useContext": {
|
||||
"enum": [
|
||||
"professional",
|
||||
"personalNonProfessional",
|
||||
"unsure"
|
||||
]
|
||||
},
|
||||
"outputDate": {
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
"lawEnforcementAuthorization": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"yes",
|
||||
"no",
|
||||
"unsure"
|
||||
],
|
||||
"description": "Self-declared assessment whether the use is authorised by law to detect, prevent, investigate or prosecute criminal offences for the Article 50(4) exception."
|
||||
},
|
||||
"deepfakeAssessment": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"yes",
|
||||
"no",
|
||||
"unsure"
|
||||
],
|
||||
"description": "Self-assessment whether the AI-involved image, audio or video qualifies as a deepfake for Article 50(4)."
|
||||
},
|
||||
"publicInterestAssessment": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"yes",
|
||||
"no",
|
||||
"unsure"
|
||||
],
|
||||
"description": "Self-assessment whether AI-involved text is published to inform the public on a matter of public interest for Article 50(4)."
|
||||
},
|
||||
"creativeWorkAssessment": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"yes",
|
||||
"no",
|
||||
"unsure"
|
||||
],
|
||||
"description": "Self-assessment whether a deepfake forms part of an evidently artistic, creative, satirical, fictional or analogous work or programme."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+341
-101
@@ -5,9 +5,9 @@
|
||||
const form = document.getElementById('generator-form');
|
||||
if (!form) return;
|
||||
const $ = id => document.getElementById(id);
|
||||
const ARTICLE_50_START = '2026-08-02';
|
||||
const presets = {
|
||||
// Never assume human/editorial review. It can be legally relevant under
|
||||
// Article 50(4) AI Act and must reflect the publisher's actual workflow.
|
||||
// Human/editorial review is a factual workflow statement and must never be inferred.
|
||||
'no-ai': {extent:'none', activities:'', review:'none'},
|
||||
research: {extent:'assisted', activities:'research', review:'none'},
|
||||
summary: {extent:'assisted', activities:'summarisation', review:'none'},
|
||||
@@ -19,6 +19,20 @@
|
||||
return el && !el.disabled ? el.value.trim() : '';
|
||||
}
|
||||
|
||||
function validValue(id) {
|
||||
const el = $(id);
|
||||
if (!el || el.disabled) return '';
|
||||
const v = el.value.trim();
|
||||
return v && (!el.validity || el.validity.valid) ? v : '';
|
||||
}
|
||||
|
||||
function setHidden(id, hidden) {
|
||||
const el = $(id);
|
||||
if (!el) return;
|
||||
el.hidden = hidden;
|
||||
el.querySelectorAll?.('input,select,textarea').forEach(input => { input.disabled = hidden; });
|
||||
}
|
||||
|
||||
function setArticleParams(params) {
|
||||
params.set('mode', 'article');
|
||||
const fields = {
|
||||
@@ -27,20 +41,23 @@
|
||||
image: 'article-image',
|
||||
research: 'article-research',
|
||||
translation: 'article-translation',
|
||||
audio: 'article-audio',
|
||||
video: 'article-video',
|
||||
code: 'article-code'
|
||||
};
|
||||
Object.entries(fields).forEach(([name, id]) => {
|
||||
params.set(name + 'Extent', $(id).value);
|
||||
params.set(name + 'Review', $(`${id}-review`).value);
|
||||
const extent = $(id).value;
|
||||
params.set(name + 'Extent', extent);
|
||||
params.set(name + 'Review', extent === 'none' ? 'none' : $(`${id}-review`).value);
|
||||
});
|
||||
}
|
||||
|
||||
function legalCategories() {
|
||||
const values = [];
|
||||
if ($('legal-deepfake')?.checked) values.push('deepfake');
|
||||
if ($('legal-public-interest')?.checked) values.push('publicInterestText');
|
||||
if ($('legal-creative')?.checked) values.push('artisticCreativeSatiricalFictional');
|
||||
if ($('legal-other-voluntary')?.checked) values.push('otherVoluntary');
|
||||
if (value('legal-deepfake') === 'yes') values.push('deepfake');
|
||||
if (value('legal-public-interest') === 'yes') values.push('publicInterestText');
|
||||
if (value('legal-creative') === 'yes') values.push('artisticCreativeSatiricalFictional');
|
||||
if ($('legal-other-voluntary')?.checked && !$('legal-other-voluntary').disabled) values.push('otherVoluntary');
|
||||
return values;
|
||||
}
|
||||
|
||||
@@ -48,31 +65,296 @@
|
||||
return mode === 'editorial' || mode === 'expert';
|
||||
}
|
||||
|
||||
function legalAssessment(locale, articleMode) {
|
||||
const categories = legalCategories();
|
||||
const deepfake = categories.includes('deepfake');
|
||||
const publicInterest = categories.includes('publicInterestText');
|
||||
const creative = categories.includes('artisticCreativeSatiricalFictional');
|
||||
if (categories.length === 0) return {text: locale.text.legal_assessment_none, legal: false, badge: locale.text.badge_article || 'Article transparency'};
|
||||
if (categories.includes('otherVoluntary')) return {text: locale.text.legal_assessment_voluntary, legal: false, badge: locale.text.badge_article || 'Article transparency'};
|
||||
if (deepfake) return {text: creative ? locale.text.legal_assessment_deepfake_creative : locale.text.legal_assessment_deepfake, legal: true, badge: locale.text.badge_legal_disclosure || 'AI-generated / manipulated content'};
|
||||
function articleMode() {
|
||||
return $('declaration-mode').value === 'article';
|
||||
}
|
||||
|
||||
function extentHasAI(extent) {
|
||||
return Boolean(extent && extent !== 'none');
|
||||
}
|
||||
|
||||
function singleHasAI(component) {
|
||||
return $('component').value === component && extentHasAI($('extent').value);
|
||||
}
|
||||
|
||||
function articleHasAI(id) {
|
||||
return extentHasAI($(id)?.value || 'none');
|
||||
}
|
||||
|
||||
function hasAIText() {
|
||||
return articleMode() ? articleHasAI('article-text') : singleHasAI('text');
|
||||
}
|
||||
|
||||
function hasAIMedia() {
|
||||
if (articleMode()) {
|
||||
return ['article-cover-image', 'article-image', 'article-audio', 'article-video'].some(articleHasAI);
|
||||
}
|
||||
return ['coverImage', 'image', 'audio', 'video'].includes($('component').value) && extentHasAI($('extent').value);
|
||||
}
|
||||
|
||||
function hasAnyAI() {
|
||||
if (!articleMode()) return extentHasAI($('extent').value);
|
||||
return ['article-text','article-cover-image','article-image','article-research','article-translation','article-audio','article-video','article-code'].some(articleHasAI);
|
||||
}
|
||||
|
||||
function currentTextReview() {
|
||||
if (articleMode()) return $('article-text-review').value;
|
||||
return $('component').value === 'text' && extentHasAI($('extent').value) ? $('review').value : 'none';
|
||||
}
|
||||
|
||||
function selectedLegalContext() {
|
||||
return {
|
||||
role: value('legal-role'),
|
||||
useContext: value('use-context'),
|
||||
outputDate: value('output-date'),
|
||||
deepfakeAssessment: value('legal-deepfake'),
|
||||
publicInterestAssessment: value('legal-public-interest'),
|
||||
creativeWorkAssessment: value('legal-creative'),
|
||||
lawEnforcementAuthorization: value('law-enforcement-authorization'),
|
||||
categories: legalCategories()
|
||||
};
|
||||
}
|
||||
|
||||
function legalAssessment(locale) {
|
||||
const ctx = selectedLegalContext();
|
||||
const deepfake = ctx.deepfakeAssessment === 'yes' || ctx.categories.includes('deepfake');
|
||||
const publicInterest = ctx.publicInterestAssessment === 'yes' || ctx.categories.includes('publicInterestText');
|
||||
const creative = ctx.creativeWorkAssessment === 'yes' || ctx.categories.includes('artisticCreativeSatiricalFictional');
|
||||
const deepfakeUnsure = ctx.deepfakeAssessment === 'unsure';
|
||||
const publicInterestUnsure = ctx.publicInterestAssessment === 'unsure';
|
||||
|
||||
if (!hasAnyAI()) {
|
||||
return {text: locale.text.legal_assessment_none, legal: false, level: 'info', consequence: locale.text.consequence_voluntary, badge: locale.text.badge_article || 'Article transparency'};
|
||||
}
|
||||
|
||||
if (!ctx.role || !ctx.useContext) {
|
||||
return {text: locale.text.legal_assessment_pending, legal: false, level: 'attention', consequence: '', badge: locale.text.badge_article || 'Article transparency'};
|
||||
}
|
||||
if (ctx.useContext === 'personalNonProfessional') {
|
||||
return {text: locale.text.legal_assessment_personal, legal: false, level: 'info', consequence: locale.text.consequence_voluntary, badge: locale.text.badge_article || 'Article transparency'};
|
||||
}
|
||||
if (ctx.role === 'provider') {
|
||||
return {text: locale.text.legal_assessment_provider, legal: false, level: 'attention', consequence: locale.text.consequence_provider, badge: locale.text.badge_article || 'Article transparency'};
|
||||
}
|
||||
|
||||
const mediaRelevant = hasAIMedia();
|
||||
const textRelevant = hasAIText();
|
||||
const lawEnforcementRelevant = [ctx.deepfakeAssessment, ctx.publicInterestAssessment].some(v => v === 'yes' || v === 'unsure');
|
||||
const initialPending = (mediaRelevant && !ctx.deepfakeAssessment)
|
||||
|| (textRelevant && !ctx.publicInterestAssessment)
|
||||
|| (lawEnforcementRelevant && !ctx.lawEnforcementAuthorization);
|
||||
if (initialPending) {
|
||||
return {text: locale.text.legal_assessment_pending, legal: false, level: 'attention', consequence: '', badge: locale.text.badge_article || 'Article transparency'};
|
||||
}
|
||||
if (ctx.outputDate && ctx.outputDate < ARTICLE_50_START) {
|
||||
return {text: locale.text.legal_assessment_pre_application, legal: false, level: 'info', consequence: locale.text.consequence_voluntary, badge: locale.text.badge_article || 'Article transparency'};
|
||||
}
|
||||
if ((deepfake || publicInterest || deepfakeUnsure || publicInterestUnsure) && ctx.lawEnforcementAuthorization === 'yes') {
|
||||
return {text: locale.text.legal_assessment_law_enforcement, legal: false, level: 'info', consequence: locale.text.consequence_voluntary, badge: locale.text.badge_article || 'Article transparency'};
|
||||
}
|
||||
if (ctx.deepfakeAssessment === 'yes' && !ctx.creativeWorkAssessment) {
|
||||
return {text: locale.text.legal_assessment_pending, legal: false, level: 'attention', consequence: '', badge: locale.text.badge_article || 'Article transparency'};
|
||||
}
|
||||
if (deepfakeUnsure) {
|
||||
return {text: locale.text.legal_assessment_deepfake_unsure, legal: true, level: 'attention', consequence: locale.text.consequence_disclosure, badge: locale.text.badge_legal_disclosure || 'AI-generated / manipulated content'};
|
||||
}
|
||||
if (deepfake) {
|
||||
return {
|
||||
text: creative ? locale.text.legal_assessment_deepfake_creative : locale.text.legal_assessment_deepfake,
|
||||
legal: true,
|
||||
level: 'attention',
|
||||
consequence: locale.text.consequence_disclosure,
|
||||
badge: locale.text.badge_legal_disclosure || 'AI-generated / manipulated content'
|
||||
};
|
||||
}
|
||||
if (publicInterestUnsure) {
|
||||
return {text: locale.text.legal_assessment_public_unsure, legal: true, level: 'attention', consequence: locale.text.consequence_disclosure, badge: locale.text.badge_public_interest || 'AI-generated / edited content'};
|
||||
}
|
||||
if (publicInterest) {
|
||||
const review = articleMode ? $('article-text-review').value : ($('component').value === 'text' ? $('review').value : 'none');
|
||||
const hasResponsibility = Boolean(value('responsible'));
|
||||
if (substantiveReview(review) && hasResponsibility) return {text: locale.text.legal_assessment_public_exemption, legal: false, badge: locale.text.badge_article || 'Article transparency'};
|
||||
return {text: locale.text.legal_assessment_public_required, legal: true, badge: locale.text.badge_public_interest || 'AI-generated / edited content'};
|
||||
const hasResponsibility = Boolean(value('responsible-role') && value('responsible'));
|
||||
if (substantiveReview(currentTextReview()) && hasResponsibility) {
|
||||
return {text: locale.text.legal_assessment_public_exemption, legal: false, level: 'info', consequence: locale.text.consequence_voluntary, badge: locale.text.badge_article || 'Article transparency'};
|
||||
}
|
||||
return {text: locale.text.legal_assessment_public_required, legal: true, level: 'attention', consequence: locale.text.consequence_disclosure, badge: locale.text.badge_public_interest || 'AI-generated / edited content'};
|
||||
}
|
||||
if (ctx.categories.includes('otherVoluntary')) {
|
||||
return {text: locale.text.legal_assessment_voluntary, legal: false, level: 'info', consequence: locale.text.consequence_voluntary, badge: locale.text.badge_article || 'Article transparency'};
|
||||
}
|
||||
return {text: locale.text.legal_assessment_none, legal: false, level: 'info', consequence: locale.text.consequence_voluntary, badge: locale.text.badge_article || 'Article transparency'};
|
||||
}
|
||||
|
||||
function renderAssessment(locale, assessment) {
|
||||
const target = $('legal-assessment');
|
||||
if (!target) return;
|
||||
target.replaceChildren();
|
||||
const heading = document.createElement('strong');
|
||||
heading.textContent = locale.text.legal_assessment_heading || 'Article 50 assessment';
|
||||
const text = document.createElement('span');
|
||||
text.textContent = assessment.text || '';
|
||||
const consequence = document.createElement('span');
|
||||
consequence.className = 'assessment-consequence';
|
||||
consequence.textContent = assessment.consequence || '';
|
||||
target.append(heading, text, consequence);
|
||||
target.dataset.level = assessment.level || (assessment.legal ? 'attention' : 'info');
|
||||
}
|
||||
|
||||
function addValidationMessage(container, text, level = 'warning') {
|
||||
if (!text) return;
|
||||
const p = document.createElement('p');
|
||||
p.className = `validation-message ${level}`;
|
||||
p.textContent = text;
|
||||
container.appendChild(p);
|
||||
}
|
||||
|
||||
function renderValidationMessages(locale) {
|
||||
const target = $('validation-messages');
|
||||
if (!target) return;
|
||||
target.replaceChildren();
|
||||
if (!hasAnyAI()) return;
|
||||
const ctx = selectedLegalContext();
|
||||
const categories = ctx.categories;
|
||||
const review = currentTextReview();
|
||||
|
||||
if (ctx.role === 'provider' || ctx.role === 'both') addValidationMessage(target, locale.text.validation_provider, 'info');
|
||||
if (ctx.role === 'unsure') addValidationMessage(target, locale.text.validation_role_unsure);
|
||||
if (ctx.useContext === 'personalNonProfessional') addValidationMessage(target, locale.text.validation_personal, 'info');
|
||||
if (ctx.useContext === 'unsure') addValidationMessage(target, locale.text.validation_use_context_unsure);
|
||||
if (ctx.outputDate && ctx.outputDate < ARTICLE_50_START) addValidationMessage(target, locale.text.validation_pre_application, 'info');
|
||||
if (ctx.lawEnforcementAuthorization === 'unsure') addValidationMessage(target, locale.text.validation_law_enforcement_unsure);
|
||||
if (ctx.deepfakeAssessment === 'unsure') addValidationMessage(target, locale.text.validation_deepfake_unsure);
|
||||
if (ctx.publicInterestAssessment === 'unsure') addValidationMessage(target, locale.text.validation_public_unsure);
|
||||
if (ctx.creativeWorkAssessment === 'unsure') addValidationMessage(target, locale.text.validation_creative_unsure, 'info');
|
||||
if (categories.includes('publicInterestText') && !hasAIText()) addValidationMessage(target, locale.text.validation_public_no_ai);
|
||||
if (categories.includes('deepfake') && !hasAIMedia()) addValidationMessage(target, locale.text.validation_deepfake_no_media);
|
||||
if (categories.includes('publicInterestText') && review === 'basic') addValidationMessage(target, locale.text.validation_formal_review);
|
||||
if (categories.includes('publicInterestText') && substantiveReview(review) && (!value('responsible-role') || !value('responsible'))) {
|
||||
addValidationMessage(target, locale.text.validation_missing_responsibility);
|
||||
}
|
||||
const complaintStarted = value('complaint-name') || value('complaint-email') || value('complaint-url');
|
||||
if (complaintStarted && (!value('complaint-name') || (!validValue('complaint-email') && !validValue('complaint-url')))) {
|
||||
addValidationMessage(target, locale.text.validation_complaint_incomplete || 'Complete the complaint contact with a name and at least one valid contact method.');
|
||||
}
|
||||
}
|
||||
|
||||
function syncVisibility(locale) {
|
||||
const isArticle = articleMode();
|
||||
$('single-fields').hidden = isArticle;
|
||||
$('article-fields').hidden = !isArticle;
|
||||
|
||||
const noSingleAI = !isArticle && !extentHasAI($('extent').value);
|
||||
setHidden('single-review-field', noSingleAI);
|
||||
setHidden('single-activities-field', noSingleAI);
|
||||
if (noSingleAI) $('review').value = 'none';
|
||||
|
||||
document.querySelectorAll('#article-fields select[data-review]').forEach(extentSelect => {
|
||||
const reviewID = extentSelect.dataset.review;
|
||||
const wrap = document.querySelector(`[data-review-wrap="${reviewID}"]`);
|
||||
const hidden = extentSelect.value === 'none';
|
||||
if (wrap) {
|
||||
wrap.hidden = hidden;
|
||||
const reviewSelect = $(reviewID);
|
||||
if (reviewSelect) {
|
||||
reviewSelect.disabled = hidden;
|
||||
if (hidden) reviewSelect.value = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const anyAI = hasAnyAI();
|
||||
setHidden('legal-context-builder', !anyAI);
|
||||
$('legal-role').required = anyAI;
|
||||
$('use-context').required = anyAI;
|
||||
$('legal-role').disabled = !anyAI;
|
||||
$('use-context').disabled = !anyAI;
|
||||
if (!anyAI) {
|
||||
$('legal-role').value = '';
|
||||
$('use-context').value = '';
|
||||
['legal-deepfake','legal-public-interest','legal-creative','law-enforcement-authorization'].forEach(id => { if ($(id)) $(id).value = ''; });
|
||||
if ($('legal-other-voluntary')) $('legal-other-voluntary').checked = false;
|
||||
setHidden('legal-deepfake-row', true);
|
||||
setHidden('legal-public-interest-row', true);
|
||||
setHidden('legal-creative-row', true);
|
||||
setHidden('law-enforcement-fields', true);
|
||||
setHidden('legal-timing-fields', true);
|
||||
setHidden('legal-other-voluntary-row', true);
|
||||
}
|
||||
|
||||
if (anyAI) {
|
||||
const prerequisitesAnswered = Boolean(value('legal-role') && value('use-context'));
|
||||
const deployerScopeRelevant = prerequisitesAnswered && value('use-context') !== 'personalNonProfessional' && value('legal-role') !== 'provider';
|
||||
const mediaRelevant = deployerScopeRelevant && hasAIMedia();
|
||||
const textRelevant = deployerScopeRelevant && hasAIText();
|
||||
setHidden('legal-deepfake-row', !mediaRelevant);
|
||||
$('legal-deepfake').required = mediaRelevant;
|
||||
$('legal-deepfake').disabled = !mediaRelevant;
|
||||
if (!mediaRelevant) $('legal-deepfake').value = '';
|
||||
|
||||
setHidden('legal-public-interest-row', !textRelevant);
|
||||
$('legal-public-interest').required = textRelevant;
|
||||
$('legal-public-interest').disabled = !textRelevant;
|
||||
if (!textRelevant) $('legal-public-interest').value = '';
|
||||
|
||||
const legalExceptionRelevant = deployerScopeRelevant && [value('legal-deepfake'), value('legal-public-interest')].some(v => v === 'yes' || v === 'unsure');
|
||||
setHidden('law-enforcement-fields', !legalExceptionRelevant);
|
||||
$('law-enforcement-authorization').required = legalExceptionRelevant;
|
||||
$('law-enforcement-authorization').disabled = !legalExceptionRelevant;
|
||||
if (!legalExceptionRelevant) $('law-enforcement-authorization').value = '';
|
||||
|
||||
const creativeRelevant = mediaRelevant && value('legal-deepfake') === 'yes' && ['no', 'unsure'].includes(value('law-enforcement-authorization'));
|
||||
setHidden('legal-creative-row', !creativeRelevant);
|
||||
$('legal-creative').required = creativeRelevant;
|
||||
$('legal-creative').disabled = !creativeRelevant;
|
||||
if (!creativeRelevant) $('legal-creative').value = '';
|
||||
|
||||
const allRelevantScopeQuestionsResolvedNo = deployerScopeRelevant && (!mediaRelevant || value('legal-deepfake') === 'no') && (!textRelevant || value('legal-public-interest') === 'no');
|
||||
const noContentScopeQuestions = deployerScopeRelevant && !mediaRelevant && !textRelevant;
|
||||
const showVoluntary = noContentScopeQuestions || allRelevantScopeQuestionsResolvedNo;
|
||||
setHidden('legal-other-voluntary-row', !showVoluntary);
|
||||
if (!showVoluntary) $('legal-other-voluntary').checked = false;
|
||||
|
||||
const legalQuestionAnswered = [value('legal-deepfake'), value('legal-public-interest')].some(Boolean);
|
||||
setHidden('legal-timing-fields', !deployerScopeRelevant || !legalQuestionAnswered);
|
||||
$('output-date').disabled = !deployerScopeRelevant || !legalQuestionAnswered;
|
||||
}
|
||||
|
||||
const publicInterest = value('legal-public-interest') === 'yes';
|
||||
const showResponsibility = anyAI && publicInterest && ['no', 'unsure'].includes(value('law-enforcement-authorization')) && substantiveReview(currentTextReview()) && value('use-context') !== 'personalNonProfessional' && value('legal-role') !== 'provider';
|
||||
setHidden('responsibility-fields', !showResponsibility);
|
||||
$('responsible-role').required = showResponsibility;
|
||||
$('responsible').required = showResponsibility;
|
||||
|
||||
const showAuthor = anyAI && (isArticle || hasAIText());
|
||||
setHidden('author-fields', !showAuthor);
|
||||
setHidden('author-url-field', !showAuthor || !value('author'));
|
||||
|
||||
const accountabilityRelevant = anyAI && [value('legal-deepfake'), value('legal-public-interest')].some(v => v === 'yes' || v === 'unsure') && value('law-enforcement-authorization') !== 'yes' && value('use-context') !== 'personalNonProfessional';
|
||||
setHidden('complaint-fields', !accountabilityRelevant);
|
||||
|
||||
renderValidationMessages(locale);
|
||||
}
|
||||
|
||||
function appendMetadataParams(params) {
|
||||
if (validValue('subject')) params.set('subject', validValue('subject'));
|
||||
if (!$('author-fields')?.hidden && value('author')) {
|
||||
params.set('author', value('author'));
|
||||
if (validValue('author-url')) params.set('authorUrl', validValue('author-url'));
|
||||
}
|
||||
if (!$('responsibility-fields')?.hidden && value('responsible-role') && value('responsible')) {
|
||||
params.set('responsibleRole', value('responsible-role'));
|
||||
params.set('responsible', value('responsible'));
|
||||
if (validValue('responsible-url')) params.set('responsibleUrl', validValue('responsible-url'));
|
||||
}
|
||||
if (!$('complaint-fields')?.hidden && value('complaint-name') && (validValue('complaint-email') || validValue('complaint-url'))) {
|
||||
params.set('complaintName', value('complaint-name'));
|
||||
if (validValue('complaint-email')) params.set('complaintEmail', validValue('complaint-email'));
|
||||
if (validValue('complaint-url')) params.set('complaintUrl', validValue('complaint-url'));
|
||||
}
|
||||
return {text: locale.text.legal_assessment_voluntary, legal: false, badge: locale.text.badge_article || 'Article transparency'};
|
||||
}
|
||||
|
||||
function update(applyPreset = false) {
|
||||
const mode = $('declaration-mode').value;
|
||||
const articleMode = mode === 'article';
|
||||
$('single-fields').hidden = articleMode;
|
||||
$('article-fields').hidden = !articleMode;
|
||||
|
||||
const isArticle = articleMode();
|
||||
const preset = $('preset').value;
|
||||
if (!articleMode && applyPreset && presets[preset]) {
|
||||
if (!isArticle && applyPreset && presets[preset]) {
|
||||
$('extent').value = presets[preset].extent;
|
||||
$('activities').value = presets[preset].activities;
|
||||
$('review').value = presets[preset].review;
|
||||
@@ -80,29 +362,37 @@
|
||||
|
||||
const lang = $('lang').value;
|
||||
const locale = cfg.locales[lang] || cfg.locales.en;
|
||||
syncVisibility(locale);
|
||||
|
||||
const assurance = $('assurance').value;
|
||||
const assuranceDescription = locale.text[`assurance_${assurance}_description`];
|
||||
if ($('assurance-description') && assuranceDescription) {
|
||||
$('assurance-description').textContent = assuranceDescription;
|
||||
}
|
||||
if ($('assurance-description') && assuranceDescription) $('assurance-description').textContent = assuranceDescription;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (articleMode) {
|
||||
if (isArticle) {
|
||||
setArticleParams(params);
|
||||
} else {
|
||||
if (preset) params.set('preset', preset);
|
||||
params.set('component', $('component').value);
|
||||
params.set('extent', $('extent').value);
|
||||
if ($('activities').value.trim()) params.set('activities', $('activities').value.trim());
|
||||
params.set('review', $('review').value);
|
||||
if (extentHasAI($('extent').value) && $('activities').value.trim()) params.set('activities', $('activities').value.trim());
|
||||
params.set('review', extentHasAI($('extent').value) ? $('review').value : 'none');
|
||||
}
|
||||
params.set('lang', lang);
|
||||
params.set('assurance', assurance);
|
||||
if ($('subject').value.trim()) params.set('subject', $('subject').value.trim());
|
||||
if (value('responsible-role')) params.set('responsibleRole', value('responsible-role'));
|
||||
if (value('responsible')) params.set('responsible', value('responsible'));
|
||||
if (value('responsible-url')) params.set('responsibleUrl', value('responsible-url'));
|
||||
const legal = legalCategories();
|
||||
if (legal.length) params.set('legalContext', legal.join(','));
|
||||
|
||||
if (hasAnyAI() && !$('legal-context-builder').hidden) {
|
||||
if (value('legal-role')) params.set('legalRole', value('legal-role'));
|
||||
if (value('use-context')) params.set('useContext', value('use-context'));
|
||||
if (value('output-date')) params.set('outputDate', value('output-date'));
|
||||
if (value('legal-deepfake')) params.set('deepfakeAssessment', value('legal-deepfake'));
|
||||
if (value('legal-public-interest')) params.set('publicInterestAssessment', value('legal-public-interest'));
|
||||
if (value('legal-creative')) params.set('creativeWorkAssessment', value('legal-creative'));
|
||||
if (!$('law-enforcement-fields')?.hidden && value('law-enforcement-authorization')) params.set('lawEnforcementAuthorization', value('law-enforcement-authorization'));
|
||||
const legal = legalCategories();
|
||||
if (legal.length) params.set('legalContext', legal.join(','));
|
||||
}
|
||||
appendMetadataParams(params);
|
||||
|
||||
if (cfg.capabilities.custom_text) {
|
||||
if (value('custom-title')) params.set('customTitle', value('custom-title'));
|
||||
@@ -121,16 +411,14 @@
|
||||
badgeParams.set('link', declarationURL);
|
||||
const badgeURL = `${base}/v1/badge.svg?${badgeParams}`;
|
||||
const manifestURL = `${base}/v1/declaration.json?${params}`;
|
||||
const assessment = legalAssessment(locale, articleMode);
|
||||
const assessment = legalAssessment(locale);
|
||||
renderAssessment(locale, assessment);
|
||||
|
||||
const alt = assessment.legal
|
||||
? assessment.badge
|
||||
: (articleMode
|
||||
: (isArticle
|
||||
? (locale.text.badge_article || 'Article transparency')
|
||||
: ((locale.presets[preset] && locale.presets[preset].title) || locale.extents[$('extent').value] || 'AI usage disclosure'));
|
||||
if ($('legal-assessment')) {
|
||||
$('legal-assessment').innerHTML = `<strong>${locale.text.legal_assessment_heading || 'Article 50 assessment'}</strong><span>${assessment.text || ''}</span>`;
|
||||
$('legal-assessment').dataset.level = assessment.legal ? 'attention' : 'info';
|
||||
}
|
||||
|
||||
$('preview-badge').src = badgeURL;
|
||||
$('preview-badge').alt = alt;
|
||||
@@ -145,68 +433,20 @@
|
||||
$('json-code').value = manifestURL;
|
||||
}
|
||||
|
||||
|
||||
document.querySelectorAll('#article-fields select[data-review]').forEach(extentSelect => {
|
||||
extentSelect.addEventListener('change', () => {
|
||||
const reviewSelect = $(extentSelect.dataset.review);
|
||||
if (!reviewSelect) return;
|
||||
if (extentSelect.value === 'none') {
|
||||
reviewSelect.value = 'none';
|
||||
}
|
||||
// For AI-assisted/generated components keep "none" until the publisher
|
||||
// explicitly selects the review process that actually took place.
|
||||
update(false);
|
||||
});
|
||||
});
|
||||
|
||||
function alignLegalContextWithContent(changedId) {
|
||||
if (changedId === 'legal-deepfake' && $('legal-deepfake')?.checked) {
|
||||
if ($('legal-other-voluntary')) $('legal-other-voluntary').checked = false;
|
||||
if ($('declaration-mode').value === 'single') {
|
||||
if ($('legal-public-interest')?.checked) {
|
||||
$('declaration-mode').value = 'article';
|
||||
} else if (!['coverImage', 'image', 'audio', 'video'].includes($('component').value)) {
|
||||
$('preset').value = '';
|
||||
$('component').value = 'image';
|
||||
if ($('extent').value === 'none') $('extent').value = 'partial';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changedId === 'legal-public-interest' && $('legal-public-interest')?.checked) {
|
||||
if ($('legal-other-voluntary')) $('legal-other-voluntary').checked = false;
|
||||
if ($('declaration-mode').value === 'single') {
|
||||
if ($('legal-deepfake')?.checked) {
|
||||
$('declaration-mode').value = 'article';
|
||||
} else {
|
||||
$('preset').value = '';
|
||||
$('component').value = 'text';
|
||||
if ($('extent').value === 'none') $('extent').value = 'partial';
|
||||
}
|
||||
}
|
||||
if ($('declaration-mode').value === 'article' && $('article-text').value === 'none') {
|
||||
$('article-text').value = 'partial';
|
||||
}
|
||||
}
|
||||
if (changedId === 'legal-creative' && $('legal-creative')?.checked && $('legal-other-voluntary')) {
|
||||
function enforceLegalExclusivity(changedId) {
|
||||
if (['legal-deepfake','legal-public-interest'].includes(changedId) && ['yes','unsure'].includes(value(changedId)) && $('legal-other-voluntary')) {
|
||||
$('legal-other-voluntary').checked = false;
|
||||
}
|
||||
if ($('declaration-mode').value === 'article' && $('legal-deepfake')?.checked) {
|
||||
const media = ['article-cover-image', 'article-image'];
|
||||
const hasMedia = media.some(id => $(id)?.value !== 'none');
|
||||
if (!hasMedia && $('article-image')) $('article-image').value = 'partial';
|
||||
}
|
||||
}
|
||||
|
||||
['legal-deepfake', 'legal-public-interest', 'legal-creative'].forEach(id => {
|
||||
$(id)?.addEventListener('change', () => alignLegalContextWithContent(id));
|
||||
form.addEventListener('input', event => {
|
||||
enforceLegalExclusivity(event.target.id);
|
||||
update(event.target.id === 'preset');
|
||||
});
|
||||
$('legal-other-voluntary')?.addEventListener('change', () => {
|
||||
if (!$('legal-other-voluntary').checked) return;
|
||||
['legal-deepfake', 'legal-public-interest', 'legal-creative'].forEach(id => { if ($(id)) $(id).checked = false; });
|
||||
form.addEventListener('change', event => {
|
||||
enforceLegalExclusivity(event.target.id);
|
||||
update(event.target.id === 'preset');
|
||||
});
|
||||
|
||||
form.addEventListener('input', event => update(event.target.id === 'preset'));
|
||||
form.addEventListener('change', event => update(event.target.id === 'preset'));
|
||||
document.querySelector('.pro-panel')?.addEventListener('input', () => update(false));
|
||||
$('copy-html')?.addEventListener('click', async () => {
|
||||
await navigator.clipboard.writeText($('html-code').value);
|
||||
|
||||
@@ -62,3 +62,14 @@
|
||||
|
||||
/* Article 50 context and cautious assessment */
|
||||
.legal-context-builder{border:1px solid var(--line);border-radius:14px;padding:22px;background:#fbfaf7}.legal-context-builder legend{padding:0 8px;font-weight:800}.legal-context-builder>p{margin-top:0;color:var(--muted)}.generator-grid .check-row{display:grid;grid-template-columns:22px 1fr;gap:10px;align-items:start;padding:10px 0;font-weight:400}.check-row input{width:18px;height:18px;margin-top:3px}.check-row span{display:flex;flex-direction:column;gap:3px}.check-row strong{font-weight:800}.legal-assessment{margin-top:14px;border-radius:12px;padding:16px 18px;background:#eeeae2;display:flex;flex-direction:column;gap:6px}.legal-assessment[data-level="attention"]{border-left:4px solid #8f2d1f}.legal-assessment[data-level="info"]{border-left:4px solid #5d5a50}.legal-assessment-section{margin-top:32px}.legal-assessment-card{border:1px solid var(--line);border-radius:14px;padding:20px;background:#fbfaf7}.legal-assessment-card.attention{border-left:5px solid #8f2d1f}.legal-assessment-card.info{border-left:5px solid #5d5a50}
|
||||
|
||||
/* Conditional generator workflow */
|
||||
[hidden]{display:none!important}
|
||||
.form-step{border:1px solid var(--line);border-radius:16px;padding:22px;background:#fff}.form-step>legend{padding:0 10px;font-weight:900;font-size:1.05rem}.form-step>p{margin-top:0;color:var(--muted)}
|
||||
.field-badge{display:inline-flex;width:max-content;align-items:center;border-radius:999px;padding:2px 8px;margin-left:6px;font-size:.68rem;line-height:1.5;font-weight:800;vertical-align:middle;background:#eeeae2;color:#4a473f}.field-badge.required{background:#fff0ed;color:#7a261c}.field-badge.best-practice{background:#eef4ff;color:#294f85}.field-badge.optional{background:#f2f1ed;color:#5d5a50}
|
||||
.field-help{font-weight:400;margin-top:2px}.field-help summary{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border:1px solid var(--line);border-radius:999px;cursor:pointer;font-weight:900;color:var(--muted);list-style:none}.field-help summary::-webkit-details-marker{display:none}.field-help[open] summary{margin-bottom:7px}.field-help span{display:block;padding:10px 12px;border-radius:10px;background:#f3f1ec;color:var(--muted);line-height:1.5}
|
||||
.legal-category-list{display:grid;gap:10px;margin-top:18px}.legal-question{border:1px solid var(--line);border-radius:12px;padding:14px 16px;background:#fff}.legal-question label{display:block}.legal-question small{display:block;margin-top:6px;color:var(--muted);font-weight:400;line-height:1.45}.legal-question select{margin-top:10px}.conditional-block{margin-top:18px}.conditional-panel{border:1px solid var(--line);border-radius:14px;padding:18px;background:#fbfaf7}.conditional-panel h3{margin:0 0 6px;font-size:1rem}.conditional-panel>p{margin:.3rem 0 1rem;color:var(--muted);font-size:.92rem}
|
||||
.validation-messages{display:grid;gap:8px;margin-top:12px}.validation-message{margin:0;padding:12px 14px;border-radius:10px;background:#fff6ed;border-left:4px solid #9a5d14;font-size:.9rem}.validation-message.info{background:#eef4ff;border-left-color:#4f6f9f}.assessment-consequence{margin-top:5px;padding-top:8px;border-top:1px solid rgba(0,0,0,.09);font-weight:650}
|
||||
input:required,select:required{border-left-width:3px}.article-component label[hidden]{display:none!important}
|
||||
@media(max-width:760px){.form-step{padding:17px}.field-badge{margin-left:0;margin-top:3px}.conditional-panel{padding:15px}}
|
||||
.legal-consequences{margin-top:16px;border:1px solid var(--line);border-radius:12px;background:#f7f4ee;padding:14px 16px}.legal-consequences summary{cursor:pointer;font-weight:850}.legal-consequences p{margin:.8rem 0 0;color:var(--muted);font-size:.9rem;line-height:1.55}.legal-consequences .form-warning{color:#5b3029}
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer><span>{{.Background.Footer}} · App 1.6.3 · Schema 1.2 · {{.License.Edition}}</span>{{template "legal-links" .}}</footer>
|
||||
<footer><span>{{.Background.Footer}} · App 1.7.0 · Schema 1.3 · {{.License.Edition}}</span>{{template "legal-links" .}}</footer>
|
||||
<script src="/static/marketing.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+199
-70
@@ -33,79 +33,208 @@
|
||||
<p>{{index .Text "generator_intro"}}</p>
|
||||
</div>
|
||||
<form id="generator-form" class="generator-grid">
|
||||
<label class="wide">{{index .Text "field_mode"}}
|
||||
<select id="declaration-mode" name="mode"><option value="single">{{index .Text "mode_single"}}</option><option value="article">{{index .Text "mode_article"}}</option></select>
|
||||
</label>
|
||||
<div id="single-fields" class="form-subgrid wide">
|
||||
<label>{{index .Text "field_preset"}}
|
||||
<select id="preset" name="preset">
|
||||
{{range .Presets}}<option value="{{.Value}}">{{.Label}}</option>{{end}}
|
||||
<option value="">{{index .Text "option_custom"}}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>{{index .Text "field_component"}}
|
||||
<select id="component" name="component">{{range .Components}}<option value="{{.Value}}">{{.Label}}</option>{{end}}</select>
|
||||
</label>
|
||||
<label>{{index .Text "field_extent"}}
|
||||
<select id="extent" name="extent">{{range .Extents}}<option value="{{.Value}}">{{.Label}}</option>{{end}}</select>
|
||||
</label>
|
||||
<label>{{index .Text "field_review"}}
|
||||
<select id="review" name="review">{{range .Reviews}}<option value="{{.Value}}">{{.Label}}</option>{{end}}</select>
|
||||
<small>{{index .Text "review_help"}}</small>
|
||||
</label>
|
||||
<label class="wide">{{index .Text "field_activities"}}
|
||||
<input id="activities" name="activities" value="research" placeholder="research,summarisation">
|
||||
<small>{{index .Text "activities_help"}}</small>
|
||||
</label>
|
||||
</div>
|
||||
<fieldset id="article-fields" class="article-builder wide" hidden>
|
||||
<legend>{{index .Text "article_fields"}}</legend>
|
||||
<p>{{index .Text "article_help"}}</p>
|
||||
<p class="form-warning">{{index .Text "review_help"}}</p>
|
||||
<div class="article-component-grid">
|
||||
<div class="article-component"><label>Text<select id="article-text" data-review="article-text-review">{{range .Extents}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label><label>{{index .Text "field_review"}}<select id="article-text-review">{{range .Reviews}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label></div>
|
||||
<div class="article-component"><label>{{index .Text "fact_activities"}} / Recherche<select id="article-research" data-review="article-research-review">{{range .Extents}}<option value="{{.Value}}" {{if eq .Value "assisted"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label><label>{{index .Text "field_review"}}<select id="article-research-review">{{range .Reviews}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label></div>
|
||||
<div class="article-component"><label>Cover / {{index .Text "fact_component"}}<select id="article-cover-image" data-review="article-cover-image-review">{{range .Extents}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label><label>{{index .Text "field_review"}}<select id="article-cover-image-review">{{range .Reviews}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label></div>
|
||||
<div class="article-component"><label>Images<select id="article-image" data-review="article-image-review">{{range .Extents}}<option value="{{.Value}}" {{if eq .Value "full"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label><label>{{index .Text "field_review"}}<select id="article-image-review">{{range .Reviews}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label></div>
|
||||
<div class="article-component"><label>Translation<select id="article-translation" data-review="article-translation-review">{{range .Extents}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label><label>{{index .Text "field_review"}}<select id="article-translation-review">{{range .Reviews}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label></div>
|
||||
<div class="article-component"><label>Code<select id="article-code" data-review="article-code-review">{{range .Extents}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label><label>{{index .Text "field_review"}}<select id="article-code-review">{{range .Reviews}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label></div>
|
||||
<fieldset class="form-step wide" aria-labelledby="step-content-title">
|
||||
<legend id="step-content-title">{{index .Text "generator_step_content"}}</legend>
|
||||
<div class="form-subgrid">
|
||||
<label class="wide">{{index .Text "field_mode"}}
|
||||
<select id="declaration-mode" name="mode"><option value="single">{{index .Text "mode_single"}}</option><option value="article">{{index .Text "mode_article"}}</option></select>
|
||||
</label>
|
||||
<div id="single-fields" class="form-subgrid wide">
|
||||
<label>{{index .Text "field_preset"}}
|
||||
<select id="preset" name="preset">
|
||||
{{range .Presets}}<option value="{{.Value}}">{{.Label}}</option>{{end}}
|
||||
<option value="">{{index .Text "option_custom"}}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>{{index .Text "field_component"}}
|
||||
<select id="component" name="component">{{range .Components}}<option value="{{.Value}}">{{.Label}}</option>{{end}}</select>
|
||||
</label>
|
||||
<label>{{index .Text "field_extent"}}
|
||||
<select id="extent" name="extent">{{range .Extents}}<option value="{{.Value}}">{{.Label}}</option>{{end}}</select>
|
||||
</label>
|
||||
<label id="single-review-field">{{index .Text "field_review"}}
|
||||
<select id="review" name="review">{{range .Reviews}}<option value="{{.Value}}">{{.Label}}</option>{{end}}</select>
|
||||
<small>{{index .Text "review_help"}}</small>
|
||||
</label>
|
||||
<label id="single-activities-field" class="wide">{{index .Text "field_activities"}}
|
||||
<input id="activities" name="activities" value="research" placeholder="research,summarisation">
|
||||
<small>{{index .Text "activities_help"}}</small>
|
||||
</label>
|
||||
</div>
|
||||
<fieldset id="article-fields" class="article-builder wide" hidden>
|
||||
<legend>{{index .Text "article_fields"}}</legend>
|
||||
<p>{{index .Text "article_help"}}</p>
|
||||
<p class="form-warning">{{index .Text "review_help"}}</p>
|
||||
<div class="article-component-grid">
|
||||
<div class="article-component"><label>Text<select id="article-text" data-review="article-text-review">{{range .Extents}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label><label data-review-wrap="article-text-review">{{index .Text "field_review"}}<select id="article-text-review">{{range .Reviews}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label></div>
|
||||
<div class="article-component"><label>{{index .Text "fact_activities"}} / Recherche<select id="article-research" data-review="article-research-review">{{range .Extents}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label><label data-review-wrap="article-research-review">{{index .Text "field_review"}}<select id="article-research-review">{{range .Reviews}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label></div>
|
||||
<div class="article-component"><label>Cover / {{index .Text "fact_component"}}<select id="article-cover-image" data-review="article-cover-image-review">{{range .Extents}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label><label data-review-wrap="article-cover-image-review">{{index .Text "field_review"}}<select id="article-cover-image-review">{{range .Reviews}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label></div>
|
||||
<div class="article-component"><label>Images<select id="article-image" data-review="article-image-review">{{range .Extents}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label><label data-review-wrap="article-image-review">{{index .Text "field_review"}}<select id="article-image-review">{{range .Reviews}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label></div>
|
||||
<div class="article-component"><label>Audio<select id="article-audio" data-review="article-audio-review">{{range .Extents}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label><label data-review-wrap="article-audio-review">{{index .Text "field_review"}}<select id="article-audio-review">{{range .Reviews}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label></div>
|
||||
<div class="article-component"><label>Video<select id="article-video" data-review="article-video-review">{{range .Extents}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label><label data-review-wrap="article-video-review">{{index .Text "field_review"}}<select id="article-video-review">{{range .Reviews}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label></div>
|
||||
<div class="article-component"><label>Translation<select id="article-translation" data-review="article-translation-review">{{range .Extents}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label><label data-review-wrap="article-translation-review">{{index .Text "field_review"}}<select id="article-translation-review">{{range .Reviews}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label></div>
|
||||
<div class="article-component"><label>Code<select id="article-code" data-review="article-code-review">{{range .Extents}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label><label data-review-wrap="article-code-review">{{index .Text "field_review"}}<select id="article-code-review">{{range .Reviews}}<option value="{{.Value}}" {{if eq .Value "none"}}selected{{end}}>{{.Label}}</option>{{end}}</select></label></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="legal-context-builder wide">
|
||||
<legend>{{index .Text "legal_context_legend"}}</legend>
|
||||
<p>{{index .Text "legal_context_help"}}</p>
|
||||
<label class="check-row"><input id="legal-deepfake" type="checkbox" value="deepfake"><span><strong>{{index .Text "legal_deepfake"}}</strong><small>{{index .Text "legal_deepfake_help"}}</small></span></label>
|
||||
<label class="check-row"><input id="legal-public-interest" type="checkbox" value="publicInterestText"><span><strong>{{index .Text "legal_public_interest"}}</strong><small>{{index .Text "legal_public_interest_help"}}</small></span></label>
|
||||
<label class="check-row"><input id="legal-creative" type="checkbox" value="artisticCreativeSatiricalFictional"><span><strong>{{index .Text "legal_creative"}}</strong><small>{{index .Text "legal_creative_help"}}</small></span></label>
|
||||
<label class="check-row"><input id="legal-other-voluntary" type="checkbox" value="otherVoluntary"><span><strong>{{index .Text "legal_other_voluntary"}}</strong></span></label>
|
||||
|
||||
<fieldset id="legal-context-builder" class="legal-context-builder form-step wide">
|
||||
<legend>{{index .Text "generator_step_legal"}}</legend>
|
||||
<p>{{index .Text "legal_scope_intro"}}</p>
|
||||
<div class="form-subgrid">
|
||||
<label>{{index .Text "field_legal_role"}} <span class="field-badge required">{{index .Text "field_required"}}</span>
|
||||
<select id="legal-role" name="legalRole" required>
|
||||
<option value="">{{index .Text "select_please_choose"}}</option>
|
||||
<option value="deployer">{{index .Text "legal_role_deployer"}}</option>
|
||||
<option value="provider">{{index .Text "legal_role_provider"}}</option>
|
||||
<option value="both">{{index .Text "legal_role_both"}}</option>
|
||||
<option value="unsure">{{index .Text "legal_role_unsure"}}</option>
|
||||
</select>
|
||||
<details class="field-help"><summary>?</summary><span>{{index .Text "legal_role_help"}}</span></details>
|
||||
</label>
|
||||
<label>{{index .Text "field_use_context"}} <span class="field-badge required">{{index .Text "field_required"}}</span>
|
||||
<select id="use-context" name="useContext" required>
|
||||
<option value="">{{index .Text "select_please_choose"}}</option>
|
||||
<option value="professional">{{index .Text "use_context_professional"}}</option>
|
||||
<option value="personalNonProfessional">{{index .Text "use_context_personalNonProfessional"}}</option>
|
||||
<option value="unsure">{{index .Text "use_context_unsure"}}</option>
|
||||
</select>
|
||||
<details class="field-help"><summary>?</summary><span>{{index .Text "use_context_help"}}</span></details>
|
||||
</label>
|
||||
</div>
|
||||
<div class="legal-category-list">
|
||||
<div id="legal-deepfake-row" class="legal-question" hidden>
|
||||
<label><strong>{{index .Text "legal_deepfake"}}</strong> <span class="field-badge required">{{index .Text "field_required"}}</span>
|
||||
<small>{{index .Text "legal_deepfake_help"}}</small>
|
||||
<select id="legal-deepfake" name="deepfakeAssessment">
|
||||
<option value="">{{index .Text "select_please_choose"}}</option>
|
||||
<option value="yes">{{index .Text "assessment_yes"}}</option>
|
||||
<option value="no">{{index .Text "assessment_no"}}</option>
|
||||
<option value="unsure">{{index .Text "assessment_unsure"}}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div id="legal-public-interest-row" class="legal-question" hidden>
|
||||
<label><strong>{{index .Text "legal_public_interest"}}</strong> <span class="field-badge required">{{index .Text "field_required"}}</span>
|
||||
<small>{{index .Text "legal_public_interest_help"}}</small>
|
||||
<select id="legal-public-interest" name="publicInterestAssessment">
|
||||
<option value="">{{index .Text "select_please_choose"}}</option>
|
||||
<option value="yes">{{index .Text "assessment_yes"}}</option>
|
||||
<option value="no">{{index .Text "assessment_no"}}</option>
|
||||
<option value="unsure">{{index .Text "assessment_unsure"}}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<label id="legal-other-voluntary-row" class="check-row" hidden><input id="legal-other-voluntary" type="checkbox" value="otherVoluntary"><span><strong>{{index .Text "legal_other_voluntary"}}</strong></span></label>
|
||||
</div>
|
||||
<div id="law-enforcement-fields" class="conditional-panel wide" hidden>
|
||||
<h3>{{index .Text "law_enforcement_title"}}</h3>
|
||||
<label>{{index .Text "field_law_enforcement_authorization"}} <span class="field-badge required">{{index .Text "field_required"}}</span>
|
||||
<select id="law-enforcement-authorization" name="lawEnforcementAuthorization">
|
||||
<option value="">{{index .Text "select_please_choose"}}</option>
|
||||
<option value="no">{{index .Text "law_enforcement_no"}}</option>
|
||||
<option value="yes">{{index .Text "law_enforcement_yes"}}</option>
|
||||
<option value="unsure">{{index .Text "law_enforcement_unsure"}}</option>
|
||||
</select>
|
||||
<details class="field-help"><summary>?</summary><span>{{index .Text "law_enforcement_help"}}</span></details>
|
||||
</label>
|
||||
</div>
|
||||
<div id="legal-creative-row" class="legal-question conditional-block" hidden>
|
||||
<label><strong>{{index .Text "legal_creative"}}</strong> <span class="field-badge required">{{index .Text "field_required"}}</span>
|
||||
<small>{{index .Text "legal_creative_help"}}</small>
|
||||
<select id="legal-creative" name="creativeWorkAssessment">
|
||||
<option value="">{{index .Text "select_please_choose"}}</option>
|
||||
<option value="yes">{{index .Text "assessment_yes"}}</option>
|
||||
<option value="no">{{index .Text "assessment_no"}}</option>
|
||||
<option value="unsure">{{index .Text "assessment_unsure"}}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div id="legal-timing-fields" class="form-subgrid conditional-block" hidden>
|
||||
<label class="wide">{{index .Text "field_output_date"}} <span class="field-badge optional">{{index .Text "field_optional"}}</span>
|
||||
<input id="output-date" name="outputDate" type="date">
|
||||
<details class="field-help"><summary>?</summary><span>{{index .Text "output_date_help"}}</span></details>
|
||||
</label>
|
||||
</div>
|
||||
<div id="legal-assessment" class="legal-assessment" role="status" aria-live="polite"></div>
|
||||
<div id="validation-messages" class="validation-messages" aria-live="polite"></div>
|
||||
<details class="legal-consequences">
|
||||
<summary>{{index .Text "legal_enforcement_title"}}</summary>
|
||||
<p>{{index .Text "legal_enforcement_help"}}</p>
|
||||
<p class="form-warning">{{index .Text "legal_disclaimer"}}</p>
|
||||
</details>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="accountability-fields" class="form-step wide">
|
||||
<legend>{{index .Text "generator_step_accountability"}}</legend>
|
||||
<p>{{index .Text "accountability_intro"}}</p>
|
||||
<div class="form-subgrid">
|
||||
<label class="wide">{{index .Text "field_subject"}} <span class="field-badge optional">{{index .Text "field_optional"}}</span>
|
||||
<input id="subject" name="subject" type="url" placeholder="https://example.org/article">
|
||||
</label>
|
||||
<div id="author-fields" class="form-subgrid wide" hidden>
|
||||
<label>{{index .Text "field_author"}} <span class="field-badge optional">{{index .Text "field_optional"}}</span>
|
||||
<input id="author" name="author" maxlength="200" placeholder="Name">
|
||||
<small>{{index .Text "author_help"}}</small>
|
||||
</label>
|
||||
<label id="author-url-field" hidden>{{index .Text "field_author_url"}}
|
||||
<input id="author-url" name="authorUrl" type="url" placeholder="https://example.org/autor">
|
||||
</label>
|
||||
</div>
|
||||
<div id="responsibility-fields" class="conditional-panel wide" hidden>
|
||||
<h3>{{index .Text "editorial_exception_title"}}</h3>
|
||||
<p>{{index .Text "editorial_exception_help"}}</p>
|
||||
<p class="form-warning">{{index .Text "responsibility_required_help"}}</p>
|
||||
<div class="form-subgrid">
|
||||
<label class="wide">{{index .Text "field_responsibility_role"}} <span class="field-badge required">{{index .Text "field_required"}}</span>
|
||||
<select id="responsible-role" name="responsibleRole">
|
||||
<option value="">{{index .Text "responsibility_none"}}</option>
|
||||
<option value="publisher">{{index .Text "responsibility_publisher"}}</option>
|
||||
<option value="other">{{index .Text "responsibility_other"}}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>{{index .Text "field_responsible"}} <span class="field-badge required">{{index .Text "field_required"}}</span>
|
||||
<input id="responsible" name="responsible" maxlength="200" placeholder="{{index .Text "responsible_placeholder"}}">
|
||||
</label>
|
||||
<label>{{index .Text "field_responsible_url"}} <span class="field-badge optional">{{index .Text "field_optional"}}</span>
|
||||
<input id="responsible-url" name="responsibleUrl" type="url" placeholder="https://example.org/impressum">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="complaint-fields" class="conditional-panel wide" hidden>
|
||||
<h3>{{index .Text "complaints_title"}} <span class="field-badge best-practice">{{index .Text "field_best_practice"}}</span></h3>
|
||||
<p>{{index .Text "complaints_help"}}</p>
|
||||
<div class="form-subgrid">
|
||||
<label>{{index .Text "field_complaint_name"}}
|
||||
<input id="complaint-name" name="complaintName" maxlength="200" placeholder="Redaktion / Ombudsstelle">
|
||||
</label>
|
||||
<label>{{index .Text "field_complaint_email"}}
|
||||
<input id="complaint-email" name="complaintEmail" type="email" placeholder="feedback@example.org">
|
||||
</label>
|
||||
<label class="wide">{{index .Text "field_complaint_url"}}
|
||||
<input id="complaint-url" name="complaintUrl" type="url" placeholder="https://example.org/beschwerde">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="form-step wide">
|
||||
<legend>{{index .Text "generator_step_output"}}</legend>
|
||||
<div class="form-subgrid">
|
||||
<label>{{index .Text "field_language"}}
|
||||
<select id="lang" name="lang">{{range .Languages}}<option value="{{.Code}}" {{if eq $.Lang .Code}}selected{{end}}>{{.Name}}</option>{{end}}</select>
|
||||
</label>
|
||||
<label>{{index .Text "field_theme"}}
|
||||
<select id="theme" name="theme"><option value="mono">{{index .Text "theme_mono"}}</option><option value="color">{{index .Text "theme_color"}}</option><option value="emoji">{{index .Text "theme_emoji"}}</option></select>
|
||||
</label>
|
||||
<label class="wide">{{index .Text "field_assurance"}}
|
||||
<select id="assurance" name="assurance">{{range .Assurances}}<option value="{{.Value}}">{{.Label}}</option>{{end}}</select>
|
||||
<small id="assurance-description">{{index .Text "assurance_selfDeclared_description"}}</small>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<label>{{index .Text "field_language"}}
|
||||
<select id="lang" name="lang">{{range .Languages}}<option value="{{.Code}}" {{if eq $.Lang .Code}}selected{{end}}>{{.Name}}</option>{{end}}</select>
|
||||
</label>
|
||||
<label>{{index .Text "field_theme"}}
|
||||
<select id="theme" name="theme"><option value="mono">{{index .Text "theme_mono"}}</option><option value="color">{{index .Text "theme_color"}}</option><option value="emoji">{{index .Text "theme_emoji"}}</option></select>
|
||||
</label>
|
||||
<label class="wide">{{index .Text "field_assurance"}}
|
||||
<select id="assurance" name="assurance">{{range .Assurances}}<option value="{{.Value}}">{{.Label}}</option>{{end}}</select>
|
||||
<small id="assurance-description">{{index .Text "assurance_selfDeclared_description"}}</small>
|
||||
</label>
|
||||
<label class="wide">{{index .Text "field_subject"}}
|
||||
<input id="subject" name="subject" type="url" placeholder="https://example.org/article">
|
||||
</label>
|
||||
<label class="wide">{{index .Text "field_responsibility_role"}}
|
||||
<select id="responsible-role" name="responsibleRole">
|
||||
<option value="">{{index .Text "responsibility_none"}}</option>
|
||||
<option value="publisher">{{index .Text "responsibility_publisher"}}</option>
|
||||
<option value="other">{{index .Text "responsibility_other"}}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>{{index .Text "field_responsible"}}
|
||||
<input id="responsible" name="responsible" maxlength="200" placeholder="{{index .Text "responsible_placeholder"}}">
|
||||
</label>
|
||||
<label>{{index .Text "field_responsible_url"}}
|
||||
<input id="responsible-url" name="responsibleUrl" type="url" placeholder="https://example.org/impressum">
|
||||
</label>
|
||||
<p class="form-warning wide">{{index .Text "review_legal_text"}}</p>
|
||||
</form>
|
||||
|
||||
@@ -160,7 +289,7 @@
|
||||
<article><p class="eyebrow">HA</p><h2>{{index .Text "api_stateless_title"}}</h2><p>{{index .Text "api_stateless_desc"}}</p></article>
|
||||
</section>
|
||||
</main>
|
||||
<footer><span>AI Usage Disclosure · App 1.6.3 · Schema 1.2 · {{.License.Edition}}</span>{{template "legal-links" .}}</footer>
|
||||
<footer><span>AI Usage Disclosure · App 1.7.0 · Schema 1.3 · {{.License.Edition}}</span>{{template "legal-links" .}}</footer>
|
||||
<script nonce="{{.CSPNonce}}">window.APP_CONFIG={{.AppConfig}};</script>
|
||||
<script src="/static/app.js" defer></script>
|
||||
</body>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<img src="/badge/full.svg?lang={{.Lang}}&theme=color" alt="Full generation badge">
|
||||
<pre><code>{
|
||||
"@type": "AIUsageDeclaration",
|
||||
"schemaVersion": "1.2",
|
||||
"schemaVersion": "1.3",
|
||||
"assurance": "selfDeclared"
|
||||
}</code></pre>
|
||||
</div>
|
||||
@@ -119,7 +119,7 @@
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer><span>{{.Marketing.Footer}} · App 1.6.3 · Schema 1.2 · {{.License.Edition}}</span>{{template "legal-links" .}}</footer>
|
||||
<footer><span>{{.Marketing.Footer}} · App 1.7.0 · Schema 1.3 · {{.License.Edition}}</span>{{template "legal-links" .}}</footer>
|
||||
<script src="/static/marketing.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user