140 lines
6.1 KiB
Markdown
140 lines
6.1 KiB
Markdown
# BrandLoop (videogen)
|
||
|
||
KI-Video-Generierung mit lernender Brand-Knowledge-Base (Elo-Scoring über Attribute).
|
||
|
||
- **Prototyp:** [prototyp-app.html](prototyp-app.html) – statische Preview unter `videogen.project.webklar.com`
|
||
- **Desktop-Ansicht:** Design-Handoff für die Desktop-Adaption in [design/desktop-ansicht/](design/desktop-ansicht/) (Spezifikation: [README.md](design/desktop-ansicht/README.md))
|
||
- **Backend:** selbst gehostetes Appwrite (`https://appwrite.webklar.com/v1`), Projekt **BrandLoop** `6a5cee34002bb8360c34`, Datenbank `brandloop`
|
||
|
||
## App (`client/`)
|
||
|
||
Expo / React Native, eine Codebase für Web und nativ. Stand: Gerüst aus E1 –
|
||
drei Tabs, sonst leer.
|
||
|
||
```bash
|
||
npm --prefix client run web
|
||
```
|
||
|
||
## Bildgenerierung
|
||
|
||
Zwei Job-Typen arbeitet der Dispatcher ab – **serverseitig**, weil der
|
||
Anbieter-Schlüssel nicht ins Client-Bundle darf:
|
||
|
||
| Job | Entsteht bei | Ergebnis |
|
||
|---|---|---|
|
||
| `modell_gen` | Modell anlegen (Person, Produkt, Kulisse) | 4 Varianten als Entwurfs-Versionen, eine wird freigegeben |
|
||
| `bild_gen` | Post erstellen | Bilderkette in `post_images` |
|
||
|
||
**Logo-Sonderfall:** Wer eine Datei hochlädt, bekommt sie unverändert als
|
||
freigegebene Version – ab da wird sie nie neu generiert. Wer keine hat, bekommt
|
||
Entwürfe. Sobald etwas freigegeben ist, weist der Dispatcher jede weitere
|
||
Generierung auf dieses Asset ab. So bleibt die Brand-Regel gewahrt, ohne Marken
|
||
ohne Logo auszusperren.
|
||
|
||
```bash
|
||
node scripts/job-dispatcher.mjs
|
||
```
|
||
|
||
Ohne Angabe läuft er auf `auto` und probiert der Reihe nach **Ark → OpenRouter →
|
||
Platzhalter**. Ark steht vorn, weil Seedream mit 0,045–0,090 $ je Bild günstiger
|
||
ist als Gemini mit 0,14 $ und in `eu-west` läuft. Ist Ark nicht freigeschaltet,
|
||
fällt die Kette still auf OpenRouter zurück; jede Zeile im Log nennt den
|
||
Anbieter, der geliefert hat.
|
||
|
||
Unterschieden wird zwischen „Anbieter nicht verfügbar" (Konto zu, kein Guthaben)
|
||
und einem echten Fehler – nur beim ersten Fall wird weitergereicht, damit ein
|
||
inhaltlicher Fehler nicht stillschweigend überdeckt wird.
|
||
|
||
Fest wählen geht weiterhin: `BILD_ANBIETER=stub|ark|openrouter`.
|
||
|
||
**Voraussetzungen je Anbieter:** OpenRouter braucht gekauftes Guthaben (eine
|
||
hinterlegte Karte genügt nicht – es ist ein Prepaid-Konto). BytePlus Ark braucht
|
||
die Aktivierung je Modell in der Konsole unter *Model activation*; das ist keine
|
||
Zahlungsfrage, verlangt aber die Zustimmung zu mehreren Verträgen.
|
||
|
||
## Demo-Daten
|
||
|
||
Demo-Daten zum Anschauen (Marke, zwei Ordner, vier Modelle mit echten Bildern
|
||
aus `uploads/`) – legt alles als **Client** an, belegt damit nebenbei die
|
||
Rechte, und ist idempotent:
|
||
|
||
```bash
|
||
node scripts/seed-demo.mjs
|
||
```
|
||
|
||
Die Mandantentrennung lässt sich jederzeit nachprüfen – legt zwei echte Konten an,
|
||
prüft über die API und räumt sich selbst auf:
|
||
|
||
```bash
|
||
node scripts/test-mandanten.mjs
|
||
```
|
||
|
||
Die Appwrite-SDKs sind **exakt** gepinnt (`appwrite@23.0.0`,
|
||
`react-native-appwrite@0.25.0`): der Server läuft 1.8.1, neuere SDKs sprechen
|
||
Response-Format 1.9.x. Beim Server-Upgrade beide zusammen anheben.
|
||
|
||
Konfiguration (Endpoint, Projekt-ID, DB-ID) steht in `client/app.json` unter
|
||
`expo.extra.appwrite`. Der Appwrite-**Server-Key** gehört dort **nicht** hinein –
|
||
er landet sonst im Client-Bundle; er steht in der `.env` im Repo-Root und wird
|
||
nur von `scripts/` und später von Appwrite-Functions benutzt.
|
||
|
||
## Datenbank-Setup
|
||
|
||
Das komplette Schema (22 Tabellen, Indizes, 5 Storage-Buckets, internes Dev-Team) legt
|
||
[scripts/setup-appwrite.mjs](scripts/setup-appwrite.mjs) an – idempotent, kann nach
|
||
Plan-Erweiterungen jederzeit erneut laufen:
|
||
|
||
```bash
|
||
# API-Key liegt als BRANDLOOP_APPWRITE_API_KEY in /home/webklar/apps/.env, dann einfach:
|
||
node scripts/setup-appwrite.mjs
|
||
|
||
# oder explizit:
|
||
APPWRITE_API_KEY=... node scripts/setup-appwrite.mjs
|
||
```
|
||
|
||
Der API-Key braucht die Scopes **Databases/Tables (read+write), Storage/Buckets (read+write), Teams (read+write)**.
|
||
|
||
## Prompt-Templates (P1–P18)
|
||
|
||
Alle 18 Prompts des Prompt-Inventars liegen versioniert in [prompts/](prompts/) (Konventionen
|
||
und Slot-Registry: [prompts/README.md](prompts/README.md)) und werden mit
|
||
[scripts/seed-prompts.mjs](scripts/seed-prompts.mjs) in die Tabelle `prompt_templates`
|
||
geseedet — idempotent: Inhaltsänderung ⇒ neue Zeile `version+1`, alte Zeilen `aktiv=false`.
|
||
P8 ist eine Familie (1× Regie-Kern + 4 Modell-Adapter seedance/veo/sora/wan) = 22 Zeilen.
|
||
Das Skript seedet außerdem die 4 globalen Brand-Regeln in `rules`.
|
||
|
||
```bash
|
||
node scripts/seed-prompts.mjs
|
||
```
|
||
|
||
### Bewusste Abweichungen vom DB-Plan
|
||
|
||
- **Referenzen als indizierte String-Spalten (size 64) statt Relationship-Spalten.**
|
||
Appwrite-Relationships sind nicht filter-/indizierbar – der Top-N-Index auf
|
||
`attribute_scores` (`brand_id, folder_id, category_id, score DESC`) und alle
|
||
Listen-Queries brauchen aber genau das. Many-to-many (`videos.attribute_ids`) ist ein String-Array
|
||
(`Query.contains`). IDs mit 64 Zeichen, weil uuid4 = 36 Zeichen.
|
||
- **`created_at`-Spalten entfallen** – Appwrite pflegt `$createdAt` automatisch
|
||
(Indizes nutzen `$createdAt` direkt, z. B. `score_events`, `jobs`).
|
||
- **Enum-Werte ohne Umlaute** (`laeuft` statt `läuft`).
|
||
- **`attributes.tags`: Key-Index statt Volltext-Index** – Appwrite verbietet
|
||
Fulltext auf Array-Spalten; Tag-Suche läuft über `Query.contains`.
|
||
- **S3/Hetzner-Backend für Storage** ist eine *instanzweite* Appwrite-Einstellung
|
||
(`_APP_STORAGE_DEVICE`) und betrifft alle Projekte auf dem Server – wird daher
|
||
nicht vom Skript gesetzt, sondern muss bewusst am Server konfiguriert werden.
|
||
Aktuell: lokales Storage.
|
||
|
||
### Rechte-Modell
|
||
|
||
- `rules` + `prompt_templates`: Tabellen-Rechte nur für Team `internal` (Dev-Modus) 🔒
|
||
- Alle anderen Tabellen: `rowSecurity` an, keine Tabellen-Rechte – Zeilen bekommen
|
||
beim Anlegen die Team-Permission der jeweiligen Brand (Multi-Tenant).
|
||
- `consents`-Bucket: zusätzlich Lese-Recht für Team `internal`.
|
||
- Append-only per Konvention (Server-Key schreibt): `score_events`, `video_metrics`, `jobs`.
|
||
|
||
### Noch offen (laut Plan)
|
||
|
||
- Appwrite **Functions**: `elo-update`, `favorit-berechnen`, `ads-metriken-holen`,
|
||
`kategorie-durchschnitt`, `archivierung`, `job-dispatcher`
|
||
- Stripe-Anbindung (metered billing über `usage_records`)
|