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:
co-authored by
Claude Opus 4.8
parent
7be125f25c
commit
372a7ac873
File diff suppressed because one or more lines are too long
@@ -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.
|
||||
@@ -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>} />
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user