KI-Kennzeichnung: 2 Optionen im Submit-Modal + Labels auf Detailseite

Submit-Modal auf zwei Auswahloptionen reduziert (kein Label vs.
"Ganz oder ueberwiegend mit generativer KI erstellt"); alle vier
Enum-Werte bleiben fuer API/Bestand gueltig und mappen per formOption()
auf die zwei Gruppen. Oeffentliche Detailseite rendert jetzt Text-Label
("KI-generiert: Nach Angaben ...") und Bild-Badge (KI-generiert/
KI-bearbeitet, abgedunkelter Hintergrund) — fehlte dort bisher komplett.
Bildnachweis-Vorschlag ohne "Foto:"-Praefix (stand sonst doppelt).
Einreich-Toast + FAQ: automatische Pruefung in Minuten statt 24 h.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin Adametz 2026-07-10 16:51:23 +00:00
parent c8836564e9
commit dc2a3443c1
13 changed files with 172 additions and 50 deletions

View file

@ -126,14 +126,16 @@
<p class="m-0 text-[12.5px] leading-[1.6] text-[color:var(--color-ink-2)]">
{{ __('Wie wurde der Text dieser Pressemitteilung erstellt? Diese Angabe ist gesetzlich verpflichtend (Art. 50 KI-VO).') }}
</p>
{{-- Zwei Gruppen (Entscheidung 10.07.2026): Option 1 = kein
Label, Option 2 = Hinweis „KI-generiert" am Pressetext. --}}
<flux:radio.group wire:model="aiTextDeclaration">
@foreach (\App\Enums\AiTextDeclaration::cases() as $aiOption)
@foreach (\App\Enums\AiTextDeclaration::formCases() as $aiOption)
<flux:radio :value="$aiOption->value" :label="$aiOption->label()" />
@endforeach
</flux:radio.group>
<flux:error name="aiTextDeclaration" />
<p class="m-0 pt-2 border-t border-[color:var(--color-bg-rule)] text-[11.5px] leading-[1.5] text-[color:var(--color-ink-3)]">
{{ __('Bei überwiegend oder vollständig KI-generierten Texten erscheint an der Veröffentlichung ein neutraler Hinweis in Ihrer Stimme („Nach Angaben des einreichenden Unternehmens …"). KI-generierte oder KI-bearbeitete Bilder kennzeichnen Sie direkt beim Bild-Upload.') }}
{{ __('Bei der ersten Option erscheint kein Label an der Veröffentlichung. Bei der zweiten Option wird der Pressetext mit dem Hinweis „KI-generiert" in Ihrer Stimme gekennzeichnet („Nach Angaben des einreichenden Unternehmens …"). KI-generierte oder KI-bearbeitete Bilder kennzeichnen Sie direkt beim Bild-Upload.') }}
</p>
</div>
</article>

View file

@ -198,13 +198,15 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
@php
$adminTitleImage = $pr->images->sortByDesc('is_preview')->first();
@endphp
@if ($adminTitleImage && ($adminTitleImage->copyright || $adminTitleImage->is_ai_generated))
@if ($adminTitleImage && ($adminTitleImage->copyright || $adminTitleImage->is_ai_generated || $adminTitleImage->ai_declaration?->publicLabel()))
{{-- Bildnachweis + KI-Kennzeichnung (Art. 50 EU AI Act) --}}
<div class="flex flex-wrap items-center gap-2 border-t border-[color:var(--color-bg-rule)] px-5 py-2.5 text-[12px] text-[color:var(--color-ink-3)]">
@if ($adminTitleImage->is_ai_generated)
<span class="badge hub">{{ __('KI-generiert') }}</span>
@if ($aiImageBadge = $adminTitleImage->ai_declaration?->publicLabel() ?? ($adminTitleImage->is_ai_generated ? __('KI-generiert') : null))
<span class="badge hub">{{ $aiImageBadge }}</span>
@endif
@if ($adminTitleImage->copyright)
<span>{{ $adminTitleImage->copyright }}</span>
@endif
<span>{{ $adminTitleImage->copyright ?? __('Bild: KI-generiert') }}</span>
</div>
@endif
@endif

View file

@ -101,7 +101,7 @@ new class extends Component {
$this->resetErrorBag('newLicenseDetail');
// Automatisch erzeugte Vorschläge weichen dem Vorschlag des neuen Typs.
if ($this->newCopyright === $this->autoCopyright || str_starts_with($this->newCopyright, __('Bild: KI-generiert'))) {
if ($this->newCopyright === $this->autoCopyright || str_starts_with($this->newCopyright, __('KI-generiert'))) {
$this->newCopyright = '';
$this->autoCopyright = '';
}
@ -135,7 +135,9 @@ new class extends Component {
return;
}
$this->autoCopyright = filled($this->newAuthor) ? __('Foto: :author', ['author' => trim($this->newAuthor)]) : '';
// Ohne „Foto:"-Präfix — die Veröffentlichungsseite stellt es dem
// Bildnachweis bereits selbst voran (sonst stünde dort „Foto: Foto: …").
$this->autoCopyright = filled($this->newAuthor) ? trim($this->newAuthor) : '';
$this->newCopyright = $this->autoCopyright;
}
@ -154,11 +156,11 @@ new class extends Component {
return;
}
if (filled($this->newCopyright) && !str_starts_with($this->newCopyright, __('Bild: KI-generiert'))) {
if (filled($this->newCopyright) && !str_starts_with($this->newCopyright, __('KI-generiert'))) {
return;
}
$this->newCopyright = filled($this->newLicenseDetail) ? __('Bild: KI-generiert (:tool)', ['tool' => trim($this->newLicenseDetail)]) : __('Bild: KI-generiert');
$this->newCopyright = filled($this->newLicenseDetail) ? __('KI-generiert (:tool)', ['tool' => trim($this->newLicenseDetail)]) : __('KI-generiert');
}
public function closeUploadForm(): void
@ -214,7 +216,7 @@ new class extends Component {
]),
],
[
'newCopyright.required' => __('Bitte einen öffentlichen Bildnachweis angeben, z. B. Foto: Max Mustermann / Beispiel GmbH.'),
'newCopyright.required' => __('Bitte einen öffentlichen Bildnachweis angeben, z. B. Max Mustermann / Beispiel GmbH.'),
'newAuthor.required' => $isAiGenerated ? __('Bitte angeben, wer für die Erstellung verantwortlich ist (Person oder Firma).') : __('Bitte Urheber, Fotograf oder Rechteinhaber angeben.'),
'newLicenseType.required' => __('Bitte einen Lizenztyp wählen.'),
'newLicenseDetail.required' => $isAiGenerated ? __('Bitte das verwendete KI-Tool angeben, z. B. Midjourney, Gemini, ChatGPT, DALL·E, Adobe Firefly.') : __('Bitte die Lizenz genauer angeben.'),
@ -624,8 +626,8 @@ new class extends Component {
<flux:input wire:model="newCopyright" :label="__('Öffentlicher Bildnachweis')"
:badge="__('Pflicht')"
:description="__('Wird öffentlich unter dem Bild angezeigt — Vorschlag anpassbar.')"
:placeholder="__('z. B. Foto: Max Mustermann / Beispiel GmbH')" required />
:description="__('Wird öffentlich unter dem Bild angezeigt („Foto: …") — Vorschlag anpassbar.')"
:placeholder="__('z. B. Max Mustermann / Beispiel GmbH')" required />
</section>
{{-- ===== Schritt 3 · Personen & Rechte Dritter ===== --}}

View file

@ -341,7 +341,7 @@ new #[Layout('components.layouts.app'), Title('Neue Pressemitteilung')] class ex
Flux::toast(
heading: __('Eingereicht'),
text: __('Die Inhaltsprüfung ist typischerweise innerhalb von 24 h abgeschlossen.'),
text: __('Die automatische Inhaltsprüfung ist in der Regel innerhalb weniger Minuten abgeschlossen.'),
variant: 'success',
);
} else {

View file

@ -125,9 +125,10 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
$this->placeholderVariant = PressReleasePlaceholder::fromValueOrDefault($pr->placeholder_variant?->value)->value;
// KI-Grad vorbelegen (bereits deklariert); die verbindliche
// Bestätigung muss bei jedem Submit erneut gesetzt werden.
$this->aiTextDeclaration = $pr->ai_text_declaration?->value;
// KI-Grad vorbelegen (bereits deklariert), gemappt auf die zwei
// Formular-Optionen; die verbindliche Bestätigung muss bei jedem
// Submit erneut gesetzt werden.
$this->aiTextDeclaration = $pr->ai_text_declaration?->formOption()->value;
}
#[On('placeholder-selected')]
@ -337,7 +338,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung bearbeiten')] cl
Flux::toast(
heading: __('Eingereicht'),
text: __('Die Inhaltsprüfung ist typischerweise innerhalb von 24 h abgeschlossen.'),
text: __('Die automatische Inhaltsprüfung ist in der Regel innerhalb weniger Minuten abgeschlossen.'),
variant: 'success',
);
} else {

View file

@ -42,6 +42,9 @@
$imageUrl = $image?->variantUrl('detail') ?? $image?->variantUrl('card') ?? $image?->url();
$imageCaption = $image?->description ?: $image?->title;
$imageCredit = $image?->copyright ?: $image?->author;
// KI-Bildkennzeichnung (Art. 50 KI-VO): Badge direkt am Bild
// („KI-generiert" / „KI-bearbeitet"), null bei Originalfotos.
$imageAiBadge = $image?->ai_declaration?->publicLabel();
// Ohne Pressefoto: die im User-Panel gewählte Platzhalter-Variante.
// Null bei Altbeständen ohne Variante — dann generischer Hero-Fallback.
@ -306,6 +309,13 @@
<div class="relative flex items-end p-[18px] bg-bg-dark overflow-hidden" style="aspect-ratio:21/9;">
<img src="{{ $imageUrl }}" alt="{{ $imageCaption ?: $release->title }}"
class="absolute inset-0 h-full w-full object-cover" loading="eager">
@if ($imageAiBadge)
{{-- KI-Badge am Bild (Art. 50 KI-VO): kräftig abgedunkelter
Hintergrund, damit es auch auf hellen Bildern lesbar bleibt. --}}
<span class="relative text-[9.5px] font-bold tracking-[0.18em] uppercase text-white px-2.5 py-[3px] border border-white/60 font-mono bg-black/75 shadow-sm">
{{ $imageAiBadge }}
</span>
@endif
</div>
@if ($imageCaption || $imageCredit)
<div class="text-[11.5px] text-ink-3 mt-2.5 italic max-w-[720px]">
@ -319,6 +329,19 @@
<section class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 pt-10 grid grid-cols-1 lg:grid-cols-[1fr_280px] gap-10 lg:gap-14">
{{-- Body --}}
<div>
{{-- KI-Label (Art. 50 KI-VO) in Einreicher-Stimme am Anfang
des Inhalts; neutrales ℹ-Icon, kein Flux auf Web-Domains. --}}
@if ($aiTextLabel = $release->aiTextPublicLabel())
<div class="flex items-start gap-2 border border-bg-rule-strong bg-bg-elev px-3 py-2.5 mb-6 max-w-[680px] text-[12px] leading-[1.5] text-ink-3">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" aria-hidden="true" class="shrink-0 mt-0.5">
<circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="1.8" fill="none" />
<path d="M12 11v5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" />
<circle cx="12" cy="8" r="1.1" fill="currentColor" />
</svg>
<span>{{ $aiTextLabel }}</span>
</div>
@endif
<div class="pm-body max-w-[680px]">
{!! $bodyHtml !!}
</div>