---
name: ta-12-content-create-image
description: "Phase 4: Generate article hero images using Gemini 2.5 Flash Image API with article_type-aware prompts and image_alt integration. Supports single article, cluster batch, and full batch mode. Persists image status in content plan."
---

# Artikel-Bild generieren

Generiert Hero-Images für Ratgeber-Artikel via Gemini 2.5 Flash Image API. Nutzt `image_alt` und `article_type` aus dem Frontmatter für präzisere Prompts. Unterstützt Einzel-, Cluster- und Batch-Modus.

## Usage

```
/ta-12-content-create-image
/ta-12-content-create-image zeiterfassung-kleinbetriebe-pflicht
/ta-12-content-create-image --cluster kleinbetriebe
/ta-12-content-create-image --all
```

## Workflow

### Step 0: Load Config

Read `data/project-config.json` and extract:
- `gemini_style_prefix` — for image generation prompt
- `content_dir` — for article file paths
- `ratgeber_base_url` — for image paths

If `project-config.json` doesn't exist, abort: "Bitte zuerst data/project-config.json anlegen."

### Step 0b: Sharp prüfen

Prüfe ob Sharp im Projekt verfügbar ist:

```bash
node -e "try { require('sharp'); console.log('SHARP_OK'); } catch(e) { console.log('SHARP_MISSING'); }"
```

Falls `SHARP_MISSING`:
```
Sharp ist nicht installiert. Bitte installieren:
  npm install sharp
  # oder
  yarn add sharp
```

### Step 1: Artikel identifizieren

**Einzelmodus** (Argument = slug):
- `{slug}`: z.B. "zeiterfassung-kleinbetriebe-pflicht"
- Datei: `{config.content_dir}/{slug}.md`

**Cluster-Batch** (Argument = `--cluster {id}`):
- Lade Content-Plan `data/content-plans/{cluster-id}.json`
- Sammle alle Artikel (inkl. Pillar) deren Bild-Datei noch nicht existiert
- Zeige Liste:
  ```
  Cluster "[name]": [X] Artikel ohne Bild
    1. [slug-1] ([article_type])
    2. [slug-2] ([article_type])
    ...
  Alle generieren? (ja/nein)
  ```

**Full-Batch** (Argument = `--all`):
- Lese alle Content-Pläne aus `data/content-plans/`
- Sammle alle Artikel über alle Cluster deren Bild-Datei noch nicht existiert
- Zeige Zusammenfassung:
  ```
  [X] Artikel ohne Bild über [Y] Cluster:
    Cluster "[name-1]": [n] Artikel
    Cluster "[name-2]": [n] Artikel
    ...
  Alle generieren? (ja/nein)
  ```

**Kein Argument:**
- Lese alle Ratgeber aus `{config.content_dir}/`
- Zeige Artikel mit leerem `image` Feld oder ohne existierende Bild-Datei
- Frage welchen Artikel bebildern

### Step 2: Artikel lesen

Lese die Frontmatter und den Body des Artikels:
- `title`: Artikeltitel
- `description`: Meta-Beschreibung
- `primary_keyword`: Ziel-Keyword
- `image`: Aktueller Bild-Pfad
- `image_alt`: Beschreibender Alt-Text (von ta-9/ta-10 generiert, enthält primary_keyword)
- `article_type`: Artikeltyp (cluster, checklist, statistics, comparison, glossary, pillar)

**Prüfen:**
- Falls `image` bereits gesetzt ist UND Bild-Datei existiert: Frage ob überschreiben
- Falls Artikel-Datei nicht existiert: Abbruch mit Hinweis

### Step 3: API-Key prüfen

Prüfe ob `GEMINI_API_KEY` als Umgebungsvariable gesetzt ist:

```bash
echo ${GEMINI_API_KEY:+KEY_SET}
```

Falls leer, zeige Anleitung:
```
GEMINI_API_KEY nicht gesetzt.
1. Gehe zu https://aistudio.google.com/apikey
2. Erstelle einen API-Key
3. Setze: export GEMINI_API_KEY="dein-key-hier"
```

### Step 4: Bildprompt generieren

