Skip to content

Feat/edit options quick reply buttons - #131

Open
priyankmodiPM wants to merge 40 commits into
render-examples:mainfrom
priyankmodiPM:feat/edit-options-quick-reply-buttons
Open

Feat/edit options quick reply buttons#131
priyankmodiPM wants to merge 40 commits into
render-examples:mainfrom
priyankmodiPM:feat/edit-options-quick-reply-buttons

Conversation

@priyankmodiPM

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>
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>
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>
* 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>
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>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <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>
…ck-reply-buttons

# Conflicts:
#	src/actions.js
#	src/app.js
#	src/express/expressApi.js
* 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>
varun kalra and others added 10 commits July 22, 2026 18:52
…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>
…rored 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.
…comes

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.
…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.
…nt/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.
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.
…ck-reply-buttons

# Conflicts:
#	src/actions.js
#	src/actions.test.js
#	src/app.js
#	src/express/expressApi.js
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