Storefront Chat Agent
Status: in progress (Phase 1)
Goal
Make the home modal feel like a small concierge:
- Onboarding — quick capture of preferences when a user first opens the chat (colors, styles, materials, price range, fit/size).
- Free chat — open-ended Q&A about Cici Label (custom design at
/design, sustainability, sizing) AND product search. - Streaming — tokens arrive as the model generates.
Architecture
Browser
│ EventSource / AI-SDK useChat
▼
Next.js API route (apps/storefront/src/app/api/ai-chat/route.ts)
│ proxies request server-side using MEDUSA_BACKEND_URL
▼
POST /store/ai/chat ← SSE endpoint (Medusa backend)
│ AI-SDK streamText(...) with tools
▼
storefrontChatAgent (apps/backend/src/mastra/agents/storefront-chat.ts)
- DB-configured LLM (DashScope qwen by default)
- system prompt: brand voice + onboarding logic + corpus
- tools:
• search_products({query, color?, material?, max_price?, limit})
• (later) save_preferences({colors?, styles?, materials?, price_range?, body?})
• (later) get_brand_info({topic})
/store/ai/search (the GET non-streaming endpoint) stays as-is for the
/store inline grid. Two surfaces, two endpoints.
Data shapes
Preferences (apps/storefront/src/lib/util/ai-chat-preferences.ts ←
versioned localStorage; mirrored on server later):
type UserPrefs = {
colors?: string[] // ["white", "indigo", "natural"]
styles?: string[] // ["minimal", "bohemian"]
materials?: string[] // ["cotton", "silk", "linen"]
price_range?: { min?: number; max?: number }
body?: { size?: string; fit?: "relaxed" | "fitted" }
notes?: string
}
Chat message (extends the current AiChatMessage):
type AiChatMessage = {
id, role, content, ts, products?, interpretation?,
partial?: boolean // true while streaming
tool_calls?: Array<{ name; args; result_summary }>
}
Request body to POST /store/ai/chat:
{
messages: AiChatMessage[]
prefs?: UserPrefs
visitor_id: string // required — see "Visitor ID" below
}
Visitor ID
localStorage["jyt_visitor_id"] already exists in the storefront —
cart.ts stamps it on cart metadata, analytics workflows consume it.
Reuse it.
- Chat request body includes
visitor_id. Server action readslocalStorage["jyt_visitor_id"]before calling the proxy route. - Backend persists threads keyed by
visitor_id(anonymous-friendly). - On customer login, attach
customer_idto existing threads matchingvisitor_idandcustomer_id IS NULL— same merge pattern as cart attribution. - Same id ties to existing analytics events, so a single visitor's chat → product → purchase flow joins in the analytics store.
(Persistence module ships in Phase 2.)
Custom design — the real flow
/design already exists with a full DesignEditorWrapper. The flow:
/design— canvas + layer tools.- Upload an idea (image / sketched in editor).
- Pick inventory (fabric) from the catalogue.
- Pick a partner (or auto-assign).
- Production run starts after partner accepts.
Docs the brand-knowledge corpus references:
media/design-to-productdesigns/send-to-partner-migrationdesigns/production-run-convergence-planprotocol/design/cici-label
Agent answer for "how do I do a custom design?":
You can do it all online via /design. The editor lets you upload an idea or sketch, pick the fabric from our inventory, and either choose a partner you'd like to make it or let us assign one. A production run starts once a partner takes the design — track it from your account.
Files
Phase 1 — minimum viable streaming chat:
Backend:
apps/backend/src/mastra/data/storefront-brand-knowledge.mdapps/backend/src/mastra/agents/storefront-chat.tsapps/backend/src/mastra/agents/tools/storefront-search-products.tsapps/backend/src/api/store/ai/chat/route.tsapps/backend/src/api/store/ai/chat/validators.tsapps/backend/src/api/middlewares.ts— wire SSE route
Storefront:
apps/storefront/src/lib/util/ai-chat-preferences.tsapps/storefront/src/lib/util/visitor-id.tsapps/storefront/src/app/api/ai-chat/route.ts— proxyapps/storefront/src/modules/home/components/ai-search-chat/onboarding.tsxapps/storefront/src/modules/home/components/ai-search-chat/index.tsx— major rewrite
Phase 2:
save_preferencesandget_brand_infotoolschat_threadsmodule (visitor_id → messages persistence)POST /store/ai/chat/thread(sync on sign-in)
Phase 3+ (carried over):
- UI to set default chat platform in External Platforms
- Region-aware pricing in search results
- HeroSeenMarker cookie reliability
Streaming implementation
- Backend: AI-SDK
streamText({ model, system, tools, messages })and returnresult.toDataStreamResponse()— emits the AI-SDK data-stream format the@ai-sdk/reacthooks understand. - Storefront proxy: a Next.js API route that forwards body to backend
and pipes the response back unmodified. Keeps
MEDUSA_BACKEND_URLserver-only. - Client: AI-SDK
useChathook (or hand-rolledfetch+ stream reader) consuming/api/ai-chat. Renders partial assistant bubbles + tool-call indicators ("🔎 looked up …").
Open questions
- Body fit capture — sizes (XS/S/M/L) + relaxed/fitted only. Skip body-shape (apple/pear) — subjective and not actionable.
- Price slider currency — detect from
countryCode, default INR. - Save prefs server-side immediately if signed in? Yes, same pattern as the desk workspace.
- Tool-call latency UI — stream a "🔎 looking …" placeholder, then replace with the real bubble. Yes.
- OpenRouter free for tool use? Most don't reliably handle tool calls. Default the chat role to a DashScope or Cloudflare model.
- Should the agent navigate the customer? No — embed
<a href>in the response text, let the user click. Safer.
Phasing
- Phase 1 (this PR): onboarding + streaming endpoint +
search_productsonly. Brand knowledge ships as system-prompt injection. Visitor-id flows through but no DB persistence yet. - Phase 2:
save_preferences,get_brand_info,chat_threadsmodule, sign-in attribution. - Phase 3: UI control for default chat platform + region-aware pricing + HeroSeenMarker investigation.