Erstelle einen Bildprompt aus vier Teilen:

**Teil 1 — Stil-Prefix (aus Config):**
```
{config.gemini_style_prefix}
```

**Teil 2 — article_type Stil-Hint:**

Ergänze den Stil-Prefix mit article_type-spezifischen visuellen Hinweisen:

| article_type | Stil-Hint (zum Prefix hinzufügen) |
|-------------|----------------------------------|
| `cluster` | *(kein Zusatz — Standard-Stil)* |
| `pillar` | `comprehensive overview composition, multiple connected elements,` |
| `checklist` | `sequential numbered steps visualization, process flow,` |
| `statistics` | `data visualization elements, charts, graphs, percentage indicators,` |
| `comparison` | `side-by-side comparison layout, versus arrangement,` |
| `glossary` | `encyclopedia-style knowledge visualization, interconnected terms,` |

**Teil 3 — Szene (aus image_alt + Artikelinhalt):**

Nutze `image_alt` als **primäre Szene-Beschreibung**. Der `image_alt` wurde von ta-9/ta-10 generiert und enthält die semantisch präziseste Beschreibung des gewünschten Bildinhalts.

Formulierungsregel:
1. Übersetze `image_alt` ins Englische (Gemini-Prompts sind auf Englisch effektiver)
2. Ergänze mit konkreten visuellen Elementen abgeleitet aus `title` und `primary_keyword`
3. Wende die Fokus-Regeln an (siehe unten)

Falls `image_alt` leer oder nicht vorhanden: Fallback auf `title` + `primary_keyword` wie bisher.

**Fokus-Regeln für die Szene:**
- Bestimme EIN Hauptelement das den Kern des Artikels visuell repräsentiert
- Das Hauptelement steht im Zentrum und ist deutlich größer als Nebenelemente
- Verwende "a large [Hauptelement] in the center" als Formulierung
- Nebenelemente als Kontext ("on the side", "in the background", "small")
- Maximal 1 Hauptelement + 2-3 Nebenelemente

Beispiele:

| image_alt | article_type | Szene |
|-----------|-------------|-------|
| "Zeiterfassung Pflicht für Kleinbetriebe - gesetzliche Regelungen" | cluster | "a large legal paragraph symbol in the center, surrounded by an official document with a red seal, a gavel, and a small digital time tracking terminal" |
| "Vergleich Zeiterfassungsmethoden - digital vs. manuell" | comparison | "two time tracking methods side by side in the center: a paper timesheet on the left and a tablet with an app on the right, a small clock above" |
| "Checkliste Zeiterfassung einführen - 7 Schritte" | checklist | "a large checklist with numbered steps in the center, a pen checking off items, a small digital time clock and office building in the background" |
| "Statistiken Zeiterfassung Deutschland - Verbreitung und Trends" | statistics | "a large bar chart in the center showing upward trend, surrounded by percentage indicators, a small map of Germany and a clock in the background" |
| "Glossar Arbeitszeitmodelle - Begriffe erklärt" | glossary | "a large open book in the center with highlighted terms, surrounded by small icons representing different work time models: clock, calendar, home office symbol" |

**Teil 4 — Negativ-Suffix (fest — immer anhängen):**
```
, no text overlays, no watermarks, no logos, no human faces, no photorealistic elements
```

### Step 5: Bild generieren

**WICHTIG:** Die API-Response ist sehr groß (1+ MB Base64). Niemals in einer Shell-Variable speichern! Immer direkt in eine Datei schreiben mit `-o`.

Stelle sicher dass das Scratchpad-Verzeichnis existiert:

```bash
SCRATCHPAD="/private/tmp/claude-$(id -u)/scratchpad/$(uuidgen)"
mkdir -p "$SCRATCHPAD"
```

API-Call via curl — Response direkt in Datei speichern:

