Welcome to the Jose Madrid Salsa developer docs — explore features, APIs, and deployment guides.
Jose Madrid SalsaJMS Docs

AI chat

Picante chat launcher, AI support, contact form, and live-team handoff behavior.

AI chat

The public storefront includes Picante, a playful pepper-penguin AI companion for product, fundraising, wholesale, and order-support questions. The animated mascot at the bottom-right of the page is also the chat launcher.

Launcher behavior

  • Picante walks within a bounded track along the bottom-right portion of the viewport.
  • Clicking or keyboard-activating Picante opens the existing chat panel.
  • Picante returns to an idle pose while the panel is open.
  • Visitors who request reduced motion see a stationary idle pose.
  • The launcher remains inside mobile safe areas and does not block the rest of the page.

Chat behavior

Picante introduces himself by name and responds with a concise, upbeat character voice that uses occasional pepper and penguin wordplay. Accuracy and clarity take priority over jokes, and Picante avoids mascot banter during sensitive support or human handoffs. Business-hours lookup and live-team handoff continue through the existing chat APIs. Closing the panel returns Picante to the launcher state.

Replies from Picante and from the team render as Markdown (components/chat/chat-markdown.tsx): bold, italics, lists, links, headings, blockquotes, code and GFM tables show as formatted text. Raw HTML in a reply is never rendered, and the output passes through rehype-sanitize. Messages the visitor types are shown exactly as typed.

Knowledge sources

Before each reply, /api/ai-chat searches Picante's knowledge index (lib/ai-rag/indexer.ts, cached for five minutes) and adds the five best matches to the system prompt. The index holds:

  • Active products, recipes, and retail locations from the database, plus the Find Us markdown.
  • A few built-in pages (fundraising, wholesale, shipping, contact).
  • Every document marked Ready under Admin → Training data (uploads, pasted text, and scraped URLs).
  • The customer Help Center articles shown at /help (apps/storefront/content/help), split into one entry per heading and ranked as store pages.
  • Every page of this documentation site (apps/docs/content/docs), split into one entry per heading.
  • Published posts from the developer blog (/developer/blog), but only while that blog is public (DEVELOPER_BLOG_PUBLIC in apps/storefront/lib/developer/visibility.ts). It is off, so none are indexed.

Documentation entries rank at half weight (lib/ai-rag/retriever.ts), so a store page that matches equally well comes first. The system prompt tells Picante to use the docs to explain how features work and to leave out implementation details unless a staff member or developer asks. The /api/ai-chat route and the inbox-triage routes that share the index list ../docs/content/docs/**/*.mdx and ./content/help/** in outputFileTracingIncludes so the pages ship with their serverless functions; add the same entry to any new route that calls getIndexedContent().

Contact form

Below Picante's opening message sits an "Or fill out a contact form by clicking here" button, for visitors who would rather leave their details than have a conversation. It swaps the panel to a standard contact form:

FieldRequired
Full NameYes
Email AddressYes
Company nameNo
Phone numberNo
Comments / questionsYes

Submitting posts to POST /api/send-email/contact with sourcePage: 'Picante Chat', the same endpoint the public /contact page uses. The submission is rate limited by IP (5 per minute), validated with Zod, stored as a Conversation, and emailed to the business inbox with the sender's address as reply-to. On success the panel shows a confirmation; closing it returns the visitor to Picante, and "Back to chat" does the same from the form.

The notification email (emails/contact-form.tsx) mirrors the layout the office already works from: logo, a "Contact form submission" heading, a labelled Details block, the comment body, and an "Open store" button. Its subject is <email> submitted the form from your <page> page, where the page comes from the optional sourcePage field. Because this email is an internal alert to the business inbox rather than a subscriber email, it carries no footer unsubscribe link; the List-Unsubscribe header applied by lib/email/client.ts is unchanged.

This is separate from the "Talk to a human" handoff, which opens a live chat thread during business hours instead of sending mail.

Asset

The storefront uses an optimized three-row derivative at public/images/ai-chat/picante-spritesheet.webp, containing only idle and left/right walking frames. The complete version 2 pet package remains the source asset outside the website bundle.

How is this guide?

Edit on GitHub

Last updated on

On this page