From 882aa60976e9e99bdef8c2f410a4ed5895c69e75 Mon Sep 17 00:00:00 2001 From: Till JS Date: Sat, 25 Apr 2026 16:42:31 +0200 Subject: [PATCH] =?UTF-8?q?feat(comic):=20Mc2=20=E2=80=94=20Character-Buil?= =?UTF-8?q?der=20UI=20+=20Variant-Grid=20+=20Routes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Datenschicht aus Mc1 wird jetzt durch UI benutzbar. End-to-end-Flow: Tab-Switch zu Characters → "+ Neuer Character" → Stil + Add-Prompt + Source-Confirm (face Pflicht, body Toggle) → 4 Varianten parallel gerendert → User pinnt eine als Identity → Character ist fertig, nutzbar als Story-Anchor (Mc3 wired das in den StoryForm-Flow). UI-Komponenten: - `api/generate-character.ts`: runCharacterGenerate({character, n=4, quality, model}) ruft /picture/generate-with-reference mit [face, body?]-Refs + Stil-Prefix + Add-Prompt + Identity-Anchor- Hint, schreibt N picture.images mit comicCharacterId-Back-Ref, appended an den Character via comicCharactersStore.appendVariant (auto-pin auf erste Variant). Ein Server-Call mit n=4 statt 4 parallele — gpt-image-2 Multi-Image-Response in einem Batch. - `components/CharacterCard.svelte`: Grid-Tile mit Cover (pinned Variant > erste Variant > Placeholder), Style-Badge, Favorit- Heart, Amber "Pin offen"-Badge wenn Varianten existieren aber keine gepinned ist. - `components/VariantTile.svelte`: einzelne Variant im Grid mit Pin-Star wenn aktiv, Bottom-Action-Bar auf Hover (Pinnen / Entf.). Pinned hat primary-Border + Schatten, Unpinned dezent. - `components/CharacterBuilder.svelte`: Zwei Modi via `existing`- Prop. Create-Modus: Name + StylePicker + AddPrompt + Source- Preview (face Pflicht, Body-Toggle). Extend-Modus: Style + Source fix vom existierenden Character, nur AddPrompt editierbar pro Generierung. Beide feuern die gleiche runCharacterGenerate-Pipeline. - `views/CharactersView.svelte`: Grid + "+ Neuer Character"-CTA + Face-Ref-Empty-State + leeres Empty-Board. Gleicher Aufbau wie StoriesView für visuelle Konsistenz. - `views/DetailCharacterView.svelte`: Meta-Card (Titel + Style- Badge + Variant-Count + Pin-offen-Hinweis), Variant-Grid mit Pin/Remove, "+ Mehr Varianten"-Button öffnet Builder im extend-Modus inline (Builder bleibt offen für Iterations-Flow). Plus Archive/Delete. - `ListView.svelte` (Modul-Root) bekommt 2-Tab-UI: **Stories | Characters** mit Count-Badge auf dem Characters-Tab. Standardpattern wie Wardrobe's Garments|Outfits. Routes: - `/comic/character` (Liste, eigenständige Route — Back-Nav aus Detail/New zeigt darauf) - `/comic/character/new` (CharacterBuilder im Create-Modus) - `/comic/character/[id]` (DetailCharacterView mit {#key id} Re-Mount wie Story-Detail). check passes 0/0 für comic-files. Mc3 (Story-Create wechselt auf den neuen Picker, Soft-Migration für bestehende Stories) folgt im nächsten Commit. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../web/src/lib/modules/comic/ListView.svelte | 100 +++++- .../modules/comic/api/generate-character.ts | 209 ++++++++++++ .../comic/components/CharacterBuilder.svelte | 317 ++++++++++++++++++ .../comic/components/CharacterCard.svelte | 78 +++++ .../comic/components/VariantTile.svelte | 98 ++++++ .../modules/comic/views/CharactersView.svelte | 77 +++++ .../comic/views/DetailCharacterView.svelte | 214 ++++++++++++ .../routes/(app)/comic/character/+page.svelte | 14 + .../(app)/comic/character/[id]/+page.svelte | 17 + .../(app)/comic/character/new/+page.svelte | 21 ++ 10 files changed, 1139 insertions(+), 6 deletions(-) create mode 100644 apps/mana/apps/web/src/lib/modules/comic/api/generate-character.ts create mode 100644 apps/mana/apps/web/src/lib/modules/comic/components/CharacterBuilder.svelte create mode 100644 apps/mana/apps/web/src/lib/modules/comic/components/CharacterCard.svelte create mode 100644 apps/mana/apps/web/src/lib/modules/comic/components/VariantTile.svelte create mode 100644 apps/mana/apps/web/src/lib/modules/comic/views/CharactersView.svelte create mode 100644 apps/mana/apps/web/src/lib/modules/comic/views/DetailCharacterView.svelte create mode 100644 apps/mana/apps/web/src/routes/(app)/comic/character/+page.svelte create mode 100644 apps/mana/apps/web/src/routes/(app)/comic/character/[id]/+page.svelte create mode 100644 apps/mana/apps/web/src/routes/(app)/comic/character/new/+page.svelte diff --git a/apps/mana/apps/web/src/lib/modules/comic/ListView.svelte b/apps/mana/apps/web/src/lib/modules/comic/ListView.svelte index dacc6112b..d9c0828be 100644 --- a/apps/mana/apps/web/src/lib/modules/comic/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/comic/ListView.svelte @@ -1,15 +1,53 @@
- + + +
+ {#if activeTab === 'stories'} + + {:else} + + {/if} +
diff --git a/apps/mana/apps/web/src/lib/modules/comic/components/CharacterCard.svelte b/apps/mana/apps/web/src/lib/modules/comic/components/CharacterCard.svelte new file mode 100644 index 000000000..dbe7eadd7 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/comic/components/CharacterCard.svelte @@ -0,0 +1,78 @@ + + + + +
+ {#if cover?.publicUrl} + {character.name} + {:else} +
+ + Noch keine Variante +
+ {/if} + + + {STYLE_LABELS[character.style].de} + + + {#if character.isFavorite} + + + + {/if} + + {#if !isPinned && variantCount > 0} + + Pin offen + + {/if} +
+ +
+

{character.name}

+

+ {variantCount} + {variantCount === 1 ? 'Variante' : 'Varianten'} +

+
+
diff --git a/apps/mana/apps/web/src/lib/modules/comic/components/VariantTile.svelte b/apps/mana/apps/web/src/lib/modules/comic/components/VariantTile.svelte new file mode 100644 index 000000000..fd17c2070 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/comic/components/VariantTile.svelte @@ -0,0 +1,98 @@ + + + +
+ {#if image?.publicUrl} + Variante {variantIndex + 1} + {:else if image$.loading} +
+ Lädt… +
+ {:else} +
+ Variante nicht gefunden +
+ {/if} + + + + #{variantIndex + 1} + + + + {#if isPinned} + + + + {/if} + + +
+ {#if !isPinned} + + {:else} + Aktiv + {/if} + + {#if onRemove} + + {/if} +
+
diff --git a/apps/mana/apps/web/src/lib/modules/comic/views/CharactersView.svelte b/apps/mana/apps/web/src/lib/modules/comic/views/CharactersView.svelte new file mode 100644 index 000000000..b0564314d --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/comic/views/CharactersView.svelte @@ -0,0 +1,77 @@ + + + +
+
+
+

Deine Comic-Characters

+

+ {characters.length} + {characters.length === 1 ? 'Character' : 'Characters'} in + {activeSpace?.name ?? 'diesem Space'} +

+
+ + + Neuer Character + +
+ + {#if !hasFace && !face$.loading} +
+
+ +
+

Lade erst dein Gesichtsbild hoch

+

+ Charakter-Generierung braucht ein Face-Bild als Source. Hochladen in + Profil → Bilder. +

+
+
+
+ {/if} + + {#if characters.length > 0} +
+ {#each characters as character (character.id)} + + {/each} +
+ {:else if !characters$.loading} +
+

Noch keine Characters.

+

+ Bau deinen ersten Comic-Character aus deinem Foto — Stil wählen, 4 Varianten generieren, + beste pinnen, fertig. +

+ + + Ersten Character bauen + +
+ {/if} +
diff --git a/apps/mana/apps/web/src/lib/modules/comic/views/DetailCharacterView.svelte b/apps/mana/apps/web/src/lib/modules/comic/views/DetailCharacterView.svelte new file mode 100644 index 000000000..ae7744b33 --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/comic/views/DetailCharacterView.svelte @@ -0,0 +1,214 @@ + + + +
+ + + {#if !character} + {#if character$.loading} +

Lädt…

+ {:else} +
+

Character nicht gefunden.

+

Gelöscht oder in einem anderen Space.

+
+ {/if} + {:else} + +
+
+
+

{character.name}

+
+ + {STYLE_LABELS[character.style].de} + + + {character.variantMediaIds.length} + {character.variantMediaIds.length === 1 ? 'Variante' : 'Varianten'} + + {#if !character.pinnedVariantId && character.variantMediaIds.length > 0} + Pin offen + {/if} +
+
+ +
+ + {#if character.description} +

{character.description}

+ {/if} + + {#if character.addPrompt} +
+ Prompt-Add: + {character.addPrompt} +
+ {/if} +
+ + +
+
+

+ Varianten +

+ {#if !showBuilder && !character.isArchived} + + {/if} +
+ + {#if character.variantMediaIds.length === 0} +
+

Noch keine Varianten.

+

+ Klick oben rechts auf + Mehr Varianten, um die + ersten 4 zu generieren. +

+
+ {:else} +
+ {#each character.variantMediaIds as variantId, index (variantId)} + handlePin(variantId)} + onRemove={character.variantMediaIds.length > 1 + ? () => handleRemove(variantId) + : undefined} + /> + {/each} +
+ {/if} + + {#if showBuilder && !character.isArchived} + (showBuilder = false)} + onCreated={() => { + // Keep the builder open so the user can iterate without + // having to re-open. New variants append + appear in + // the grid above via the liveQuery. + }} + /> + {/if} +
+ + +
+ + +
+ + {#if character.isArchived} +

+ Archivierter Character — keine Variant-Generierung möglich, + bis wieder aktiviert. +

+ {/if} + {/if} +
diff --git a/apps/mana/apps/web/src/routes/(app)/comic/character/+page.svelte b/apps/mana/apps/web/src/routes/(app)/comic/character/+page.svelte new file mode 100644 index 000000000..dfb03b8a7 --- /dev/null +++ b/apps/mana/apps/web/src/routes/(app)/comic/character/+page.svelte @@ -0,0 +1,14 @@ + + + + Comic-Characters · Mana + + + +
+ +
+
diff --git a/apps/mana/apps/web/src/routes/(app)/comic/character/[id]/+page.svelte b/apps/mana/apps/web/src/routes/(app)/comic/character/[id]/+page.svelte new file mode 100644 index 000000000..a4fbe52ca --- /dev/null +++ b/apps/mana/apps/web/src/routes/(app)/comic/character/[id]/+page.svelte @@ -0,0 +1,17 @@ + + + + Comic-Character · Mana + + + + {#key id} + + {/key} + diff --git a/apps/mana/apps/web/src/routes/(app)/comic/character/new/+page.svelte b/apps/mana/apps/web/src/routes/(app)/comic/character/new/+page.svelte new file mode 100644 index 000000000..545b0b34c --- /dev/null +++ b/apps/mana/apps/web/src/routes/(app)/comic/character/new/+page.svelte @@ -0,0 +1,21 @@ + + + + Neuer Comic-Character · Mana + + + +
+
+

Neuer Comic-Character

+

+ Wähle Stil + optionalen Add-Prompt — wir rendern direkt 4 Varianten zur Auswahl. Aus dem + Detail kannst du jederzeit weitere generieren. +

+
+ +
+