```bash
PROMPT="[zusammengesetzter prompt aus Step 4]"

curl -s "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent?key=$GEMINI_API_KEY" \
  -H "Content-Type: application/json" \
  -o "$SCRATCHPAD/response.json" \
  -d "{
    \"contents\": [{\"parts\": [{\"text\": \"$PROMPT\"}]}],
    \"generationConfig\": {
      \"responseModalities\": [\"IMAGE\"],
      \"imageConfig\": {\"aspectRatio\": \"16:9\"}
    }
  }"
```

**Hinweis:** `imageConfig` unterstützt NUR `aspectRatio`. Keine weiteren Parameter wie `imageSize` verwenden — die API lehnt unbekannte Parameter ab.

### Step 6: Bild speichern

Base64-Daten mit Python aus der Response-Datei extrahieren und dekodieren:

```bash
python3 << 'PYEOF'
import json, base64

with open("$SCRATCHPAD/response.json") as f:
    response = json.load(f)

if "error" in response:
    print(f"API ERROR: {response['error']['message']}")
    exit(1)

part = response["candidates"][0]["content"]["parts"][0]
mime = part["inlineData"]["mimeType"]
data = part["inlineData"]["data"]
print(f"MIME Type: {mime}")

raw_bytes = base64.b64decode(data)
with open("$SCRATCHPAD/image_raw.png", "wb") as f:
    f.write(raw_bytes)
print(f"Image saved: {len(raw_bytes)} bytes ({len(raw_bytes)/1024:.1f} KB)")
PYEOF
```

Dann mit Sharp nach WebP konvertieren:

```bash
node -e "
const sharp = require('sharp');
sharp('$SCRATCHPAD/image_raw.png')
  .resize(1024, 576, { fit: 'cover' })
  .webp({ quality: 85 })
  .toFile('public/images/ratgeber/{slug}.webp')
  .then(info => {
    console.log('WebP saved: ' + (info.size / 1024).toFixed(1) + ' KB');
    console.log('Dimensions: ' + info.width + 'x' + info.height);
  })
  .catch(err => { console.error('Error:', err.message); process.exit(1); });
"
```

Ziel-Pfad: `public/images/ratgeber/{slug}.webp`

### Step 6b: Scratchpad aufräumen

Nach erfolgreichem WebP-Export das Scratchpad-Verzeichnis löschen:

```bash
rm -rf "$SCRATCHPAD"
```

Falls Fehler beim Löschen: Ignorieren (Temp-Verzeichnis wird vom OS gereinigt).

### Step 7: Frontmatter aktualisieren

Ersetze in `{config.content_dir}/{slug}.md`:

```yaml
image: ""
```

durch:

```yaml
image: "/images/ratgeber/{slug}.webp"
```

Falls `image` bereits einen Pfad hat (Überschreiben-Modus), ersetze den bestehenden Pfad.

Verwende das Edit-Tool für diese Änderung.

### Step 8: Image-Status im Content-Plan persistieren

Aktualisiere `data/content-plans/{cluster-id}.json` für den Artikel:

```json
{
  "image_generated": true,
  "image_date": "2026-02-24"
}
```

Diese Daten ermöglichen:
- **ta-15:** Image-Status im Audit prüfen ohne Datei-Existenz zu checken
- **ta-12 Re-Run:** Erkennen ob Bild bereits generiert wurde und wann
- **ta-14:** Erkennen ob nach Refresh ein neues Bild nötig ist (Content geändert, Bild veraltet)

### Step 9: Report

**Einzelmodus:**
```
Artikel-Bild generiert:

Artikel: [Titel]
Slug: [slug]
article_type: [type]
image_alt: [image_alt aus Frontmatter]
Prompt: [vollständiger Prompt]
Datei: public/images/ratgeber/[slug].webp
Dateigröße: [X] KB
Dimensionen: 1024x576

Content-Plan aktualisiert: image_generated, image_date

Nächste Schritte:
- Bild visuell prüfen (Read-Tool auf die WebP-Datei)
- yarn build ausführen um das Bild in die Site zu integrieren
```

**Batch-Modus:**
```
Bilder generiert für [Cluster/Alle]:

Erfolgreich: [X] von [Y] Artikeln
  1. [slug-1] ([article_type]) -> [size] KB
  2. [slug-2] ([article_type]) -> [size] KB
  ...

Fehlgeschlagen: [Z] Artikel
  - [slug-a]: [Fehlergrund]

Content-Pläne aktualisiert: image_generated, image_date

Nächste Schritte:
- Bilder visuell prüfen
- yarn build ausführen
```

