Pebble Chat
Answer the small questions, hand over the big ones.
Pebble Chat is a small chat widget for independent shops. It answers shipping, returns, workshop and care questions from a FAQ the owner writes, and when it is not sure it says so and offers to pass the question to the owner by email. I built it around a ceramics shop, Clayform Studio, to show a deliberately simple and well-bounded LLM integration.
- React.js
- Node.js
- Express.js
- LLM API
- REST API
- SQLite
Overview
- Business problem
- A small shop gets the same dozen questions every week: how long delivery takes, whether a mug can go in the dishwasher, how to book a workshop. The owner answers them by hand in the evenings, and a visitor who does not get a quick reply often leaves without buying or booking.
- Product objective
- Answer the repeated questions instantly and accurately from the shop's own policies, never invent a policy, and make sure anything the bot cannot answer reaches the owner with enough context to reply quickly.
- Target users
- Independent shop owners with a small website and no support team, and the visitors who browse their shop looking for a quick answer before ordering or booking.
- Solution
- A short system prompt plus the owner's FAQ text sent with every question to an LLM, a widget that streams the reply, a rule that anything outside the FAQ gets a hand-off instead of a guess, and an owner view that shows every conversation and every unanswered question.
Project classification
- Project type
- AI chat widget
- Industry
- Small business / e-commerce
- Frontend
- React.js widget loaded by a single script tag on the shop site
- Backend
- Express.js service that builds the prompt and makes one LLM call per message
- Database
- SQLite for the conversation log and FAQ entries (MySQL is a drop-in for a hosted setup)
- Architecture
- Widget, REST API, prompt builder and LLM API; no retrieval layer
User roles
- Visitor (shop customer)
- Shop owner
My role
- Product scoping: kept it to a prompt, a FAQ and a hand-off instead of adding retrieval, accounts or analytics.
- Backend design: Express.js endpoint, prompt assembly, message caps and the hand-off rule.
- Prompt design: system prompt, grounding instructions, refusal wording and basic injection hardening.
- Database design: a small SQLite schema for conversations, messages, FAQ entries and unanswered questions.
- Frontend: the embeddable widget and the owner screens in React.js.
- Integration: LLM API call with streaming, and the email hand-off.
- This is a self-initiated concept; there is no client, live deployment or user base.
Key features
Widget open on the shop page. Interface designed and built by Vikrant A widget that lives on the shop page
The widget opens over the shop's own pages and answers in the shop's voice. Replies stream in, stay short, and point to the relevant policy. If the question is outside the FAQ, the bot says it is not sure and offers to send the question to the owner instead of guessing.
Conversation log. Interface designed and built by Vikrant Every conversation, readable at a glance
The owner sees each conversation with its first question, the outcome (answered or handed off) and a flag for answers that look wrong. Opening one shows the full exchange, so a bad answer can be traced to a missing or unclear FAQ entry.
FAQ editor. Interface designed and built by Vikrant A FAQ editor that is just text
The shop's knowledge is a list of plain question-and-answer entries grouped by topic, such as shipping, returns, workshops and care. There is no model training involved: editing an entry changes the text sent with the next question, and a character counter shows how much of the context budget the FAQ uses.
Unanswered questions inbox. Interface designed and built by Vikrant Unanswered questions become FAQ entries
Every question the bot could not answer lands in an inbox with the visitor's message and, if they left one, their email. The owner replies by email, then turns the question into a FAQ entry with one click so the next visitor gets an answer straight away.
Widget settings and embed. Interface designed and built by Vikrant Settings and a one-line embed
The owner sets the greeting, the bot's tone, the hand-off email and the message limits, and copies a single script tag to paste into the shop's site. The limits keep cost predictable without needing a billing system.
Also in the product
- Chat widget embedded with one script tag
- Answers drawn only from the owner's FAQ and policy text
- Honest fallback with a hand-off to the owner's email
- Conversation log with a flag for answers that need a fix
- Unanswered questions inbox that turns gaps into new FAQ entries
- Per-visitor and per-day message caps
- Basic prompt-injection hardening
User flow
- Owner writes the FAQ and sets the hand-off email and message limits
- Owner pastes one script tag into the shop's website
- Visitor opens the widget and asks a question
- Server checks the message caps and builds the prompt from instructions, FAQ and recent messages
- LLM reply streams back to the widget
- If the answer is not in the FAQ, the bot offers a hand-off and the visitor can leave an email address
- The conversation is saved; unanswered questions go to the inbox
- Owner reviews the log, replies by email and adds missing answers to the FAQ
Technical architecture
- Shop website with a single script tag
- React.js chat widget (streams replies)
- Express.js REST API: validation, message caps, hand-off
- Prompt builder: system instructions, FAQ text, last few messages
- LLM API (one call per visitor message)
- SQLite log: conversations, messages, FAQ entries, unanswered questions
- Owner dashboard (React.js) and email hand-off
Database and backend
Prompt and grounding
- One system prompt states the shop's name, tone and the rule: answer only from the FAQ below, never invent prices, dates or policies.
- The FAQ is plain text appended to the system prompt, small enough to fit in every request, so no retrieval or embeddings are needed at this size.
- When the FAQ does not cover a question, the model is told to reply with a fixed marker and a short apology; the server swaps the marker for the hand-off prompt.
- Only the last few messages of the conversation are sent, to keep each request short and cheap.
Hand-off and unanswered questions
- A hand-off creates an unanswered-question row with the visitor's message, the conversation id and an optional email address.
- The shop owner is emailed a short summary with a link to the conversation.
- Hand-offs are rate limited per conversation so a visitor cannot trigger a flood of emails.
Cost control and basic hardening
- Message caps per conversation and per visitor per day, plus a maximum visitor message length, enforced on the server.
- A cap on output tokens and a request timeout, with a friendly fallback message if the LLM API fails.
- Visitor text is passed as a user message and never concatenated into the system prompt; a short instruction tells the model to ignore requests to change its rules.
- Basic checks for obvious injection phrases and for replies that quote the system prompt; these reduce the problem but do not make it impossible.
- The API key stays on the server; the widget only talks to the shop's own endpoint.
Data model
- Conversation, Message, FaqEntry, UnansweredQuestion, Setting.
- Messages store role, text and a created timestamp; no payment or account data is collected.
- Visitor email is stored only when the visitor chooses to leave one for a hand-off.
Challenges and solutions
Keeping answers inside the shop's real policies
The prompt tells the model to answer only from the FAQ text, to quote policy wording where it can and to use a fixed fallback when the answer is not there. The conversation log makes wrong answers easy to spot, and the fix is usually a clearer FAQ entry rather than a longer prompt.
Deciding when to hand off instead of answering
The model returns a marker when the FAQ does not cover the question, and the server turns that into an offer to email the owner. Questions about orders, refunds on a specific purchase or anything personal always hand off, because the bot has no access to order data.
Cost control on a small shop's budget
Short context (instructions, FAQ and a few recent messages), an output token cap, a conversation message limit and a daily limit per visitor. A shop with a short FAQ pays for a small, predictable request every time.
Prompt-injection basics for a public widget
Visitor text never goes into the system prompt, the instructions say to ignore attempts to override them, and replies are checked for leaked instructions. The bot has no tools and no private data beyond the public FAQ, so the worst case is a rude or off-topic reply.
A FAQ that stays current
The unanswered inbox shows what visitors actually ask, and converting a question into an FAQ entry is one click. The FAQ editor shows how much of the context budget is used so the owner knows when to trim.
Results
- A compact case study of a bounded LLM integration: one prompt, one FAQ, one call per message, with clear limits.
- Shows honest scoping, where the bot says it does not know and hands the question to a person.
- A small data model and API that a single developer can host and maintain for a small shop.
- Illustrates cost and safety basics (message caps, hardening, no secrets in the widget) without overstating what they guarantee.
Qualitative outcomes only; no usage or revenue figures.
Visual identity
- Primary #C65D3B
- Secondary #2B2623
- Accent #8FAE8B
- Background #FBF7F3
Outfit for headings and the widget header, Inter for body text. A rounded pebble shape holding a speech-bubble tail. Warm, soft-cornered widget and a calm owner dashboard with a narrow sidebar and plain tables.
Related projects
- Demonstration ProjectAI / automation
AgentForge
Build, ground and monitor AI agents with RAG, tools, a testing playground and usage tracking.
- Next.js
- React.js
- TypeScript
- Node.js
- Laravel
- PostgreSQL
- pgvector
- RAG
- LLM APIs
- REST API
- Webhooks
- Portfolio ProjectFood / subscription commerce
Cadence Kitchen
Meal-plan subscription platform with skip, pause and nutrition tracking.
- React.js
- TypeScript
- React Query
- Node.js
- Express.js
- MongoDB
- REST API