feat(studio): integrate Product Studio Pro (Phase 1 — gated embed)

AIBrandSolutions' Product Studio Pro (flat garment + prompt -> on-model shots)
is a full async chat/canvas app with a trial session + per-browser-id quota —
not the no-auth one-shot endpoint Swatch->Garment was. A server-side proxy would
share one trial quota across all users; embedding gives each user their own
browser session/quota, needs no creds (works anonymously from the link), and the
tool sets no frame-bust headers.

- New "Product Studio" nav tab -> /product-studio (pages/ProductStudio.tsx),
  gated on the moodboard module like Concept Studio: full-height iframe to the
  tool + an "Open in new tab" fallback.
- docs/plans/product-studio-integration.md: reverse-mapped async contract
  (edit-image -> edit-status, X-Browser-ID trial, R2 storage) for the Phase-2
  native persist-to-library proxy (deferred until SAPL has a vendor tenant key).

Embed smoke 5/5 PASS (gated route, iframe->tool, nav tab, fallback link,
0 errors). tsc -b + build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ajay Sable
2026-06-26 11:13:29 +05:30
co-authored by Claude Opus 4.8
parent 7be125f25c
commit 372a7ac873
5 changed files with 78 additions and 1 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+36
View File
@@ -0,0 +1,36 @@
# Product Studio Pro — integration
**Tool:** AIBrandSolutions **Product Studio Pro** — `https://saplproductstudio.aibrandsolutions.in`
**What it does:** upload a *flat* garment image + a text prompt ("style it with loose pants, white background, front & back") → AI **on-model / styled product shots** (the "flat → on-model" ask in `plm-portal-features.md` P5).
## Phase 1 — embed (shipped 2026-06-26)
Unlike Swatch→Garment (a no-auth one-shot endpoint we proxy in `concept.py`), Product Studio Pro is a **full async chat/canvas app with a trial session + usage quota**. So we surface it **inside our portal via iframe**, gated by RBAC (`moodboard` module), nav tab **"Product Studio"** → `/product-studio` (`pages/ProductStudio.tsx`).
**Why embed, not proxy (for now):**
- It works **anonymously from the link** (no email/login) — confirmed by Ajay + by the trial flow in the JS.
- The trial **quota is per browser-id** → embedding means *each user's own browser* gets its own allowance; a server-side proxy would share **one** quota across everyone and exhaust fast.
- No frame-bust headers (`X-Frame-Options` / CSP `frame-ancestors` absent) → iframe embedding is allowed. A prominent **"Open in new tab"** fallback covers any JS-level framing issue.
- Nothing fragile to reverse-engineer; always current with the vendor's app.
**Trade-off:** results are **not** auto-persisted into our library (the user downloads from the tool). That's what Phase 2 buys.
## Phase 2 — native proxy with persist-to-library (deferred)
Worth building **only once SAPL has a proper tenant / API key** from AIBrandSolutions (lifts the trial quota and stabilises the contract for a server-side proxy). Reverse-mapped contract (from the app's JS bundle, 2026-06-26) for when we do:
**Session**
- `ps_browser_id` (client-generated) sent as header **`X-Browser-ID`**; `Authorization: Bearer <token>` when logged in.
- `GET /api/tenant-info` → `{ isTrial }` (trial vs tenant mode).
- Trial works anonymously (browser-id only); `POST /api/auth/login {email}` / `register {email,phone,name,address}` mints a `trial_token`. Quota tracked via `POST /api/chats/increment-image-count`.
**Generate (async)**
- `POST /api/edit-image` — JSON `{ imageUrl, prompt, aspect_ratio, output_resolution, imageMemory }` → returns `requestId`.
- Poll `GET /api/edit-status?requestId=…` → status `COMPLETED` | `FAILED` → result `imageUrl`.
- Supporting: `POST /api/analyze-image {image_urls,…}`, `POST /api/resize-image {imageUrl, aspectRatio,…}`, batch `GET /api/image-status?requestId=…`.
**Input image** — `imageUrl` is a **URL string**, not a file. The flat garment is likely passed as a **base64 data-URL** (`readAsDataURL`/`toDataURL` are used) or uploaded to **Cloudflare R2** first (`r2.` references). ⚠️ **Confirm by capturing one real generation** before building — this is the only residual unknown.
**Phase-2 blocker:** the trial quota. A robust multi-user proxy needs a tenant key; raise with Pratik/AIBrandSolutions (same ask that would also lift any Swatch→Garment limits).
See `[[aibrandsolutions-api]]` memory for the full tool roster + the Swatch→Garment contract.
+2
View File
@@ -10,6 +10,7 @@ import Analytics from './pages/Analytics';
import Ingest from './pages/Ingest';
import Moodboard from './pages/Moodboard';
import ConceptStudio from './pages/ConceptStudio';
import ProductStudio from './pages/ProductStudio';
import Scraper from './pages/Scraper';
import ScraperReport from './pages/ScraperReport';
import TNA from './pages/TNA';
@@ -109,6 +110,7 @@ export default function App() {
<Route path="/ingest" element={<RequirePerm module="ingest"><Ingest /></RequirePerm>} />
<Route path="/moodboard" element={<RequirePerm module="moodboard"><Moodboard /></RequirePerm>} />
<Route path="/concept" element={<RequirePerm module="moodboard"><ConceptStudio /></RequirePerm>} />
<Route path="/product-studio" element={<RequirePerm module="moodboard"><ProductStudio /></RequirePerm>} />
<Route path="/scraper" element={<RequirePerm module="trends"><Scraper /></RequirePerm>} />
<Route path="/scraper/:id" element={<RequirePerm module="trends"><ScraperReport /></RequirePerm>} />
<Route path="/inbox" element={<RequirePerm module="inbox"><OrderInbox /></RequirePerm>} />
+1
View File
@@ -8,6 +8,7 @@ const NAV = [
{ to: '/boards', label: 'Boards', module: 'boards' },
{ to: '/moodboard', label: 'Moodboard', module: 'moodboard' },
{ to: '/concept', label: 'Studio', module: 'moodboard' },
{ to: '/product-studio', label: 'Product Studio', module: 'moodboard' },
{ to: '/scraper', label: 'Trends', module: 'trends' },
{ to: '/analytics', label: 'Analytics', module: 'analytics' },
{ to: '/review', label: 'Review', module: 'review' },
+38
View File
@@ -0,0 +1,38 @@
// Product Studio Pro — AIBrandSolutions' flat-garment → on-model styling tool,
// surfaced INSIDE our portal behind our login/RBAC (gated on the `moodboard`
// module, like Concept Studio). It's a full async chat/canvas app with its own
// trial session, so we embed it rather than proxy: each user hits it in their own
// browser, so the free-trial quota is per-user (a server-side proxy would share
// one quota across everyone). A native, results-persist-to-library proxy is the
// phase-2 upgrade once SAPL has a proper tenant/API key from the vendor.
const PRODUCT_STUDIO_URL = 'https://saplproductstudio.aibrandsolutions.in';
export default function ProductStudio() {
return (
<div className="h-screen pt-16 flex flex-col bg-paper text-ink font-archivo overflow-hidden">
<div className="px-5 sm:px-8 py-2.5 border-b border-line flex items-center justify-between gap-4 shrink-0">
<div className="min-w-0">
<p className="font-plex text-[9px] uppercase tracking-[0.22em] text-muted leading-none">Product Studio Pro</p>
<p className="text-[12px] text-muted mt-1 truncate">
Upload a flat garment, describe the styling (e.g. “loose pants, white background, front & back”) → on-model product shots.
</p>
</div>
<a
href={PRODUCT_STUDIO_URL}
target="_blank"
rel="noreferrer"
className="shrink-0 h-9 px-4 inline-flex items-center gap-1.5 rounded border border-line bg-ivory text-[12px] text-ink hover:border-ink/40 transition-colors"
>
Open in new tab ↗
</a>
</div>
<iframe
src={PRODUCT_STUDIO_URL}
title="Product Studio Pro"
className="flex-1 w-full border-0 bg-ivory"
allow="clipboard-read; clipboard-write; fullscreen"
/>
</div>
);
}