### Batch-Verhalten

Im Batch-Modus (`--cluster` oder `--all`):

1. **Reihenfolge:** Artikel werden in Content-Plan-Reihenfolge verarbeitet
2. **Pause:** 5 Sekunden Pause zwischen API-Calls (Rate-Limit-Schutz)
3. **Fehlertoleranz:** Bei API-Fehler (429, 5xx) wird der Artikel übersprungen und im Report als fehlgeschlagen markiert. Die restlichen Artikel werden weiter verarbeitet.
4. **Überspringen:** Artikel mit existierender Bild-Datei werden übersprungen (kein Überschreiben im Batch)
5. **Bestätigung:** Vor dem Start Gesamtzahl anzeigen und Bestätigung einholen
6. **Fortschritt:** Nach jedem Bild den aktuellen Stand anzeigen: `[X/Y] {slug} -> OK ({size} KB)`

### Re-Run-Verhalten

- **Einzelmodus:** Wenn Bild-Datei bereits existiert, wird der User gefragt ob überschrieben werden soll
- **Batch-Modus:** Artikel mit existierender Bild-Datei werden **übersprungen** (kein Überschreiben im Batch)
- Bei Überschreiben: Neues Bild wird generiert, altes wird ersetzt, Frontmatter `image` bleibt unverändert (gleicher Pfad)
- `image_generated` und `image_date` im Content-Plan werden bei jedem erfolgreichen Lauf aktualisiert
- Sinnvoll nach Content-Änderungen: Wenn sich `image_alt` oder `article_type` geändert haben, ergibt ein Re-Run mit Überschreiben ein passenderes Bild

## Fehlerbehandlung

| Fehler | Aktion |
|--------|--------|
| Config fehlt | Abbruch: "Bitte zuerst data/project-config.json anlegen." |
| Artikel nicht gefunden | Abbruch, verfügbare Slugs auflisten |
| GEMINI_API_KEY fehlt | Anleitung zur Key-Erstellung anzeigen |
| Sharp nicht installiert | Abbruch: "Sharp nicht installiert. Bitte `npm install sharp` ausführen." |
| API Rate Limit (429) | Einzelmodus: "Bitte 1 Minute warten und erneut versuchen." Batch: Artikel überspringen, im Report markieren. |
| API Auth Error (401/403) | Abbruch: "API-Key ungültig oder abgelaufen." |
| API Invalid Argument | Prüfen ob unbekannte Parameter in imageConfig (nur aspectRatio erlaubt) |
| Bild bereits vorhanden | Einzelmodus: Fragen ob überschreiben. Batch: Überspringen. |
| Argument list too long | Response MUSS in Datei gespeichert werden (-o Flag), nie in Shell-Variable |
| Leere API-Response | "Bildgenerierung fehlgeschlagen. Prompt ggf. anpassen." |
| image_alt fehlt im Frontmatter | Info: "Kein image_alt vorhanden. Fallback auf title + primary_keyword für Prompt." |
| article_type unbekannt | Info: "article_type '{type}' nicht erkannt. Standard-Stil verwenden." |
| Content-Plan nicht gefunden | Warnung: "Content-Plan für '{id}' nicht gefunden. image_generated nicht persistiert." |
| Cluster-ID ungültig (Batch) | Verfügbare Cluster-IDs auflisten |

## Bildspezifikationen

- **Format:** WebP (via Sharp-Konvertierung)
- **Aspect Ratio:** 16:9
- **Auflösung:** 1024x576 (Sharp resize)
- **Qualität:** WebP q85
- **Zielgröße:** < 100 KB
- **Hintergrund:** Off-white (#f4f4f5)
- **Ablageort:** `public/images/ratgeber/`
- **Namensschema:** `{slug}.webp` (identisch mit Artikel-Slug)
- **Konvertierung:** Sharp (muss im Projekt installiert sein)
