# Przepływ: Tara → draft → AI → Base.com Krótki opis ścieżek w `dywanowy-bazar-ext` (JTL / źródło → magazyn BaseLinker). ## 1. Wejście danych - **Ekstrakcja**: `extractSourceProduct` w `src/pipeline.js` → adapter hosta → `sourceProduct` (m.in. nazwa DE, opis, specs, cena, tagi katalogu w polu **`sourceTags`**; format HTTP → **`sourceHttpResponseFormat`**). Stare zapisy draftów mogą mieć jeszcze **`shopifyTags`** — odczyt scala `getSourceTagList` w `src/catalogOffer.js`. - **Import z API** (`POST /api/draft-from-source`, duplikat z nowym URL, bulk): `buildDraftFromSource` w `src/buildDraftFromSource.js` — ekstrakcja → `buildProductDraft` → opcjonalnie AI (jak poniżej). - **`buildProductDraft`**: klasyfikacja **`strategicCategory`**, domyślne **`meta.copyStyle`**, **`channelSelection`**, **`aiMode`**, obrazy, pola Base. - **Wejście z dashboardu (`/editor`)**: parametr **`draftId` w URL** powoduje przekierowanie na **`/editor/legacy?draftId=...`**. Z listy draftów: **„Otwórz”** → przy **`lastGeneratedAt`** → legacy; w przeciwnym razie **`/editor/wizard`**. - Przy imporcie z AI (`draft-from-source`, bez `skipAi`): jeśli **`defaults.generateMarketplaceChannels`**, **`aiMode === multichannel`** i są kanały poza `base` — używane jest **`generateMultichannelBaseCombined`** (jak w §4). Przy błędzie ten sam fallback co **`generate-ai`**: **`generateChannelDrafts`** (scoped) → **`translateForBase`**. ## 2. Zapis draftu - **`POST /api/drafts`**, **`PATCH /api/drafts/:id`**: przed zapisem **`mergeEditorConfigForSession`** (świeże prompty z pliku + zachowane profile / tagi użytkownika). - **`GET /api/drafts/:id`**: ten sam merge w odpowiedzi, żeby UI widziało aktualny szablon. ## 3. Zmiana tagów / stanu (PATCH) - Zmiana **zbioru tagów** (`baseFields.tags` / `tag`) → **ponowna klasyfikacja** `strategicCategory` + opcjonalna notatka w `meta.warnings`. - Zmiana **tylko `offerState`** → **bez** przeliczania linii (wynika głównie z treści źródła); krótkie ostrzeżenie w `meta.warnings` o spójności. ## 4. Generacja AI (`POST .../generate-ai`) Opcjonalne body JSON: **`aiScope`**: `full` (domyślnie) | `channels-only` | `base-only`. 1. Merge **`editorConfig`** z dysku. 2. Ustalenie **`persona`** (value/quality) i zapis w `draft.persona` / `meta.personaUsed`. 3. **`copyStyleKey`** z `draft.meta.copyStyle` (lub domyślny z configu) przekazywany w payloadzie do modelu. 4. **Multichannel** (`aiMode === multichannel` + co najmniej jeden kanał poza `base`): - **`aiScope === full`**: jedno wywołanie **`generateMultichannelBaseCombined`** (w jednej odpowiedzi JSON: polska karta magazynowa Base z faktów + treści wybranych kanałów — bez dosłownego kopiowania opisu DE). Przy błędzie parsowania / API — automatyczny fallback na kolejność: **`generateChannelDrafts`** → **`translateForBase`** (dwa wywołania). - **`aiScope === channels-only`**: tylko **`generateChannelDrafts`** (scoped config); opis Base bez zmian. - **`aiScope === base-only`**: tylko **`translateForBase`** (kanały dodatkowe bez zmian). 5. **Tryb tylko Base** (jedna odpowiedź JSON na kartę magazynową PL albo brak dodatkowych kanałów): jedno wywołanie **`translateForBase`**. 6. Odpowiedź może zawierać **`aiUsage`**: `{ openAiCalls, billingMode, aiScope }`. Log serwera: **`[ai-usage]`** (wyłącz: `LOG_AI_USAGE=0`). ## 5. Pojedynczy kanał - **`POST /api/regenerate-channel`**: merge config + **`copyStyleKey`** z draftu. ## 6. Payload i wysyłka - **`POST /api/build-payload`** / **`POST /api/save-to-base`**: merge **`editorConfig`**, **`normalizeDraftForSave`**, złożenie **`buildBaseLinkerEnvelope`**, opcjonalnie **`sendToBaseLinker`**. ## 7. UI - **Dashboard** (`/editor`): lista draftów, wyszukiwarka Tara. - **Kreator** (`/editor/wizard`): kroki 1–5 (w tym styl przekazu, generacja, podgląd, zapis dry/live). - **Edytor ekspert** (`/editor/legacy`): pełna edycja pól i kanałów. - **Szybkie użycie trybu auto etykiet**: 1. Wejdź w krok **Media**. 2. Włącz `Tryb auto`, ustaw `Coverage` i opcjonalnie `AI enhancer`. 3. Kliknij **Wygeneruj zróżnicowane etykiety**. 4. W razie potrzeby popraw plan per obraz i kliknij **Przerenderuj etykiety wg planu**. ## 8. Zdjęcia: etykiety i kolejka do BaseLinker - **Auto etykiety premium (v1)**: - `POST /api/drafts/:id/generate-badge-plan` generuje deterministyczny plan `galleryBadgePlanByImageId`. - `POST /api/drafts/:id/render-gallery-badges-from-plan` renderuje `galleryImageOverlays` na podstawie planu. - Plan jest seedowany przez `draftId|imageId|policyVersion|offerState`, więc daje stabilną różnorodność. - **Tryb operacyjny uproszczony**: - Auto etykiety premium są dostępne bez bramki rollout. - Decyzja per produkt: `mainImageOverlay.mode` (`manual` albo `auto`) + `coveragePct`. - Dzięki temu można pracować „od razu do Base.com” bez zarządzania allowlistą draftów i procentami rollout. - **Render etykiet na zdjęciach**: model AI **nie** przerabia pikseli produktu — źródłowe JPEG są ładowane do sharp i nałożona jest warstwa SVG z tekstem (`src/badgeRenderer.js`). Sugestie tekstu na etykietę mogą pochodzić z LLM (`suggest-label-copy`), ale to tylko copy, nie generacja obrazu. - **Render** przeglądarkowy: `public/imageBadge.js` — Canvas → JPG `data:` (`mainImageOverlay`). - **Źródło grafiki nakładki głównej (etykieta)**: zawsze **pierwsze wybrane zdjęcie** w `selection.images` (`mainImageOverlay.dataUrl` powstaje w przeglądarce na tej bazie). - **Kolejność dla API** [`orderedImageSourcesForPayload`](../src/payloadImageOrder.js) (re-eksport w `baselinkerImages.js`): adresy wybranych zdjęć (+ opcjonalnie `galleryImageOverlays[id]`), opcjonalnie **kolaż dodany na początku** (`useAsFirstInPayload`). Jeśli kolaż jest pierwszym slotem i jest nakładka — **`mainImageOverlay` trafia na drugi slot** (pierwsze realne zdjęcie produktu), żeby kolaż został osobną miniaturą; bez kolażu nakładka jak dotąd na `sources[0]`. - **Serwer**: po `normalizeDraftForSave`, sharp (max ok. 1500 px JPEG) i `assertBaseLinkerImagesPreflight` (m.in. ~2 MB zdekodowanego JPG na slot według publicznego limitu BL). Wiele ciężkich `data:` zwiększa rozmiar PATCH drafcia. ### Checklist nakładki ↔ draft ↔ BL - Mapować wg **`selection.images[].id`**, nie wg numeru miniaturki po dodaniu kolażu (indeksy się rozjeżdżają). - **Segmentacja prod** na JPG nie jest dostępna — to rogi lub dolny pas informacji, jak w prostych zestawieniach marketplace. - **Gradient nagłówka** (`mainImageOverlay.color`): `red` \| `yellow` \| `green` \| `teal` — mapowanie na gradient w `imageBadge.js`. Dwie linie tekstu: **`customHeadline`** + **`customSubline`** (preset CUSTOM); pole **`customText`** jest synchronizowane z nagłówkiem dla kompatybilności wstecznej. - **Warianty wizualne** (`mainImageOverlay.variant`): `soft-pill` (default), `compact`, `ribbon-diagonal`. - **Constrainty wizualne**: wysokość badge docelowo 12–13% zdjęcia, safe margin ok. 3.5%, pozycje wspierają rogi (`top/bottom-left/right`), diagonal jest ograniczony do wariantu ribbon. - **`POST /api/drafts/:id/suggest-label-copy`**: krótkie wywołanie modelu (JSON `headline` / `subline`) na podstawie tytułu drafcie, tagów, stanu oferty itd.; wynik tylko **propozycją** — użytkownik edytuje w kreatorze / edytorze przed renderem JPG. - **Limit ~16 zdjęć** w kolejce BaseLinker: przy kolażu na początku slotów jest więcej — przy dużej galerii sprawdź `orderedImageSourcesForPayload` przed wysyłką. - Po **duplikacji draftu z nowym URL** (`POST .../duplicate` z `newSourceUrl`): serwer **nie kopiuje** `galleryImageOverlays` ani starych `data:` w `mainImageCollage` — zachowuje ustawienia tekstu etykiety z oryginału, ale **czyści `mainImageOverlay.dataUrl`** i `meta.warnings` zawiera przypomnienie o ponownym wygenerowaniu JPG/kolażu. - **`validateDraft` (serwer + edytor z `payloadImageOrder.js`)**: ostrzeżenie `payloadImages`, gdy `orderedImageSourcesForPayload` przekracza 16 slotów (ten sam limit co w `assertBaseLinkerImagesPreflight`). Odpowiedzi **`/api/build-payload`** i **`/api/save-to-base`** zwracają też `validationIssues`. ### Spójność walidacji (sprawdź przed wysyłką) - **Serwer**: każdy zapis draftu przez `normalizeDraftForSave` → `validateDraft`; odpowiedź JSON zawiera **`meta.validationIssues`** (ostrzeżenia nie blokują przycisków w UI). - **Przeglądarka**: ta sama kolejka slotów co BL — skrypt **`public/payloadImageOrder.js`** musi pozostawać zgodny z **`src/payloadImageOrder.js`**. - **Kreator**: po auto-zapisie (**PATCH**) odświeżany jest baner kolejki — komunikat ostrzegawczy z **`meta.validationIssues`** (pole `payloadImages`), gdy serwer go zwrócił; w przeciwnym razie to samo liczenie co w **`public/payloadImageOrder.js`**. - **Edytor ekspert**: krok „Walidacja” + komunikat po „Odśwież preview payloadu”, jeśli **`validationIssues`** nie jest puste. ## 9. Jedna ścieżka: draft → sloty → Base (źródło prawdy) 1. **Stan po stronie operatora** — zapisany obiekt **`draft`** (`PATCH` / `POST`): `selection.images`, `baseFields.mainImageCollage` (`dataUrl`, **`useAsFirstInPayload`**), `baseFields.mainImageOverlay`, opcjonalnie `meta.primaryBadgeImageId`. **Kreator i edytor ekspert** edytują ten sam model — nie ma dwóch „prawd”; obowiązuje ostatni zapis. 2. **Jak z tego powstaje kolejka do BaseLinker** — wyłącznie funkcje w **[`src/payloadImageOrder.js`](../src/payloadImageOrder.js)**: - **`orderedImageSourcesForPayload(draft)`** — lista URL-i / `data:` w kolejności slotów `parameters.images`; - **`describePayloadImageSlots(draft)`** — ten sam układ w postaci opisów tekstowych (podgląd w UI). W przeglądarce skrypt **`public/payloadImageOrder.js`** musi pozostawać **zsynchronizowany** z wersją serwerową. 3. **Szablon (`editorConfig`)** — plik **`config/editor-config.json`**: m.in. **`defaults.collageAsFirstInPayload`** — domyślna sugestia „kolaż jako pierwszy slot” przy **braku** wygenerowanego kolażu (wizard / checkbox w edytorze) oraz fallback na **`POST .../compose-collage`**, gdy body nie podaje `useAsFirstInPayload`. Przy **`mergeEditorConfigForSession`** zachowanie klucza jest takie jak innych domyślnych pól szablonu zapamiętanych w drafcie. 4. **Podgląd przed wysyłką** — kreator (krok 5) i panel **Transmit** w edytorze legacy pokazują **listę slotów** zgodną z pkt. 2; surowy JSON payloadu nadal w **`/api/build-payload`** / **`/api/save-to-base`**. Szczegóły merytoryczne JTL → Base: **[ARCHITECTURE.md](./ARCHITECTURE.md)**.