A sales dashboard builder for Northstar, a fictional multi-brand retailer. Describe what you want to see ("Show me net sales vs target by region for last month") and it builds a live dashboard from a Neon Postgres database: KPIs, charts and tables whose numbers load fresh from the database every time the dashboard opens. Ask for a change in the chat and the same dashboard updates in place.
Built on Next.js, assistant-ui and OpenUI. Adapted from OpenUI's assistant-ui-dashboards example, with its npm/GitHub data tools replaced by SQL-backed sales tools.
- Home: a one-sentence builder, "Show me [metric] by [split] for [range]", with a chip per slot and a Build dashboard button.
- Agent view: three panes.
- A collapsible icon rail on the left lists your recent dashboards and has View connected data at the bottom.
- The open dashboard fills the centre.
- The conversation sits on the right and can be collapsed to a Chat button.
- Status notch: a notch at the top of the dashboard shows "Fetching live data…", "Building your dashboard…", "Updating your dashboard…" and "Loading live data…". When a dashboard settles, it shows how many fewer tokens the dashboard's OpenUI Lang program took than the HTML it renders to.
- Working state: while the agent works and no dashboard is open yet, the centre shows a subtle WebGL backdrop of drifting "+" symbols.
- Follow-ups: suggested follow-up questions sit above the composer as a card stack that fans out on hover.
- Connected data: a full-page popup that browses the database's tables, with an overview page and paged rows.
-
Node.js 22.13+ and npm.
-
A Thesys API key (
THESYS_API_KEY). It runs the chat model and the dashboard builder through OpenUI Gateway. Create one in the Thesys Console, or runnpm run generate:apiKey, which writes it to.env.local. The default models are paid (credits). To avoid credits, use BYOK or set the model variables below to a free model. -
A Postgres database with the
retailschema (DATABASE_URL). The app reads these tables:orders,order_itemsandcustomersproductsandproduct_variantsreturnsandpromotions- monthly sales targets
The original runs on Neon with about 100k orders. Ask the person who shared this repo for a connection string, or point it at your own copy of the schema. Queries are parameterized and read-only, are cached for 3 minutes, and time out after 10 seconds.
npm install
cp .env.example .env.local # then fill in THESYS_API_KEY and DATABASE_URL
npm run devOpen http://localhost:3000.
Optional settings in .env.local:
| Variable | What it does | Default |
|---|---|---|
OPENUI_MODEL |
Chat model, as a Gateway provider/model id |
google/gemini-3.6-flash-free |
OPENUI_DASHBOARD_MODEL |
Model that builds dashboards | anthropic/claude-sonnet-5.5 |
OPENUI_FOLLOWUPS_MODEL |
Model that writes follow-up suggestions | OPENUI_MODEL |
NEXT_PUBLIC_BASE_PATH |
Path the app is served under, such as /demo/sales-dashboard, read at build time |
the root |
THESYS_BASE_URL |
Another OpenUI Cloud deployment | https://api.thesys.dev |
Try:
- "Show me net sales vs target by region for last month."
- "Top products by category for last quarter."
- "Returns by brand for last year."
- With a dashboard open: "Add a 6-month trend by region."
browser (assistant-ui) /api/chat (AI SDK) OpenUI Cloud
Thread ──messages──────────────────► streamText, tools from the toolkit
model calls create_dashboard ─execute──► dashboards.create()
◄─ result: a sentence + the dashboard's reference ─┘
card (the tool's render) ◄─────────── streamed with the reply
opens the panel ─► LiveArtifactRenderer streams the dashboard by its reference
Query() ─► /api/interpreter ─► SQL sales tools ─► Postgres (retail schema)
- The chat sends the thread to
/api/chat. That route streams a reply from OpenUI Gateway with two tools,create_dashboardandedit_dashboard. create_dashboardruns on the server and starts generating the dashboard in OpenUI Cloud. It returns as soon as the dashboard has an id.- The tool's card appears in the chat and opens the dashboard in the centre pane.
LiveArtifactRendererthen streams the dashboard as it's generated. - The dashboard loads its numbers with
Query()calls. Each one goes to/api/interpreter, which runs the matching sales tool insrc/lib/data/sales.tsagainst the database. - For a change, the model calls
edit_dashboardwith the dashboard's id, and the notch shows "Updating your dashboard…" until the new version is in.
OpenUI Cloud stores each dashboard's definition, not its data. Numbers reach the browser only through /api/interpreter, from your server. Chats are kept in the browser's localStorage, with the chat each dashboard was made in, so picking a dashboard in the rail opens its chat.
| File | Purpose |
|---|---|
src/lib/data/sales.ts, db.ts |
SQL-backed sales tools: KPIs, trends, breakdowns, top products, returns, targets vs actuals, customers, promotions |
src/lib/openui.ts |
OpenUIClient, the dashboard definition and base URL |
src/lib/visitor.ts, src/proxy.ts |
Each browser's anonymous visitor id, which owns its dashboards |
src/lib/base-path.ts |
The base path, for hand-written URLs |
src/lib/toolkit.tsx |
create_dashboard and edit_dashboard: schema, execute, render |
src/lib/prompt.ts |
Chat instructions and the data dashboards can show |
src/app/page.tsx, src/components/home-sentence.tsx |
Home page and the sentence builder |
src/components/app.tsx |
Runtime, providers, and the three-pane layout |
src/components/dashboard.tsx |
Chat card, dashboard panel and status notch |
src/components/dashboard-sidebar.tsx |
Icon rail of recent dashboards |
src/components/notch.tsx, shader-backdrop.tsx |
Status notch and the working-state backdrop |
src/components/database-view.tsx |
Connected-data popup |
src/app/api/token-savings/route.ts |
Token count of OpenUI Lang vs rendered HTML |
src/app/api/followups/route.ts |
Follow-up suggestions |
npm run generate builds the component spec in src/generated/ (ignored) before dev and build.
The app binds to loopback. Each browser gets an anonymous visitor id in a cookie, which keeps visitors' dashboards apart but doesn't prove who anyone is. To put it on the internet:
- Add authentication, and use the signed-in user's id in place of the visitor id (
src/lib/visitor.ts). - Add rate limits to the API routes.
- Use a read-only database role for
DATABASE_URL.