Case study

angrynirds

The website for a small studio that builds websites, online stores and AI assistants for small businesses. It prices a project from one price book, builds a Stripe Checkout deposit from that price, and runs the AI assistant the studio sells to its clients.

live siteprivate repositoryTypeScriptNext.js on Vercel

The problem.

A small business owner who wants a website often cannot get a price without booking a sales call first, and many leave at that step.

A chat assistant that does answer can make it worse. It can state a price the business never set, or promise results the business cannot deliver, and the business is then held to words it never wrote.

How it solves it.

One price book
Every price on the site comes from one file, data/pricing.ts. The service pages, the quote wizard and the assistant all read from it, so they cannot disagree.
A quote wizard
The visitor answers plain questions about the business and the project. The wizard maps the answers to line items, prices them from the price book, and shows one project price plus any monthly cost.
A deposit through Stripe Checkout
The visitor can pay the deposit or the full price. The server recomputes the amount from the quote, so the browser never sends an amount.
An assistant that only quotes tool results
The assistant answers questions, looks up prices, books calls, opens checkout links and collects project files. It can state a price only if a tool returned that price in the same turn. Any other dollar figure or discount percentage, and any promise about search rankings, traffic or conversions, is replaced with a fixed safe reply, and the case is sent to a person.
A person when it matters
The assistant can hand a conversation to the studio, which receives it by email with the end of the conversation attached. When a question touches a policy the business has not decided, the knowledge search returns no excerpts and a plain "not settled yet" line instead, so the assistant has nothing to improvise from.

Who it helps.

Small service businesses
A site that quotes a project and takes a deposit without a sales call.
Buyers
A real price before they talk to anyone, and an assistant that cannot invent one.
The studio
It sets up the same assistant for each client as a separate instance, with that client's prices, documents and on/off switch. The reply guard and the spend limits are shared code and cannot be changed per instance.

Technical details.

Stack
TypeScript, Next.js 16 (App Router), React 19, Tailwind CSS v4, the Vercel AI SDK with the Anthropic provider, Vitest and Biome. Hosted on Vercel. Leads and payment records are stored in Vercel Blob, and email goes out through Resend.
Tools
The model calls eleven tools, among them get_price, list_services, search_knowledge_base, create_booking, create_checkout_link and escalate_to_human. Tool output is cleaned before it is passed to the model, so internal notes in the price book never reach a customer.
Stream guard
Each streamed text part is held until it ends, then checked together with everything already sent in that turn, so a figure split across parts is still caught. A dollar figure or discount percentage counts only if it appears in a tool result from the same turn. A part that fails is replaced before the customer sees it, and an escalation is recorded.
Prompt-injection check
Incoming messages are checked for attempts to override instructions, requests to reveal the system prompt, role changes and demands for a discount. A message with two or more flags is escalated. An oversized message is refused.
Spend limits
Checked before any tokens are bought: a request limit per IP over a rolling ten-minute window, a daily request ceiling per server instance, a cap on how much conversation history is accepted, a cap on output tokens per reply, and a cap on model steps per turn. The counters live in server memory, so the real ceiling is the number of running instances times these limits, and the spend limit on the API key is the backstop.
Three switches
The assistant answers only when all three are set: NEXT_PUBLIC_CONCIERGE at build time mounts the chat launcher, CONCIERGE_ENABLED at request time lets the endpoint answer, and ANTHROPIC_API_KEY is the key replies are billed to. Without the last two, the endpoint returns 503 before any spend. A status endpoint reports whether the assistant is answering, so the widget never shows a chat box in front of a closed endpoint.
Stripe Checkout
The checkout route recomputes the quote with the same pricing function the wizard uses, then creates a Checkout session through the Stripe REST API with a server-built order summary attached. A webhook checks the Stripe signature with a five-minute replay window, saves the payment or refund record under its Stripe id, and emails the studio.
Per-client config
An instance is one config object: system prompt, price book, knowledge documents, booking and checkout providers, where escalations go, allowed origins and an on/off variable. One route serves any registered instance, with a CORS allowlist for client sites on other domains. A salon demo site is the worked example.
Tests
Vitest tests cover the stream guard, the percentage and guarantee checks, the spend limits, input screening, checkout, the Stripe webhook, the pricing engine and the wizard. Offline evals run in the same suite against a mock model and attack the input screen, the validator, the stream guard and the tool schemas.
Live evals
A separate suite sends fixed scenarios to the production model and grades each reply with fixed graders, which are plain string checks tested against known-bad replies, and with an LLM judge. A calibration test gives the judge replies whose verdict is already known, about half of which must fail, so a judge that passes everything is caught. The live suite spends tokens and runs only when asked.
CI
GitHub Actions runs lint and format checks, the test suite and a production build on every pull request, and tests and builds each demo client site.
Source
The repository is private. The live site is at angrynirds.com.