Synaplan
Open-source AI platform for companies: self-hosted AI agents, GDPR-compliant chatbot widget, RAG knowledge base, WhatsApp and Outlook AI, MCP, multi-model routing. Docker, Kubernetes or hosted in Germ
Seasonal American cooking in Ashford, Ohio.
Translation pending
BusinessJavaScript · CSSGitHub @shash825OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Embeddable AI chat widget for restaurant websites — menu Q&A, reservations, and to-go ordering. Vanilla JS + one serverless proxy.
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
An embeddable chat widget for restaurant websites. Guests can ask about the
menu, get hours and directions, request a reservation, or build a to-go order —
all in a floating chat bubble, grounded in one config file per restaurant.
Built as a sales demo: it ships with a believable fictional restaurant
(The Copper Thistle) and a mock website behind it so it looks real on a
projector or a phone, and it repoints at a real client by editing one file.
The widget is restaurant-data.js + widget.css + widget.js + api/chat.js.
The rest is stage dressing for the pitch.Read the full README ↗An embeddable chat widget for restaurant websites. Guests can ask about the menu, get hours and directions, request a reservation, or build a to-go order — all in a floating chat bubble, grounded in one config file per restaurant.
Built as a sales demo: it ships with a believable fictional restaurant (The Copper Thistle) and a mock website behind it so it looks real on a projector or a phone, and it repoints at a real client by editing one file.
| File | What it is | Edit it? |
|---|---|---|
restaurant-data.js | All restaurant content — name, hours, address, full menu, policies | Yes — this is the file you change per client |
widget.css | Widget styling. Brand colors are CSS variables at the top | Yes — change the colors |
widget.js | The widget itself: chat, tools, order cart, API calls | Rarely |
api/chat.js | Serverless function that holds the API key and forwards to Claude | Rarely |
index.html | The mock restaurant site used as a demo backdrop | Demo only |
site.css | Styling for that mock site | Demo only |
The widget is restaurant-data.js + widget.css + widget.js + api/chat.js.
The rest is stage dressing for the pitch.
The key lives in an environment variable on the server, never in the front-end code. You set it once and never touch it again.
On Vercel (production):
ANTHROPIC_API_KEY = your key from https://console.anthropic.comLocally:
cp .env.example .env.local # then paste your key into .env.local
npm i -g vercel # once
vercel dev # serves the site AND /api/chat on localhost:3000
.env.local is gitignored. Do not commit it.
Why not just put the key in the JavaScript? Because anything in the browser is public. A key in
widget.jsis visible to every visitor via View Source or the Network tab, and GitHub's secret scanning will flag it the moment you push — Anthropic auto-revokes keys that leak, so you'd be rotating keys constantly.api/chat.jsexists so that never happens: the browser calls/api/chat, and only the server sees the key.There is a
directApiKeyfield at the top ofwidget.jsfor the rare case where you need to run the demo by double-clickingindex.htmlwith no server at all. It carries the same exposure problem. Leave it empty unless you're offline, and clear it before you commit or deploy.
Open restaurant-data.js. Everything the bot knows is in the RESTAURANT
object at the top, in plain English:
brand — name, tagline, cuisine, greeting, and the quick-reply buttonshours — one line per day, written however the restaurant writes itcontact — address, phone, email, and a free-text parking/directions notemenu — categories, each with items: name, description, price, tagspolicies — reservations, allergies, to-go timingorder — tax rate, currency symbol, pickup estimateNotes:
22, not "$22"). The widget adds the symbol.tags are free text and shown to guests as written. Keep them consistent:
vegetarian, vegan, gluten-free, gluten-free available,
contains nuts, dairy-free, spicy, shellfish.index.html) reads from the same file, so swapping data
reskins the mock site and the widget together.You do not need to touch widget.js to change restaurants.
Open widget.css. The first block is the entire theme:
:root {
--rct-brand: #1f4d3d; /* header, launcher, guest bubbles */
--rct-brand-hover: #17392d; /* a darker shade of the above */
--rct-brand-contrast: #ffffff; /* text on top of the brand color */
--rct-accent: #c2703d; /* prices, cart badge, highlights */
...
}
Change those four and the widget is reskinned. --rct-radius controls how
rounded everything is; --rct-font sets the typeface.
For a logo: set brand.logoUrl in restaurant-data.js to an image path and it
replaces the two-letter placeholder in the chat header. Leave it empty to keep
the initials from brand.logoInitials.
Three files and three lines:
<link rel="stylesheet" href="/widget.css">
<script src="/restaurant-data.js"></script>
<script src="/widget.js"></script>
The widget mounts itself into <body>; there's no container div to place and
no initialization call. Every CSS class is prefixed .rct- so it won't collide
with the client's existing styles.
To open the chat from the client's own buttons:
<button onclick="RestaurantChat.open()">Chat with us</button>
<button onclick="RestaurantChat.send('I would like to make a reservation')">Book a table</button>
The client's site needs /api/chat available on the same domain, or you point
CONFIG.apiEndpoint in widget.js at wherever you deployed it.
git init
git add .
git commit -m "Restaurant chat widget"
git remote add origin https://github.com/YOUR-USERNAME/YOUR-REPO.git
git push -u origin main
Then on Vercel: Add New → Project → import the repo. No framework preset, no
build command, no output directory — it's static files plus one function.
Add ANTHROPIC_API_KEY under Environment Variables and deploy.
Or from the terminal:
vercel --prod
The model doesn't just talk — it calls four tools that the widget executes in the browser:
| Tool | Effect |
|---|---|
add_to_order | Adds items to the cart. Matches loosely on names, rejects anything not on the menu |
remove_from_order | Removes an item or reduces its quantity |
show_order_summary | Renders the final order card with subtotal, tax, and total |
confirm_reservation | Renders the reservation request card |
That's why the order drawer stays in sync with the conversation — the cart is real state the model manipulates, not text it wrote.
The tool definitions plus the system prompt (which carries the whole menu) come
to roughly 2,200 tokens that are identical on every request. Rather than paying
full price to resend them each time, widget.js marks that prefix with
cache_control, so the API stores it after the first request and bills later
requests at about 10% of the input price for that chunk.
This only holds while the text is byte-identical. If you interpolate anything
per-request into systemPrompt() — a timestamp, the guest's name, "today is
Tuesday" — the cache silently stops matching and you quietly pay full price
again. Put dynamic content in a message instead.
To confirm it's working, watch the server log (vercel dev, or Vercel →
Logs). Every request prints a line like:
tokens — cache write: 2214, cache read: 0, uncached in: 31, out: 88
tokens — cache write: 0, cache read: 2214, uncached in: 47, out: 112
First request writes, everything after reads. If cache read stays at 0
forever, something in the prefix is changing between requests.
Nothing here books or submits anything for real. There is no backend, no
POS, no reservation system. confirm_reservation draws a card that says
"not booked yet — we'll text you to confirm," and show_order_summary says
"not sent to the kitchen yet." The system prompt instructs the model to say the
same. If a client wants real bookings, those two tool handlers in widget.js
are where you'd POST to OpenTable, Resy, Toast, or the client's own system.
There is no payment handling, and the bot is told to refuse card details.
Conversations live in memory only — nothing is written to localStorage,
sessionStorage, or any database. Closing the tab clears everything.
This is a pitch demo. It's fine on a URL you control. Before it's a product:
/api/chat. Right now anyone who finds the endpoint can spend
your tokens. Add per-IP limiting (Vercel KV, Upstash) and set a monthly
spend cap in the Anthropic console.ALLOWED_ORIGINS in api/chat.js with the
client's domain.| What you see | What it means |
|---|---|
| "The assistant is not configured yet" | ANTHROPIC_API_KEY isn't set on the server |
"could not be reached... start it with vercel dev" | You're on a plain static server, so /api/chat doesn't exist. Use vercel dev |
| "We're getting a lot of messages right now" | Rate limited by the API — wait a few seconds |
| Bot says it doesn't know something obvious | It's missing from restaurant-data.js. Add it there |
| Bot invents a dish | Shouldn't happen. If it does, check the item is really absent from the data, then tighten the grounding rules in systemPrompt() in widget.js |
The model is set in widget.js (CONFIG.model, currently claude-opus-5) and
allowlisted in api/chat.js. To use a cheaper model, change it in both places.