Closes the one checklist item M4 left for later — "TryOnButton auf
DetailGarmentView (mit impliziten 'Solo-Outfit')". A user can now open
a single garment's detail page, see "An mir anprobieren · 10 Credits",
and get an inline preview of themselves wearing just that one item
(or just that accessory, for glasses/jewelry/hat/accessory).
Client:
- api/try-on.ts: extracts a shared callGenerateWithReference() helper
and a dimsForSize() utility from runOutfitTryOn so the new
runGarmentTryOn can share the HTTP-error matrix + picture.images
row shape without a refactor of the outfit path.
- runGarmentTryOn({ garment, faceRefMediaId, bodyRefMediaId?, prompt?,
quality? }): auto-detects accessoryOnly from the garment's category
(FACE_ONLY_CATEGORIES), composes the DE default prompt ("im/in
<Name>", "mit <Name>" für Accessoires), writes a picture.images row
with wardrobeOutfitId=null so it doesn't pollute any outfit's
try-on history. Does NOT update any outfit.lastTryOn — it's a
standalone preview, on purpose.
- GarmentTryOnButton.svelte: thinner sibling of TryOnButton. Same
three states (ready / missing-refs / loading), same non-personal-
space disclaimer. Extra: inline preview panel showing the last
rendered result, with a link to the Picture gallery ("Gefunden in
der Picture-Galerie als normale Generierung.").
- DetailGarmentView now puts the try-on action above the existing
wear-tracking button. Try-on is the more engaging action for this
page; demoting "heute getragen" to a secondary-styled button
respects that without removing it.
Plan docs:
- docs/plans/wardrobe-module.md — rewrites the Status block to M1-M5
with actual commit hashes, and checks off the per-milestone task
lists. Adds a new M4.1 block for solo-garment try-on.
- docs/plans/me-images-and-reference-generation.md — adds the v40
space-scope migration (cb9a9bb42) as its own row in the commit
table, with a pointer to the sub-plan.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
27 KiB
Me-Images + Reference-basierte Bildgenerierung — Plan
Status (2026-04-23, Stand nach M5)
M1–M5 + M2.5 SHIPPED — das Feature ist end-to-end lieferbar. Nutzer legt unter /profile/me-images Gesicht + Ganzkörper (+ optional weitere Referenzen) ab, toggled pro Bild "KI darf nutzen", geht in den Picture-Generator, wählt Referenzen, triggert eine OpenAI gpt-image-2-Edit. Ergebnis landet in der Picture-Galerie. MCP-Tools (me.listReferenceImages, me.generateWithReference) sind registriert — Claude Desktop / Persona Runner können dasselbe automatisiert.
Commits (teils durch parallele Sessions in Commits mit anderer Attribution gelandet — Code korrekt, nur Message irreführend):
| Milestone | Commit | Inhalt |
|---|---|---|
| M1 Foundation | 89258eb45 |
Dexie v38 meImages-Table, Encryption-Registry (label+tags), Store + Queries, POST /api/v1/profile/me-images/upload |
| M2 Settings-UI | a64a7e39c |
Route /profile/me-images, Face/Fullbody-Slots, Grid, Drag-Drop, pro-Bild opt-in Toggle |
| M3 Edits-Endpoint | in 38dc80654 |
POST /picture/generate-with-reference → OpenAI /v1/images/edits; getMediaBuffer, verifyMediaOwnership in apps/api/lib/media |
| M4 Reference-Picker | in d087b4744 |
ReferenceImagePicker.svelte, Model-Auto-Switch, Endpoint-Routing, generationMode+referenceImageIds auf LocalImage |
| M2.5 Avatar-Migration | e2b5ac38c |
One-shot migration/legacy-avatar.ts, Autosync face-ref→avatar→auth.users.image, EditProfileModal-Cleanup |
| M5 MCP-Tools | fc635f983 |
packages/mana-tool-registry/src/modules/me.ts — zwei Tools, auto-registriert |
| Space-Scope-Migration | cb9a9bb42 |
meImages aus USER_LEVEL_TABLES ausgetragen, Dexie v40 retro-stampt spaceId = _personal:<uid>-Sentinel + authorId + visibility, drops userId. Queries/Store/MCP-Tool filtern jetzt auf aktiven Space. auth.users.image bleibt an Personal-Space primary-avatar gekoppelt — andere Spaces haben ihren eigenen lokalen Avatar. Sub-Plan: docs/plans/me-images-space-scope-migration.md |
Offen (noch nicht angefangen)
-
M6 — Lokaler Fallback via mana-image-gen (mehrere Tage, optional). FLUX + PuLID/InstantID auf dem GPU-Server (Windows, RTX 3090),
POST /edit-Endpoint in mana-image-gen, Routing überlocal/flux-pulidim apps/api-Endpoint. Lohnt sich erst, wenn Zero-Knowledge-Mode-User das brauchen oder OpenAI-Limits zum Problem werden. -
M7 — Inpainting Mask Drawing (~2 Tage, optional). Canvas-basiertes Mask-Editor im Picture-Generator (Brush-Size, Clear, Invert), Mask als zweites Multipart-Part an
/generate-with-reference. OpenAI/v1/images/editsakzeptiertmaskbereits — nur der Client-Editor fehlt. Nice-to-have für "ersetze nur das Outfit, Gesicht bleibt". -
M8 — Zero-Knowledge-Bild-Blobs (größerer Workstream). Client-seitige AES-Verschlüsselung der Bild-Blobs bevor sie zu mana-media gehen; beim Generate-Call lokal entschlüsseln, temporär an den Server durchreichen, Ergebnis wieder client-seitig verschlüsseln. Dann sieht selbst der Server nichts ausser Ciphertext. Braucht eine Architektur-Skizze eigener Güte — nicht Teil dieses Plans.
-
Global Kill-Switch
profile.aiUsesReferenceImages— im Plan als Feld auf dem profile-Singleton vorgesehen (Panic-Switch für "alle Referenzen temporär aus"). In M2 als "Pro-Bild reicht für jetzt" deferred, noch nicht gebaut. ~30 Minuten: Feld aufLocalUserContext+ Toggle im Intro-Block von MeImagesView + bei Empty-Set auf dem Generator die Referenzen ausblenden. -
Kind-Editor pro Tile — der
kindeines uploadeten Bilds (face/fullbody/halfbody/hands/reference) ist beim Upload fix. Ein späteres "Kind ändern"-Kontrollelement im Tile ist eine Stunde Arbeit, aber keiner hat's bis jetzt vermisst. -
Detailansicht eines generierten Bilds zeigt Referenzen — die Felder (
generationMode,referenceImageIds) sind aufLocalImagegespeichert, aberListView.svelteim Picture-Modul rendert sie noch nicht. Im Detail-Modal wäre ein "Erstellt mit Referenzen: [Thumbnail ×3]"-Block der sinnvolle Schritt. ~1 Stunde. -
Re-Upload-Pfad für Legacy-Avatar — die M2.5-Migration setzt
mediaId = 'legacy-avatar:<uid>'und lässt den Legacy-Avatar bewusst nicht durch mana-media laufen. Wenn der Nutzer diesen Avatar als KI-Referenz nutzen will, müsste er das Bild nochmal hochladen. Heute bouncedverifyMediaOwnership— das ist das korrekte Sicherheitsverhalten, aber die UI sagt das dem Nutzer nicht. Ein Hint "Dieses Bild stammt noch aus dem alten Profil — für KI-Nutzung bitte neu hochladen" im Avatar-Tile würde reichen. ~30 Minuten.
Vorläufer
Picture-Modul hatte bereits ungenutzte sourceImageId + generationId Felder (Platzhalter), OpenAI gpt-image-2 war für Text-zu-Bild produktiv über apps/api/src/modules/picture/routes.ts:65-96.
Ziel
Der Nutzer hinterlegt mehrere eigene Referenzbilder (Gesicht, Ganzkörper, weitere Posen/Outfits) in einem zentralen Pool. Diese Bilder werden explizit opt-in von KI-Bildgenerierung als Referenz verwendet, primär über OpenAI gpt-image-2 (der /v1/images/edits-Endpoint akzeptiert bis zu 16 Reference-Images pro Call) mit Replicate-Fallback und optional lokalem mana-image-gen (FLUX + IP-Adapter, später).
Kernfragen, die dieser Plan beantwortet:
- Wo leben die Referenzbilder? (Datenmodell, Scope, Verschlüsselung)
- Wie kommen sie in den Generator-Payload? (UI + API)
- Wie ruft der Server OpenAI mit Reference-Images? (Backend)
- Welche Use-Cases ergeben sich? (Konsumenten-Module)
Nicht im Scope:
- Wardrobe/Outfit-Modul — bekommt einen eigenen Plan (
wardrobe-module.md), konsumiert nur das hier entstehende Fundament. - Face-Swap in Video/Live-Streams — nur Still-Images.
- Per-Space-Avatare — ein Nutzer hat eine Identität; falls später Bedarf, reicht ein
spaceId-Zusatzfeld. - Gesichtsvalidierung / Liveness-Check — Vertrauensmodell: der Nutzer lädt nur Bilder seiner selbst hoch, wir erzwingen das nicht.
Abgrenzung
- Kein
photos:photosist Album/Tag-orientiert für beliebige Fotos.meImagesist ein kuratierter, winziger Pool (typ. 2–10 Bilder) mit klarer KI-Opt-in-Semantik. - Kein
body:bodytrackt Messungen/Workout. Progress-Fotos (Before/After) gehören dort hin, nicht inmeImages— das hier ist für KI-Referenz, nicht für Fitness-Logging. - Kein
picture.images:imagessind KI-generierte oder importierte Assets für Boards.meImagesist der Input für Generierung, nicht das Ergebnis. - Cross-Link:
picture.images.sourceImageIdundpicture.images.referenceImageIds[]zeigen aufmeImages.mediaId(oder andere media-IDs). Das Picture-Modul bleibt der zentrale Ort, an dem das Ergebnis landet.
Entscheidungen
1. Eigene Dexie-Tabelle, nicht auth.users.image erweitern
Gründe:
auth.users.imageist eine einzelne Text-URL in Better Auth. Mehrere Bilder + Metadaten + KI-Flags passen nicht rein ohne das Auth-Schema zu verunstalten.- Dexie + mana-sync + Encryption-Registry sind das etablierte Pattern für per-User-Daten.
auth.users.imagebleibt als abgeleitete Anzeige erhalten (Primary-Face → Avatar-URL), wird aber über einen Sync-Hook gepflegt, nicht direkt beschrieben.
2. Pro User, nicht pro Space
Ein Mensch hat eine Identität. Space-spezifische Avatare (Brand-Space vs. Personal-Space) sind ein 10%-Fall und können später über ein optionales spaceOverride: { [spaceId]: meImageId } Feld im profile-Singleton gelöst werden, ohne meImages selbst zu ändern.
3. Primär gpt-image-2 via /v1/images/edits, nicht Text-zu-Bild
Der Text-zu-Bild-Endpoint (/v1/images/generations) wird produktiv für freie Generierung genutzt und bleibt wie er ist. Für Reference-Workflows nutzen wir /v1/images/edits — derselbe Endpoint akzeptiert:
image(multipart) — eine oder mehrere Reference-Bilder (gpt-image-2: bis zu 16)prompt— der Transformations-Wunschmask(optional) — für Inpaintingsize,quality,nwie gehabt
Das ist der native OpenAI-Weg und erspart uns IP-Adapter-Engineering auf dem eigenen GPU-Server. Lokaler Fallback (FLUX + PuLID/InstantID auf RTX 3090) wird als M5 / später geplant, nicht in M1-M3.
4. Opt-in pro Bild, nicht global
Jedes meImage hat ein usage.aiReference: boolean Flag. Default beim Upload: false. Der Nutzer aktiviert gezielt, welche Bilder die KI verwenden darf. Global-Kill-Switch kommt aus dem Profile-Singleton (profile.aiUsesReferenceImages: boolean), Default true, damit einzelne Opt-ins direkt wirken.
Architektur-Überblick
┌─ Client (SvelteKit) ────────────────────────────────────┐
│ /profile/me-images (Upload + Toggles) │
│ picture/GeneratorForm (Reference-Picker) │
│ Dexie: meImages (encrypted label/tags/kind) │
└──────┬──────────────────────────────────────────────────┘
│ mana-sync (encrypted rows)
▼
┌─ mana-sync → PostgreSQL (mana_sync.meImages) ───────────┐
└─────────────────────────────────────────────────────────┘
┌─ Generate-Flow (NEU) ───────────────────────────────────┐
│ POST /api/v1/picture/generate-with-reference │
│ { prompt, referenceMediaIds: [...], mode, mask? } │
│ │
│ Backend: │
│ 1. Credits validieren (edits kostet wie generate) │
│ 2. Fetch reference buffers aus mana-media (via mediaId) │
│ 3. multipart → OpenAI /v1/images/edits │
│ oder (Fallback) mana-image-gen /edit │
│ 4. Response → uploadImageToMedia → return {images[]} │
└─────────────────────────────────────────────────────────┘
┌─ Tool-Registry / MCP ───────────────────────────────────┐
│ me.listReferenceImages (read-only, für Personas) │
│ me.generateWithReference (triggert obigen Endpoint) │
└─────────────────────────────────────────────────────────┘
Datenmodell
Neue Dexie-Tabelle: meImages
// apps/mana/apps/web/src/lib/modules/profile/types.ts
export type MeImageKind =
| 'face' // Kopf/Schulter, neutral
| 'fullbody' // Ganzkörper, stehend
| 'halfbody' // Hüfte aufwärts
| 'hands' // für Schmuck/Ring-Anproben
| 'reference'; // sonstige (andere Pose, anderer Lichtkontext)
export interface LocalMeImage {
id: string;
kind: MeImageKind;
label?: string; // "Portrait neutral Studio", "Outfit Juni"
mediaId: string; // → mana-media CAS (quelle-of-truth fürs Bild)
storagePath: string; // cached vom mana-media-Response
publicUrl: string;
thumbnailUrl?: string;
width: number;
height: number;
tags: string[]; // 'smiling', 'glasses-off', 'studio-light'
usage: {
aiReference: boolean; // Opt-in: darf KI das nutzen?
showInProfile: boolean; // für Avatar-Fallback-Logik
};
primaryFor?: 'avatar' | 'face-ref' | 'body-ref' | null;
createdAt: number;
updatedAt: number;
_pendingSync?: number;
}
Primary-Logik: Pro primaryFor-Wert existiert maximal ein meImage mit diesem Flag. Setzen eines neuen Primary räumt das alte auf (Store-Methode setPrimary(id, slot)).
Encryption-Registry-Eintrag
// apps/mana/apps/web/src/lib/data/crypto/registry.ts
meImages: {
enabled: true,
fields: ['label', 'tags', 'kind']
}
mediaId, storagePath, publicUrl, width, height, primaryFor, Timestamps → plaintext (konsistent mit images im Picture-Modul). Das Bild selbst liegt hinter mana-media-Auth — nicht verschlüsselt auf Dateiebene, aber nur für den Owner abrufbar. Für Zero-Knowledge-Modus-Nutzer: im M4 kommt optionale client-seitige Blob-Verschlüsselung dazu (out-of-scope für M1).
Kein neuer Sync-Endpoint nötig
mana-sync behandelt meImages wie jede andere per-User-Tabelle (userScoped, nicht spaceScoped). Nur Registrierung in der Sync-Schema-Liste.
Picture-Modul: bestehende Felder aktivieren + eins ergänzen
// apps/mana/apps/web/src/lib/modules/picture/types.ts
export interface LocalImage {
// ... bestehend
sourceImageId?: string | null; // bereits vorhanden — jetzt genutzt
referenceImageIds?: string[] | null; // NEU: für multi-reference gpt-image-2
generationMode?: 'text' | 'edit' | 'inpaint'; // NEU
generationId?: string | null; // bereits vorhanden
}
Encryption-Registry: referenceImageIds, generationMode → plaintext (IDs sind random, keine Leak-Gefahr).
Backend-Erweiterungen
Neuer Endpoint: POST /api/v1/picture/generate-with-reference
Datei: apps/api/src/modules/picture/routes.ts (erweitern, nicht neue Datei)
routes.post('/generate-with-reference', async (c) => {
const userId = c.get('userId');
const {
prompt,
model, // 'openai/gpt-image-2' | 'local/flux-pulid' | …
referenceMediaIds, // string[] (mana-media IDs; aus meImages oder picture.images)
mode, // 'edit' | 'inpaint'
maskMediaId, // optional, nur für inpaint
quality,
width,
height,
n,
} = await c.req.json();
// 1. Credits — gleicher Tarif wie /generate (3/10/25 je quality × n)
// 2. Reference-Buffers holen (parallel): for each id → fetchMediaBuffer(id, userId)
// — mana-media verifiziert, dass userId der Owner ist (keine fremden IDs)
// 3. multipart/form-data bauen:
// model, prompt, size, quality, n
// image[] (als File-Parts; bei n>1 refs: image[]=ref1, image[]=ref2, …)
// mask (optional)
// 4. POST https://api.openai.com/v1/images/edits
// 5. b64_json → uploadImageToMedia → return { images: [...] }
});
Lib-Helper neu in apps/api/src/lib/media.ts: fetchMediaBuffer(mediaId, userId): Promise<ArrayBuffer> — lädt + verifiziert Ownership in einem Call.
Modell-Routing analog zum bestehenden /generate:
openai/gpt-image-2(default) → OpenAI/v1/images/editslocal/*→ mana-image-gen/edit(siehe M5)- Replicate hat keinen äquivalenten Multi-Reference-Endpoint → wir überspringen Replicate hier; fällt auf OpenAI zurück.
Fehler-Matrix:
- 402 Insufficient credits
- 404 Reference media not found or not owned
- 413 Reference zu groß (OpenAI-Limit: 4MB pro PNG)
- 502 OpenAI-Fehler (mit
detail.slice(0,500)wie bisher)
mana-image-gen erweitern (M5, nicht M1)
Python/FastAPI-Seite bekommt einen POST /edit Endpoint, der IP-Adapter oder PuLID auf FLUX lädt und reference_images: list[bytes] + prompt annimmt. Weil Replicate/lokal nicht parallel zu OpenAI im selben Call laufen müssen, ist das ein reiner Fallback für Offline-/Zero-Knowledge-Szenarien und kann später dazukommen.
UI: zwei Touchpoints
1. /profile/me-images (neu)
- 2 prominente Slots oben: Gesicht (quadratisch, 512×512 empfohlen) und Ganzkörper (portrait, min 1024 hoch)
- Darunter Grid für zusätzliche Referenzen (Drag-and-Drop, Multi-Select-Upload — Pattern aus
picture/ListView.svelte:165-217klauen) - Pro Bild-Kachel:
- Kind-Badge (Gesicht / Ganzkörper / Hände / …)
- Toggle
usage.aiReference(prominent, mit Tooltip "Wird an OpenAI gesendet wenn du ein Bild mit Referenz generierst") - Primary-Stern (nur einer pro Slot aktiv)
- Tag-Editor
- Löschen
- Oben Globaler Kill-Switch: "KI darf meine Referenzbilder verwenden" (aus
profile-Singleton) - Hinweis-Card zu Datenschutz: wo landen die Bilder, wer sieht sie, wie löschen
Zugriff: ⚙ im profile-Modul → "Meine Bilder" + direkte Route.
2. Picture-Generator: Reference-Picker
In apps/mana/apps/web/src/lib/modules/picture/components/GeneratorForm.svelte (oder Äquivalent):
- Neuer "Referenz hinzufügen"-Button öffnet ein Popover
- Popover listet:
- Mich: alle
meImagesmitusage.aiReference === true(primary zuerst) - Aus diesem Modul: letzte N
images(für Generation-Chaining)
- Mich: alle
- Multi-Select bis zu 4 Referenzen (Client-Limit, OpenAI erlaubt 16)
- Wenn mindestens eine Referenz gewählt: Endpoint switched auf
/generate-with-reference, UI zeigt "gpt-image-2 Edit" statt "Generate" - Optional: Mask-Drawing für Inpainting (out-of-scope für M2, kommt in M3)
Tool-Registry + MCP
Nach M1+M2 bekommt packages/mana-tool-registry (siehe Memory, MCP M1+M1.5 shipped) zwei neue Tools:
me.listReferenceImages()— read-only, gibt{ id, kind, label, primaryFor, thumbnailUrl }[]zurück, nuraiReference=trueEinträge. Plaintext-Tier (label wird ent-verschlüsselt auf Server-Seite wie andere encrypted Tools).me.generateWithReference({ prompt, referenceImageIds, mode })— wrappt den neuen Endpoint, gibt{ imageIds, mediaIds }zurück.
Damit können Personas (AI Workbench, Chat, ai-missions) und externe MCP-Clients (Claude Desktop) den Nutzer "visualisieren". Beispiel: Persona "Stylistin" bekommt me.listReferenceImages + me.generateWithReference als Tool-Subset und kann in Chat sagen "Probieren wir drei Brillen-Looks?".
Verschlüsselung + Datenschutz
- Metadaten (label, tags, kind): client-seitig AES-GCM-256 vor Dexie-Write, wie im Standard-Pattern.
- Bilddaten: bleiben in MinIO (mana-media Bucket) mit Owner-RLS. Für Zero-Knowledge-Mode-Nutzer kommt in M4 optionale Client-Blob-Verschlüsselung (Upload verschlüsselt → Server sieht Ciphertext → OpenAI bekommt nur Bilder, wenn der Nutzer den Key entsperrt und den Edit-Call triggert). Das ist ein eigener Workstream und kein Blocker für M1-M3.
- OpenAI-Call: jeder
/generate-with-reference-Call geht als HTTPS-Multipart raus. Bilder landen kurzzeitig auf OpenAI-Servern (Policy: 30 Tage). Das muss die Settings-UI explizit erwähnen. - Audit: jeder Edit-Call loggt
{userId, referenceMediaIds, prompt, model, timestamp}in eine neuepicture.generation_log-Tabelle (nicht encrypted, für Rechnungs-/Abuse-Prüfung — Memoro-seitig, nicht in Dexie).
Use-Cases + Modul-Zuordnung
M1–M3 decken diese Use-Cases direkt ab:
| Use Case | Wo im UI | Modul |
|---|---|---|
| "Zeig mir wie ich mit einer schwarzen Brille aussehe" | Picture Generator → Reference: face → Prompt | picture |
| "Generiere ein Profilbild im Studio-Look aus meinem Selfie" | Picture Generator → Reference: face → Prompt | picture |
| "Mach ein Titelbild für meine Präsentation mit meinem Portrait" | Presi → Cover-Generator → Reference-Picker | presi (M4 Konsument) |
| "Ich in mittelalterlicher Rüstung" / kreative Spielereien | Picture Generator | picture |
| Avatar automatisch aus primary face ableiten | Profile-Settings | profile |
Eigener Folge-Plan wardrobe-module.md (nicht in diesem Plan):
| Use Case | Wo im UI | Modul |
|---|---|---|
| Outfit-Katalog pflegen (T-Shirts, Hosen, Schuhe als einzelne Items) | Wardrobe Gridview | wardrobe (neu) |
| "Kombiniere diese Jacke mit meinem Outfit aus Foto X" | Wardrobe → Outfit-Composer | wardrobe |
| Virtual Try-On mit Ganzkörper-Referenz + Garment-Referenz | Wardrobe → Try-On | wardrobe |
| Jahreszeit-Vorschläge ("Was ziehe ich heute an") | Wardrobe Daily-Card | wardrobe |
Weitere sinnvolle Konsumenten (eigene Tickets, nicht Teil dieses Plans):
website(Block-Tree CMS, in Planung): Portrait-Block kannprimaryFor='avatar'automatisch ziehen.presi: Cover-Slide-Template mit Nutzer-Portrait.broadcast/social-relay: Avatar-Generierung für Posts.dreams: "Ich im Traum" — Nutzer als Protagonist in KI-generierten Traum-Szenen.wishes: "Wie würde mir das stehen" — Wishlist-Preview vor dem Kauf.
Migrationsplan
Soft-first/Hard-follow-up-Regel (siehe Memory):
- Soft: Dexie v27 führt
meImagesein, Encryption-Registry um den Eintrag erweitern, sync-Schema registrieren.auth.users.imagebleibt als-is. Neue Primary-Face-Uploads schreiben zusätzlich zurmeImages-Tabelle. - Hard (Folge-Commit, einige Tage später): One-shot-Migration im Client: existierendes
auth.users.image→meImagesmitkind='face',primaryFor='avatar',usage.aiReference=false(Opt-in bleibt explizit).auth.users.imagewird danach zum abgeleiteten Feld, das über einen Sync-Hook ausmeImages(primaryFor='avatar').publicUrlgefüllt wird.
Milestones
-
M1 —
meImagesFoundation ✅ SHIPPED89258eb45- Dexie v38 (nicht v27 — v26 war library, v37 website-builder):
meImages-Tabelle apps/mana/apps/web/src/lib/modules/profile/types.ts:MeImageKind,MeImagePrimarySlot,MeImageUsage,LocalMeImage,MeImage,toMeImage- Encryption-Registry-Eintrag —
label+tagsencrypted;kind,primaryFor,usageplaintext - Store
stores/me-images.svelte.ts—createMeImage,updateMeImage,setPrimary(transactional),setAiReferenceEnabled,deleteMeImage+ Domain-Events - Queries —
useAllMeImages,useMeImagesByKind,useReferenceImages,useImageByPrimary - Sync-Schema registriert (
module.config.ts) +meImagesinUSER_LEVEL_TABLES(user-scoped, kein spaceId-Stamping) - Upload-Endpoint
POST /api/v1/profile/me-images/uploadwrapptuploadImageToMedia({ app: 'me' }) eigener me-storage-Bucket: mana-media nutzt einen Bucket für alle Apps;app='me'als Tag inmedia_referencesreicht
- Dexie v38 (nicht v27 — v26 war library, v37 website-builder):
-
M2 — UI Route
/profile/me-images✅ SHIPPEDa64a7e39c- Route +
RoutePage-Wrapping (nicht/settings/me-images— Repo-Konvention: pro-Modul-Subrouten) MeImageSlotCardfür Face/Fullbody,MeImageTilefür Grid,MeImageUploadZone(reusable)- Drag-and-Drop + Multi-File via File Picker
- Opt-in-Toggle pro Bild (
aiReference) - Primary-Stern (für kinds mit zugewiesenem Slot)
- Profile-ListView → "Meine Bilder"-Eintrag im Konto-Tab mit Sub-Hint
- (Hard-Migration wurde nach M2.5 ausgelagert — siehe unten)
- Global Kill-Switch
profile.aiUsesReferenceImages— offen (siehe Offenes-Liste)
- Route +
-
M2.5 — Legacy-Avatar-Migration + Autosync ✅ SHIPPED
e2b5ac38cmigration/legacy-avatar.ts— idempotenter One-Shot beim Öffnen der RoutesetPrimary(id, 'face-ref')claimt silent auch'avatar'auf derselben Zeile (Kopplung)syncAvatarToAuth()nach jeder primary/delete-Änderung — schreibtauth.users.imageEditProfileModalInline-Upload → "In Meine Bilder verwalten"-LinkprofileService.uploadAvatar+AvatarUploadResponse+ Test gelöscht (dead code)
-
M3 — Backend
generate-with-reference✅ SHIPPED in38dc80654getMediaBuffer+verifyMediaOwnershipinapps/api/src/lib/media.tsPOST /api/v1/picture/generate-with-referencemit OpenAI/v1/images/editsmultipart- Credit-Validierung identisch zu
/generate(3/10/25 × n) - Fehler-Matrix: 400 (prompt/refs), 402 (credits), 404 (ownership), 502 (OpenAI), 503 (keine Config)
- Degraded-Fallback: wenn mana-media nach OpenAI-Success failed → inline base64 in Response (Generierung nicht verloren)
Generation-Log-Tabelle: verworfen — Credit-Audit-Trail reicht, kein Postgres-Schema-Change in M3 nötig
-
M4 — Picture-Generator UI ✅ SHIPPED in
d087b4744ReferenceImagePicker.svelte— Multi-Select bis 4, leerer Zustand linkt zu/profile/me-images- Payload-Switch
/generate↔/generate-with-referenceviaisReferenceMode - Auto-Model-Switch auf
openai/gpt-image-2wenn Referenzen gewählt; Flux Schnell im Dropdown disabled negativePromptwird im Referenz-Modus disabled + als "wird ignoriert" markiertgenerationMode+referenceImageIdsaufLocalImagepersistiert (und intoImagepropagiert)- Detailansicht eines Bilds zeigt genutzte Referenzen — offen, ~1h
-
M5 — Tool-Registry + MCP-Exposure ✅ SHIPPED
fc635f983packages/mana-tool-registry/src/modules/me.tsme.listReferenceImages({kind?})— pullt via mana-sync (app=profile), decryptetlabel+tags, filtert aufusage.aiReference=trueme.generateWithReference({prompt, referenceMediaIds, quality, size, n})— Proxy über M3-Endpoint- MCP-Server exponiert beide automatisch (iteriert Registry in
createMcpServerForUser) - Persona-Runner kann sie sobald ANTHROPIC_API_KEY gesetzt + Persona ihnen erlaubt ist konsumieren
-
M6 — Lokaler Fallback via mana-image-gen (mehrere Tage) — OFFEN
- FLUX + PuLID/InstantID auf GPU-Server (Windows, RTX 3090)
POST /editin mana-image-gen- Routing über
local/flux-pulidim apps/api-Endpoint
-
M7 — Inpainting Mask Drawing (~2 Tage) — OFFEN
- Canvas-Mask-Editor im Picture-Generator
- Mask als zweites Multipart-Part an
/v1/images/edits
-
M8 — Zero-Knowledge-Bild-Blobs — OFFEN, großer Workstream
- Client-seitige AES-Verschlüsselung vor Upload zu mana-media
- Generate-Call entschlüsselt client-seitig, sendet temp an Server → OpenAI → Ergebnis wieder verschlüsseln
- Braucht eigene Architektur-Skizze; das hier ist nur ein Hinweis dass der Bedarf existiert
Entschieden (2026-04-23)
- Bucket-Namensgebung:
eigenerrevidiert — mana-media nutzt heute einen einzelnen Bucket (me-storage-Bucketmana-media); derapp-String landet als Tag inmedia_references.app. Upload geht mitapp='me', kein neuer Bucket nötig. Falls später Lifecycle-Rules pro App-Tag nötig werden, reicht eine mc-Regel mit--prefix 'me/'auf dem mana-media-Bucket. primaryFor='avatar'→auth.users.image: Client-Dexie-Hook ruftPUT /api/v1/auth/profile. mana-sync bleibt außen vor.- OpenAI Ref-Image-Format: Original-Format durchreichen (PNG/JPG/WEBP — OpenAI akzeptiert alle). Keine Server-Konvertierung.
- Credit-Kosten für Multi-Ref-Edits: identisch zu
/generate, pro Output-Bild, unabhängig von Reference-Anzahl. profile.aiUsesReferenceImages-Default:true(globaler Panic-Kill-Switch; Pro-Bild-Opt-in ist die eigentliche Hürde).- Alter Avatar-Upload-Pfad: bleibt in M1 unangetastet; M2 biegt
EditProfileModalauf/profile/me-imagesum und räumt den toten Endpoint-Call weg.
Verweise
- Bestehender Picture-Generate-Endpoint:
apps/api/src/modules/picture/routes.ts:43-227 - Picture Upload-Pattern (für UI-Klau):
apps/mana/apps/web/src/lib/modules/picture/ListView.svelte:165-217 - Encryption-Registry-Pattern:
apps/mana/apps/web/src/lib/data/crypto/registry.ts - mana-media CAS:
services/mana-media/CLAUDE.md - MCP-Gateway + Tool-Registry:
services/mana-mcp/CLAUDE.md,packages/mana-tool-registry/ - Spaces-Modul-Allowlist (falls neues
wardrobekommt):packages/shared-types/src/spaces.ts:63-184