refine: redesigned layout, flower cards, edit sync, and bouquet preview framing
* refine: move upload mode toggle to top and compact it * refine: simplify upload layout and remove editorial numbers * refine: unify flow continue bar and redesign result page layout * refine: show bouquet flowers as scrollable cards on result page * refine: add flip-to-Korean on result flower cards * refine: improve result rationale and sync recipe on edit * refine: shorten edit title and align bouquet image framing to 3:4
This commit is contained in:
@@ -3,8 +3,12 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { resolve } from '$app/paths';
|
||||
import DescriptionCard from '$lib/components/ui/Artwork/DescriptionCard.svelte';
|
||||
import FlowContinueBar, {
|
||||
FLOW_CONTINUE_BUTTON
|
||||
} from '$lib/components/ui/FlowContinueBar.svelte';
|
||||
import Header from '$lib/components/ui/Header.svelte';
|
||||
import { editImages, fetchJob, finalizeJob, toDataUrl } from '$lib/flowerFlow/api.js';
|
||||
import { buildBriefBouquetTitle } from '$lib/flowerFlow/resolveRecipeFlowers.js';
|
||||
import { getFlowString, saveFlow } from '$lib/flowerFlow/session.js';
|
||||
|
||||
const jobId = getFlowString('jobId');
|
||||
@@ -23,7 +27,7 @@
|
||||
let selectionPoints = $state([]);
|
||||
let initialImage = $state(null);
|
||||
let generatedImage = $state(null);
|
||||
let recipe = $state(null);
|
||||
let moodAnalysis = $state(null);
|
||||
let editing = $state(false);
|
||||
let continuing = $state(false);
|
||||
/** @type {Array<{ id: string, role: 'user' | 'assistant', prompt?: string, mode?: string, status?: 'pending' | 'done' | 'error', afterImage?: { mimeType: string, base64: string } | null, error?: string }>} */
|
||||
@@ -33,7 +37,7 @@
|
||||
|
||||
const imageSrc = $derived(toDataUrl(generatedImage));
|
||||
const hasAreaSelection = $derived(selectionPoints.length > 2);
|
||||
const title = $derived(recipe?.concept ?? 'Generated bouquet');
|
||||
const title = $derived(buildBriefBouquetTitle(moodAnalysis));
|
||||
const description = $derived.by(() => {
|
||||
if (hasAreaSelection) {
|
||||
return 'Your prompt will apply to the marked area only.';
|
||||
@@ -234,7 +238,7 @@
|
||||
|
||||
initialImage = job.images.primary;
|
||||
generatedImage = job.images.primary;
|
||||
recipe = job.recipe ?? null;
|
||||
moodAnalysis = job.moodAnalysis ?? null;
|
||||
loading = false;
|
||||
} catch (err) {
|
||||
error = err instanceof Error ? err.message : 'Failed to load generated bouquet';
|
||||
@@ -244,19 +248,19 @@
|
||||
</script>
|
||||
|
||||
{#snippet editableImageFrame(image, editable = false)}
|
||||
<div class="relative w-[42%] overflow-hidden bg-track ring-1 ring-black/5">
|
||||
<div class="relative w-full max-w-44 sm:max-w-52 overflow-hidden bg-track ring-1 ring-black/5">
|
||||
{#if image}
|
||||
<img
|
||||
src={toDataUrl(image)}
|
||||
alt="Generated bouquet"
|
||||
class={[
|
||||
'aspect-[4/5] w-full object-contain',
|
||||
'aspect-[3/4] w-full object-contain object-center',
|
||||
editable && areaSelectionActive ? 'opacity-90' : ''
|
||||
]}
|
||||
draggable="false"
|
||||
/>
|
||||
{:else}
|
||||
<div class="aspect-[4/5] w-full bg-placeholder"></div>
|
||||
<div class="aspect-[3/4] w-full bg-placeholder"></div>
|
||||
{/if}
|
||||
|
||||
{#if editable && image}
|
||||
@@ -336,13 +340,17 @@
|
||||
class="flex min-h-0 w-full shrink-0 flex-col border-b border-line px-6 py-6 lg:w-[44%] lg:border-r lg:border-b-0 lg:px-10 lg:py-8"
|
||||
>
|
||||
<div class="mx-auto flex min-h-0 w-full max-w-100 flex-1 flex-col items-center justify-center gap-6">
|
||||
<div class="overflow-hidden bg-track shadow-sm ring-1 ring-black/5">
|
||||
<div class="w-full max-w-24 overflow-hidden bg-track shadow-sm ring-1 ring-black/5 sm:max-w-28 lg:max-w-75">
|
||||
{#if loading}
|
||||
<div class="aspect-[4/5] w-full animate-pulse bg-placeholder"></div>
|
||||
<div class="aspect-[3/4] w-full animate-pulse bg-placeholder"></div>
|
||||
{:else if imageSrc}
|
||||
<img src={imageSrc} alt="Generated bouquet" class="aspect-[4/5] w-full object-cover" />
|
||||
<img
|
||||
src={imageSrc}
|
||||
alt="Generated bouquet"
|
||||
class="aspect-[3/4] w-full object-contain object-center"
|
||||
/>
|
||||
{:else}
|
||||
<div class="aspect-[4/5] w-full bg-placeholder"></div>
|
||||
<div class="aspect-[3/4] w-full bg-placeholder"></div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -350,9 +358,9 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="relative flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<section class="relative flex min-h-0 flex-1 flex-col overflow-hidden pb-44 lg:pb-8">
|
||||
<div
|
||||
class="mx-auto flex min-h-0 w-full max-w-2xl flex-1 flex-col gap-4 px-6 py-5 pb-36 lg:py-6 lg:pb-6"
|
||||
class="mx-auto flex min-h-0 w-full max-w-2xl flex-1 flex-col gap-4 px-6 py-5 lg:py-6"
|
||||
>
|
||||
<div class="shrink-0">
|
||||
<p class="text-xs tracking-[0.2em] text-muted uppercase">Edit bouquet</p>
|
||||
@@ -415,16 +423,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="fixed right-0 bottom-0 left-0 z-20 space-y-2 border-t border-line/60 bg-surface/95 px-4 pt-3 pb-5 backdrop-blur-sm lg:static lg:mx-auto lg:w-full lg:max-w-2xl lg:border-t-0 lg:bg-transparent lg:px-6 lg:pt-0 lg:pb-6 lg:backdrop-blur-none"
|
||||
>
|
||||
<FlowContinueBar class="lg:mx-auto lg:w-full lg:max-w-2xl">
|
||||
{#if error}
|
||||
<p class="rounded bg-surface/95 px-3 py-2 text-sm text-red-600 ring-1 ring-black/5">
|
||||
{error}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<div class="flex items-center gap-2 rounded-full border border-pill bg-surface py-1.5 pr-1.5 pl-5">
|
||||
<div class="flex w-full items-center gap-2 rounded-full border border-pill bg-surface py-1.5 pr-1.5 pl-5">
|
||||
<textarea
|
||||
bind:value={prompt}
|
||||
rows="1"
|
||||
@@ -468,17 +474,15 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
disabled={editing || continuing}
|
||||
onclick={continueToResult}
|
||||
class="px-2 py-2 text-sm whitespace-nowrap text-ink underline-offset-4 hover:underline disabled:opacity-50"
|
||||
>
|
||||
{continuing ? 'Preparing result...' : 'Continue to result ->'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
disabled={editing || continuing}
|
||||
onclick={continueToResult}
|
||||
class={FLOW_CONTINUE_BUTTON}
|
||||
>
|
||||
{continuing ? 'Preparing result...' : 'Continue to result ->'}
|
||||
</button>
|
||||
</FlowContinueBar>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user