Skip to content

Feat/flow2 deterministic phone gated - #133

Open
varun6325 wants to merge 43 commits into
render-examples:mainfrom
priyankmodiPM:feat/flow2-deterministic-phone-gated
Open

Feat/flow2 deterministic phone gated#133
varun6325 wants to merge 43 commits into
render-examples:mainfrom
priyankmodiPM:feat/flow2-deterministic-phone-gated

Conversation

@varun6325

Copy link
Copy Markdown

No description provided.

priyankmodiPM and others added 30 commits June 26, 2026 00:30
When an incoming WhatsApp message contains "update the discount", the webhook
automatically replies to the sender with the discount image via the WhatsApp
Cloud API. Phone number ID and token are read from env vars.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ant (#1)

Adds a GPT decision loop (with per-phone conversation history) that
routes inbound WhatsApp messages to campaign-graphic actions —
listing graphics, checking allowed edits, editing via Adobe Express,
and bulk generation from an uploaded file — instead of only replying
to a hardcoded "update the discount" phrase.

Co-authored-by: Priyank Modi <pmodi@adobe.com>
Co-authored-by: Priyank Modi <pmodi@adobe.com>
yarn.lock resolved every package from Adobe's internal Artifactory
(picked up via local ~/.npmrc), which Render's build servers can't
reach or validate certs for, causing "unable to get local issuer
certificate" during deploy. Regenerated against registry.npmjs.org
and dropped package-lock.json since render.yaml only runs yarn.

Co-authored-by: Priyank Modi <pmodi@adobe.com>
app.js reads process.env.OPENAI_API_KEY but the Blueprint never
declared the key, so a value set in the Render dashboard was never
wired into the service, causing "Missing credentials" at boot.
…ails

Model was hardcoded to gpt-4o while the configured Azure OpenAI resource
only has a gpt-5.4-mini deployment, causing every request to fail with a
404 "deployment does not exist" error. Reads OPENAI_MODEL/OPENAI_BASE_URL
from env instead, and logs the resolved config plus full error details on
failure to make future mismatches easy to diagnose.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* docs: add design spec for mocked Adobe Express image-edit flow

* docs: add implementation plan for image-edit flow

* feat: add in-memory tracked-image store

* feat: add mock Adobe Express API module

* feat: add mock Meta media upload module

* feat: extract GPT action handlers and add image-edit validation flow

* feat: wire image-edit flow into the webhook and GPT tool schema

* feat: map Croma earbuds graphic to Price/Address/Product Image/Partner Logo edits

Replaces the New Arrival Poster mock seed image with the real Croma
earbuds graphic and its allowed-edit fields.

* feat: return fixed updated Croma earbuds image for any edit

Any allowed edit on the Croma earbuds graphic now resolves to the
fixed croma1-earbuds-updated image, sent as a new WhatsApp message,
instead of a randomized mock render/upload URL.

---------

Co-authored-by: Priyank Modi <pmodi@adobe.com>
* Add design spec for global fixed edit-flow responses

Whenever an edit is requested, always send the fixed Croma earbuds
image; whenever allowed-edits is asked, always reply with the fixed
layer list. Removes per-template edit-key validation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Add design spec for real Adobe Express API integration

Replaces the mocked expressApi.js/imageStore.js/metaUpload.js flow with
real tagged-documents/generate-variation/status calls against a shared
docID catalog, per the brainstorming session.

* Ignore .superpowers scratch directory

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: add Adobe IMS token fetch/cache module

Implement expressAuth.js with getAccessToken and buildAuthHeaders functions.
Provides IMS token caching with 60s refresh margin per Adobe IMS v3 convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: read the shared docID catalog instead of hardcoded seed images

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: validate catalog is an array in loadCatalog()

If the catalog file parses successfully but isn't an array (e.g., `{}` or `42`),
treat it as an invalid catalog, log the error, and return empty array instead of
crashing on `.map()`. Aligns with design spec requirement to degrade gracefully
on catalog read failures.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: replace mocked Express API with real tagged-documents/generate-variation/status calls

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat: wire actions.js to the real Express API and drop mocked edit validation

* feat: log webhook metadata fields and declare Express API env vars

* Fix cross-task issues from final branch review

- Use the real statusUrl returned by generateVariation instead of
  reconstructing a status URL from apiBaseUrl()+jobId, which was an
  unverified assumption about the API's URL shape.
- Overlay image.currentEdits onto tagged elements before formatting
  allowed-edits messages so previously edited fields show their latest
  value instead of the stale original document value.
- Narrow the hardcoded campaign graphics list to match the real
  single-entry catalog (Croma Earbuds).
- Guard the sendImage call in actionEditGraphic with try/catch so a
  delivery failure doesn't swallow an already-successful edit; users
  now get a "couldn't send" message instead of no reply at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Add implementation plan for real Adobe Express API integration

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Move source files into src/, group Express API modules under src/express/

Keeps data/ (shared catalog) and docs/ at repo root. Updates
imageStore.js's default catalog path, actions.js/actions.test.js's
expressApi import, and the app.js entry point in package.json/render.yaml
to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Priyank Modi <pmodi@adobe.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…catalog

- Reject edit_graphic requests with a discount field over 40%, telling the
  user the real cap instead of silently clamping the value.
- Instruct GPT to translate tag text into Devanagari script (not
  transliteration) when the user asks for a Hindi translation.
- Rename catalog entry to "Croma Diwali offer" with its correct docId.
- Ignore .env locally.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sends "Change heading / price / discount / product" as WhatsApp
interactive buttons (or a list message when there are more than 3
options) instead of a plain text message, and handles the incoming
button/list tap the same way as free-text input.

Co-authored-by: Priyank Modi <pmodi@adobe.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* Always render edit options as native reply buttons

Replace the list-picker fallback with additional button messages
(3 per message, WhatsApp's per-message cap) so options over 3 still
show up as tappable buttons instead of a "Choose a field" dropdown.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Simplify edit-options prompt and button labels

Show a short "What would you like to change?" prompt instead of
dumping every field's current value into the button message body,
and humanize camelCase field names (e.g. discountPercentage ->
"Change discount") instead of only stripping _text/_image suffixes.
The detailed field listing is still kept in conversation history for
GPT context.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Priyank Modi <pmodi@adobe.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Implements Flow 2.2 — creating a brand-new creative from a text
description, assembled from approved brand elements, with canned images.

- add create_design tool + action; registers a per-phone, in-memory
  source: 'local' design backed by canned image URLs (data/onam-design.json)
- route edit_graphic / check_allowed_edits on image source: local designs
  skip the Adobe Express API; catalog designs keep the existing Express path
- palette guardrail: reject off-palette background edits and offer the
  approved festive accents (Marigold / Maroon / Deep Green)
- Malayalam banner translation via canned image (detect Malayalam script)
- tighten tool descriptions + system prompt so messages that carry concrete
  values route to edit_graphic instead of check_allowed_edits
- add one log line per action for observability
- tests: cover create/edit/guardrail/translation; update stale expectations

Co-authored-by: varun kalra <varkalra@adobe.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Supports the simplified Anjali flow (create -> add address -> translate to
Malayalam) and fixes the case where "translate the banner to Malayalam" was
rejected by the palette guardrail.

- editLocalDesign: a Malayalam/translate request now short-circuits and maps
  straight to the Malayalam creative, bypassing the locked-field and palette
  guardrails (the model may place the translated text on any key, including
  one that looks like "background")
- detect a translation request from Malayalam script or the word "Malayalam"
  in any edit key/value; mark language so resolveLocalImage picks the
  Malayalam image regardless of the edit key used
- address-only edits still resolve to the final image
- tests: cover address-only -> final, translate via unrecognized key,
  Malayalam under any key, and the background-key regression

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Always render edit options as native reply buttons

Replace the list-picker fallback with additional button messages
(3 per message, WhatsApp's per-message cap) so options over 3 still
show up as tappable buttons instead of a "Choose a field" dropdown.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Simplify edit-options prompt and button labels

Show a short "What would you like to change?" prompt instead of
dumping every field's current value into the button message body,
and humanize camelCase field names (e.g. discountPercentage ->
"Change discount") instead of only stripping _text/_image suffixes.
The detailed field listing is still kept in conversation history for
GPT context.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Wire quick-reply button taps to the actual field/image being edited

Button/list-row ids now encode edit:<imageId>:<fieldName> instead of just
a display title, so tapping an edit option tells GPT precisely which
field to edit instead of a truncated, ambiguous label. GPT now asks for
the missing value and applies it via edit_graphic once supplied, rather
than never reaching the Express API.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Add design doc for TV product-swap quick-reply flow

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Add implementation plan for TV product-swap quick-reply flow

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Extract interactive-reply id parsing into a testable module

* Support fully-specified multi-field edits in interactive reply ids

* Add actionSelectTvModel handler for the TV product-swap quick replies

* Wire select_tv_model GPT tool into the webhook handler

---------

Co-authored-by: Priyank Modi <pmodi@adobe.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…-routing

Fix/onam malayalam translate routing
The two flows now live in their own files so changing one cannot affect the
other; actions.js is a thin router that dispatches deterministically on
image.source. No behavior change (all tests pass).

- expressFlow.js: Flow 1 (source: 'express') — Adobe Express catalog designs
  (checkAllowedEdits, editGraphic, selectTvModel, discount cap)
- localFlow.js: Flow 2 (source: 'local') — canned-image designs
  (createDesign, checkAllowedEdits, editGraphic, palette + Malayalam handling)
- editOptions.js: shared pure presentation helpers (buildEditOptions,
  formatAllowedEdits), extracted so neither flow depends on the other;
  expressApi re-exports them for backward compatibility
- actions.js: thin dispatcher only (resolve image → route by source)
- tests mirror the modules: expressFlow.test.js + localFlow.test.js, with a
  small actions.test.js for router/dispatch behavior
- docs/diagrams: mermaid flowcharts for both flows

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…l-flows

refactor: split the two edit flows into self-contained modules
…ions

Implements the dealer-guided local flow: describe products+offer → assistant
suggests Onam → asks to add address → generates the creative → offer to change
→ translate to Malayalam. Uses only the onam-final and onam-malayalam creatives.

- ask_for_more_information can now carry options (e.g. ["Yes","No"]) rendered as
  tappable WhatsApp reply buttons; the tapped title flows back as the user's text
- create_design takes includeAddress; when true the with-address ("final")
  creative is sent (records the address so later edits keep that state)
- images are now sent WITH a friendly caption describing the offer / the change,
  so text + image arrive together like a real promo
- widen the GPT context window (last 3 → last 12) so multi-step follow-ups retain
  the original product/offer request; system prompt drives the guided flow
- tests: cover includeAddress → final image, captions, and button-reply parsing

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…delay

Generation no longer returns instantly. create_design and the local edit path
now stream progress messages one at a time (with a tunable pause) before sending
the image, so it feels like real work.

- streamProgress() sends step messages with a GEN_STEP_DELAY_MS pause (default
  1800ms); no-op when no sendText is provided, so unit tests stay instant
- create streams: creating → pulling brand kit → adding products → applying
  offer/styling → (if chosen) placing address, then the image
- Malayalam edit streams: translating → re-rendering, then the image
- messages are dynamic (name the actual products/offer; address step only when
  address was requested)
- sendText is threaded through actions → both flows; app.js drops the old static
  pre-step texts; expressFlow shows a single guarded re-render notice
- demo-onam-flow.sh waits for generation before the next step (GEN_PAUSE)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e the image

WhatsApp delivers link images a beat after plain text, so a follow-up sent as a
separate message after the image ("Want to change anything?" / "Anything else?")
was showing up before the image.

- fold the follow-up prompt into the image caption for create and edit success
  paths; the flow sends the image+caption and returns { skipSend, historyText }
  so app.js no longer sends a separate (racing) text
- app.js create_design/edit_graphic handle the string-vs-object return like
  check_allowed_edits already does; guardrail rejections still return a string
- apply the same caption treatment to the express edit path (latent race)
- tests updated for the new return shape / caption assertions

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
)

* Always render edit options as native reply buttons

Replace the list-picker fallback with additional button messages
(3 per message, WhatsApp's per-message cap) so options over 3 still
show up as tappable buttons instead of a "Choose a field" dropdown.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Simplify edit-options prompt and button labels

Show a short "What would you like to change?" prompt instead of
dumping every field's current value into the button message body,
and humanize camelCase field names (e.g. discountPercentage ->
"Change discount") instead of only stripping _text/_image suffixes.
The detailed field listing is still kept in conversation history for
GPT context.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Wire quick-reply button taps to the actual field/image being edited

Button/list-row ids now encode edit:<imageId>:<fieldName> instead of just
a display title, so tapping an edit option tells GPT precisely which
field to edit instead of a truncated, ambiguous label. GPT now asks for
the missing value and applies it via edit_graphic once supplied, rather
than never reaching the Express API.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Add design doc for TV product-swap quick-reply flow

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Add implementation plan for TV product-swap quick-reply flow

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Extract interactive-reply id parsing into a testable module

* Support fully-specified multi-field edits in interactive reply ids

* Add actionSelectTvModel handler for the TV product-swap quick replies

* Wire select_tv_model GPT tool into the webhook handler

* Add design doc for Edit Product/Discount/Price menu with Hinglish-mirrored replies

Specifies the fixed 3-button menu, WhatsApp list picker, generalized
discount-cap validation, and GPT-phrased language-mirrored responses
requested for the Express-catalog edit flow.

* Generalize discount cap to price edits and return structured edit outcomes

editExpressDesign now rejects any price edit implying more than 40% off
(not just literal "discount" fields), with rounding tolerance for
whole-rupee prices, and returns structured outcome objects instead of
hardcoded reply strings so the caller can phrase the final reply.

* Replace dynamic edit menu with fixed Edit Product/Discount/Price for Express-catalog images

Express-catalog graphics now always show the same 3-option menu instead
of a per-document field list, so choosing what to edit no longer needs
an Express API round trip. Local/Onam-style designs are unaffected.

* Present TV model picker as a list with Choose product button

* Add WhatsApp list-message support for the product picker

* Add GPT-phrased, language-mirrored edit replies and bare-field discount/price routing

Express-catalog edit outcomes are now phrased by a second GPT call that
mirrors the user's language style (English or Hinglish), and the fixed
menu's discount/price bare fields get explicit system-prompt rules for
computing a new price from a requested discount percentage.

* Add implementation plan; amend spec with discount-cap rounding tolerance

The spec's cap check needed a rounding-tolerance clause: whole-rupee
price rounding means an at-cap request never implies exactly 40.0000%,
so a strict > 40 check would reject the script's own 40%-cap example.

---------

Co-authored-by: Priyank Modi <pmodi@adobe.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
selectTvModel encoded the same imageId + shared TV_MODEL_EDITS into
every option id, so all 3 WhatsApp list rows (Sony/LG/Samsung) got an
identical id. WhatsApp rejects list messages with duplicate row ids
(400 #131009), so the picker never reached the user. buildValueEditId
now accepts an optional discriminator (the model title) so ids stay
unique while still resolving to the same edits on tap.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adobe's generate-variation API only accepts pre-signed image URLs from
AWS/Dropbox/Azure (windows.net) for tagMappings; the Scene7 CDN URL
was rejected, causing the Sony Bravia edit to fail against the real
Express API. Also export the constant so the test doesn't hardcode a
copy that'll drift when the URL is next rotated.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Swapping the placeholder image to a long pre-signed S3 URL made
selectTvModel's row ids ~2000 chars (encoded edits round-trip through
both the WhatsApp row id and the GPT-facing message text), which
WhatsApp rejects with #131009 "Row id is too long. Max length is 200."

Encode a short token in place of the real URL, and expand it back to
the actual S3 URL in editGraphic right before it reaches the Express
API, so nothing WhatsApp- or GPT-facing carries the full URL.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tton emojis

The edit_graphic success caption was a GPT-phrased one-liner; replace it with
the actual Diwali banner promo copy (with the updated price baked in) so the
message reads like real product marketing. Also pause briefly before sending
the follow-up edit-menu buttons — WhatsApp delivers link-image messages a beat
after text/interactive ones, so the buttons could arrive first even though the
image is awaited first in code. Add emojis to the 3 fixed edit-menu buttons.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adobe's Express API rejects a JSON number for a text tag with
"Unsupported text value for tag: price" (422) — price/oldPrice are the
first numeric edit fields this bot computes, so this only surfaced now.
Reworks the local (canned) flow from the Onam festive design into the
dealer→customer personalised offer story: a salesman creates an on-brand,
personalised insurance offer for a specific customer, then localises it.

- data/offer-design.json replaces onam-design.json: two canned creatives
  (English banner-apoorva, Hindi banner-apoorva-hindi), the HQ-approved plans,
  and the featured plan's price floor for governance
- localFlow: createDesign now takes customer/model/plan/includeContact, streams
  personalised progress, and sends the English creative; editGraphic resolves
  English↔Hindi (robust: Devanagari OR the word "Hindi" under any key); language
  is the only editable slot (brand/price/plan locked)
- app.js: create_design schema + system-prompt guidance rebuilt for the guided
  offer flow (plan picker → add contact? → anything else? → create), with the
  HQ price-floor governance injected from the design data so GPT refuses
  below-floor pricing; robust to phrasing variations
- getOfferContext() surfaces plans + governance to the prompt
- tests rewritten for the auto flow (create/caption/streaming, EN↔HI, governance
  context, router dispatch); 60 passing
- demo-auto-flow.sh drives the full conversation (plan tap → contact → anything
  else → generate → governance refusal → translate to Hindi)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cepts

With tool_choice:'required', tapping "Keep it" made GPT re-fire the same
price-floor explanation. Add explicit guidance: an acceptance ("Keep it") is a
brief acknowledgement via ask_for_more_information (no options) — never a repeat
of the governance message.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
varun kalra and others added 13 commits July 23, 2026 21:08
- remove the insurance-amount governance beat (plan/price refusal + "Keep it")
  from the create flow — not needed
- merge "Adding the <model>…" and "Applying the approved <plan> plan…" into one
  streaming line to cut a message
- demo-auto-flow.sh: remove the governance steps (generate → translate to Hindi)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…elivery

Switch the offer banner URLs to https:// and drop ?cache=off. WhatsApp needs
https for link media, and cache-busting forced a slower origin fetch — together
the likely cause of intermittent missing images.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restore the cache-busting query param (still on https) per preference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(express): update discountPercentage tag alongside price on discount edits

Discount edits were only computing and setting price, leaving the document's
discountPercentage tag stale even though the price had changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(express): auto-generate the TV product image S3 URL instead of a hardcoded one

The previous presigned S3 URL for the TV placeholder image was hand-generated
and expired after ~12h, requiring manual regeneration. Now the Scene7 source
image is downloaded and re-uploaded to S3 on demand (ported from
dynamicmedia-autoreflow's ImageS3Uploader), so the presigned URL is always
fresh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Priyank Modi <pmodi@adobe.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* fix(express): update discountPercentage tag alongside price on discount edits

Discount edits were only computing and setting price, leaving the document's
discountPercentage tag stale even though the price had changed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(express): auto-generate the TV product image S3 URL instead of a hardcoded one

The previous presigned S3 URL for the TV placeholder image was hand-generated
and expired after ~12h, requiring manual regeneration. Now the Scene7 source
image is downloaded and re-uploaded to S3 on demand (ported from
dynamicmedia-autoreflow's ImageS3Uploader), so the presigned URL is always
fresh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(deps): resolve new AWS SDK packages from the public npm registry

yarn.lock pointed at Adobe's internal Artifactory host for the new
@aws-sdk packages, picked up from this machine's global npm registry
override. That's only reachable/trusted on Adobe's network — Render's
build container has no CA trust for it, so `yarn install --frozen-lockfile`
failed there with "unable to get local issuer certificate". Re-resolved
against registry.yarnpkg.com to match the rest of the lockfile.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Priyank Modi <pmodi@adobe.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…e7 (#17)

Appends &fmt=png-alpha to the Scene7 SonyTv URL so the downloaded source
image keeps transparency before it's re-hosted on S3.

Co-authored-by: Priyank Modi <pmodi@adobe.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…pacing

Flow 2's Yes/No follow-ups and the "Change X" edit-menu button now carry
emojis, matching the express flow's existing style. Plan-picker buttons
stay plain text — prefixing them pushed titles like "Engine Protect combo"
past WhatsApp's 20-char reply-button cap (#131009 API error).

Also slows the streamed "generating..." progress messages (1800ms ->
3500ms per step) so the final banner takes longer to arrive and feels
more like real generation work, and bumps the demo script's wait
accordingly.
…and-slower-generation

feat(local): emoji-prefixed Yes/No + edit buttons, slower generation …
…skip live Express/S3 calls (#19)

Product-image swap requests (select_tv_model) now bypass the real Adobe
Express generate-variation pipeline and S3 re-hosting entirely, returning a
pre-rendered banner URL instead. The usual "calling Express API" progress
message still streams to the user. Price/discount-only edits are unaffected
and continue to hit the real Express API.

Co-authored-by: Priyank Modi <pmodi@adobe.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…20)

"discountPercentage" (sent by GPT alongside the computed price per the
system prompt) isn't a tagged document field, so every discount edit was
being rejected as disallowed_fields before the discount-cap logic ever ran
— e.g. "thik hai 40 hi kardo" always failed. editGraphic now recognizes
discountPercentage explicitly: requests over the 40% cap still return
discount_capped, and everything else returns the hardcoded
Croma-Diwali-Banner-discount-update banner (same pattern as the earlier
product-update bypass), still showing the usual Express progress message.

Co-authored-by: Priyank Modi <pmodi@adobe.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Take the plan → contact → confirm → create sequence out of the LLM's hands
and drive it from a code state machine, so the questions always get asked in
order instead of the model sometimes jumping straight to creating.

- flow2Session.js: pure state machine (start/advance) returning ask/reask/create
  descriptors; app.js owns the WhatsApp side effects. While a session is active
  the LLM is bypassed entirely.
- fuzzy.js: typo-tolerant matching (Levenshtein) for plan and yes/no answers, so
  a tapped button OR a mistyped reply both resolve; unmatched answers re-ask.
- Gate Flow 2 to a single demo phone (FLOW2_PHONE, default 9899860983, substring
  match). Other numbers are restricted to the current behaviour (Flow 1 + generic
  tools); a stray create_design there is politely declined.
- Deterministic fuzzy trigger (isCreateOfferIntent) detects a "create a
  personalised offer" message without the LLM and (re)starts the session from
  step 1 — checked before the in-progress bypass so a re-prompt restarts cleanly.
- extractOffer pulls customer/model from the trigger text, falling back to
  defaults in offer-design.json.
- The LLM's only Flow-2 role is now intent detection + entity extraction; system
  prompt and create_design schema updated to match.

Flow 1 logic is untouched; it's now safer since arbitrary numbers can't trigger
Flow 2. Adds fuzzy.test.js + flow2Session.test.js (82 tests pass).
Lower the default pause between streamed "generating…" progress messages so
the banner arrives faster. Still env-tunable via GEN_STEP_DELAY_MS.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants