Skip to content

Commit e00836c

Browse files
authored
SaaS banking demo: customer-ready on CopilotKit v2 (FOR-138) (CopilotKit#5180)
## Summary Makes the `examples/showcases/banking/` SaaS demo customer-ready by migrating it to **CopilotKit v2** and polishing it into a credible, reliable reference. Closes the non-memory gaps from FOR-133's assessment (FOR-138). - **v2 migration**: v2 Hono runtime route (`BuiltInAgent` + `createCopilotHonoHandler` + `InMemoryAgentRunner`), `CopilotKit` provider + `CopilotPopup`, hooks moved to `@copilotkit/react-core/v2` (`useAgentContext`, `useHumanInTheLoop`, `useComponent`, Zod params), on `workspace:*` packages. - **File-backed data store**: domain data in `src/data/seed.json` behind a typed `src/lib/store.ts` (in-memory, resets to seed on restart). Clean seam left for the memory track (FOR-137). - **Credible identity + data**: Northwind Finance / Alex Morgan / `@northwind.example`, refreshed transaction dates + future card expiries, `Intl` currency formatting. - **Dropped** the SQL page + fake-MSA RAG feature (and committed ServiceNow secret) — focused fintech story. - **Framework/build**: Next 16 async route `params`, Tailwind v4 (so the v2 stylesheet imports normally), correct `next`/`react` versions, single lockfile, accurate v2 README. - **Tests**: LLM-free Playwright smoke test (also fails on hydration/uncaught errors). ## Verified live (real OpenAI key) Full golden path works end-to-end: chat renders → "show transactions for card 4242" renders the generative-UI list → "add a card" → HITL approval card → Approve → new card appears in the grid. 0 console errors. Build + typecheck + smoke test green; no committed secrets. Three v2 chat-rendering bugs were found and fixed by running it live: use the full `CopilotKit` provider (not the low-level `CopilotKitProvider`, which omits `ThreadsProvider`); set `useSingleEndpoint={false}` to match the multi-endpoint route; and use `useComponent` with a `deps` array for display-only generative UI (avoids a stale-closure empty render). ## Non-goals (deliberately out of scope) - Real auth. - Conversation threads + long-term/self-learning memory → FOR-137. - Deleting the duplicate `enterprise-brex` demo + repointing its deploy. ## Test plan - [ ] `pnpm --filter demo-saas-copilot build` succeeds - [ ] `pnpm --filter demo-saas-copilot exec tsc --noEmit` clean - [ ] `pnpm --filter demo-saas-copilot test:e2e` passes - [ ] With `OPENAI_API_KEY` set: run the golden path (transactions gen-UI, add-card HITL → approve → grid update) ## Known minor follow-ups - Removing deprecated `showDevConsole` surfaces the v2 Web Inspector / announcement banner. - Newly-added cards get a near-term generated expiry. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents df78840 + d5f11c1 commit e00836c

45 files changed

Lines changed: 1343 additions & 30951 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

examples/showcases/banking/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,11 @@ node_modules/
55
.DS_Store
66

77
.next
8+
9+
# TypeScript incremental build cache
10+
tsconfig.tsbuildinfo
11+
12+
# Playwright
13+
test-results/
14+
playwright-report/
15+
playwright/.cache/
Lines changed: 91 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,115 @@
1-
# CopilotKit Demo App
1+
# Northwind Finance — CopilotKit v2 Banking Demo
22

3-
This demo application highlights the capabilities of CopilotKit by demonstrating how to build an app that emphasizes authorization, supports multiple operations, and incorporates generative UI elements. The banking application scenario serves as a practical example of these features in action.
3+
A customer-ready reference demo showing how to build a SaaS app with an embedded
4+
AI copilot on top of CopilotKit v2. The app — "Northwind Finance" — models a
5+
corporate banking dashboard where role-based users can view transactions,
6+
manage credit cards, and (for admins) manage team members. The copilot is
7+
wired into the same UI: it reads app context, calls typed tools to render
8+
generative UI, and asks the user to approve sensitive actions via
9+
human-in-the-loop.
410

5-
## Installation and running
6-
7-
To get started, install the package and run the development server:
8-
9-
```bash
10-
pnpm i
11-
```
12-
13-
and then
11+
## Running locally
1412

1513
```bash
16-
pnpm dev
14+
export OPENAI_API_KEY=your-key
15+
pnpm install # from the repo root — this demo is a workspace package
16+
pnpm --filter demo-saas-copilot dev
1717
```
1818

19-
Open [http://localhost:3000](http://localhost:3000) in your browser to view the application.
19+
Then open <http://localhost:3000>.
2020

21-
Please ensure to `export OPENAI_API_KEY=your-key` to enable OpenAI functionality.
21+
The demo runs against the workspace versions of `@copilotkit/*` (see the root
22+
`pnpm-workspace.yaml`). The seed dataset lives in memory and resets every time
23+
the server restarts.
2224

23-
## Key Features and Their Locations
25+
## Architecture at a glance
2426

25-
### Authorization and Contextualization
26-
27-
Authorization is key in this app, with users assigned to different departments and roles.
27+
```
28+
┌─────────────────────────────────────────────────────────────────┐
29+
│ Frontend (Next.js 16, React 19, Tailwind v4) │
30+
│ CopilotKitProvider + CopilotPopup (@copilotkit/react-core/v2) │
31+
│ ├── useAgentContext → share user / page state with agent │
32+
│ ├── useFrontendTool → generative UI (showTransactions) │
33+
│ └── useHumanInTheLoop → approval flows (addNewCard, …) │
34+
└─────────────────────────────┬───────────────────────────────────┘
35+
│ AG-UI over SSE
36+
37+
┌─────────────────────────────────────────────────────────────────┐
38+
│ Runtime (Hono, same Next process) │
39+
│ src/app/api/copilotkit/[[...slug]]/route.ts │
40+
│ BuiltInAgent + CopilotRuntime + createCopilotHonoHandler │
41+
│ (from @copilotkit/runtime/v2) │
42+
└─────────────────────────────┬───────────────────────────────────┘
43+
44+
45+
┌─────────────────────────────────────────────────────────────────┐
46+
│ Data layer │
47+
│ src/data/seed.json → seed cards, team, policies, txns │
48+
│ src/lib/store.ts → typed, in-memory store (resets) │
49+
│ src/app/api/v1/* → REST surface │
50+
│ (cards, transactions, │
51+
│ users, policies) │
52+
│ src/lib/identity.ts → Northwind branding strings │
53+
└─────────────────────────────────────────────────────────────────┘
54+
```
2855

29-
Explore how user roles and departments impact the app's behavior. Navigate to the bottom left corner and switch between users. This is done through an app-wide context provided to the co-pilot.<br>
30-
Implemented in `copilot-context.tsx`, it's a wrapper component that includes `useCopilotReadable` and `useCopilotAction` hooks for anything app-wide.
56+
## Key features and where to find them
3157

32-
### Multiple operations and information
58+
### App-wide context for the copilot
3359

34-
The application offers various operations that can be performed through the co-pilot on different pages. Here are some examples:
60+
`src/components/copilot-context.tsx` shares the current user and the current
61+
page with the agent via `useAgentContext`, so the LLM can adapt its responses
62+
to the logged-in role and the route the user is on. The Northwind brand and
63+
assistant greeting are centralized in `src/lib/identity.ts`.
3564

36-
- On the `/cards` page, you can request the co-pilot to change a credit card's PIN or add a new card. Note that adding a new card may have different outcomes depending on the user's role.
37-
- On the `/team` page, the co-pilot can assist with inviting a new member, editing a member's role or department, or removing a member.
65+
Switch between users from the bottom-left avatar in the sidebar to see how
66+
role (Admin vs Assistant) changes what the copilot will agree to do.
3867

39-
### Generative UI
68+
### Generative UI`showTransactions`
4069

41-
The app demonstrates the power of Generative UI through two main examples in `cards/page.tsx`:
70+
The cards landing page at `src/app/page.tsx` registers
71+
`useFrontendTool({ name: "showTransactions", render })`. When you ask the
72+
copilot something like _"Show me transactions for my card ending 4242"_, the
73+
LLM calls the tool and the rendered list IS the answer — there is no
74+
follow-up paragraph restating the data.
4275

43-
- Transaction Viewing:
44-
- The `showTransactions` `useCopilotAction` exemplifies the ability to present information via a component, eliminating the need for additional text or LLM follow-up.
45-
- Trigger this feature by requesting the co-pilot to display all transactions for a specific card, identified by its last 4 digits.
46-
- Transaction Approval:
47-
- The `showAndApproveTransactions` `useCopilotAction` demonstrates the capacity to solicit user action, specifically the approval of transactions. This process is done one transaction at a time, ensuring all are resolved.
48-
- Engage this feature by asking the co-pilot to display all transactions awaiting approval, such as "Show me all transactions pending my approval".
76+
### Human-in-the-loop — `addNewCard` and `navigateToPageAndPerform`
4977

50-
### Handling Unavailable Actions
78+
- `useHumanInTheLoop({ name: "addNewCard", render })` in `src/app/page.tsx`
79+
shows the "add card" confirmation card directly in chat; the user clicks
80+
Approve / Cancel and the result is sent back to the agent. The team page
81+
(`src/app/team/page.tsx`) uses the same pattern for removing a member and
82+
changing a member's role or team (inviting a member is a UI-only dialog
83+
flow, not an agent tool).
84+
- `useHumanInTheLoop({ name: "navigateToPageAndPerform" })` in
85+
`src/components/copilot-context.tsx` is the cross-page fallback: if the user
86+
asks for an operation that lives on another page (e.g. "change my Visa PIN"
87+
from the team page), the copilot asks for permission to navigate, then
88+
redirects with an `?operation=…` query param so the destination page can
89+
open the right dialog.
5190

52-
The app handles unsupported actions by redirecting users to the relevant page, optionally starting the task. Explore this on the main page:
91+
### Role-based behaviour
5392

54-
- Ask the co-pilot to change a card's PIN (e.g., "Let's change the pin for my Visa"), and it will redirect you to the cards page with a change PIN popup.
55-
- Request assigning a policy to a card, and the co-pilot will acknowledge its inability to assist and offer guidance.
93+
Authorization is communicated to the agent through `useAgentContext` rather
94+
than enforced on the LLM by prompt alone. The REST handlers in
95+
`src/app/api/v1/*` enforce the same rules on the server side, so a curious
96+
user (or a hallucinating model) cannot bypass them.
5697

57-
This feature is implemented in `copilot-context.tsx` as `navigateToPageAndPerform`.
98+
## Backend & data
5899

59-
## SQL Query Generator
100+
- All read/write goes through `src/lib/store.ts`, which exposes typed helpers
101+
— readers like `cards()`, `team()`, `policies()`, `transactions()` and
102+
mutators like `findCard`, `updateCardPin`, `assignPolicyToCard`,
103+
`updateTransaction` — over an in-memory copy of `src/data/seed.json`.
104+
- The REST endpoints under `src/app/api/v1/*` (cards, transactions, users,
105+
policies) are thin handlers around the store and are what the UI uses.
106+
- There is no database. State resets on every server restart — this keeps the
107+
demo deterministic for screenshots, e2e tests, and customer walkthroughs.
60108

61-
The SQL query generator at `/sql` leverages co-pilot chat with Generative UI to convert user questions into SQL queries. Users can pose questions like "Show me all transactions for my visa ending with 4242" or "Let's find the pending transaction for the policy assigned to the card ending with 4242" and receive a corresponding SQL query. The query can be copied or executed directly (execution functionality is currently unavailable).
109+
## Tests
62110

63-
## Backend and data
111+
End-to-end Playwright smoke tests live under `e2e/` and can be run with:
64112

65-
The `/api/v1` path serves as the primary endpoint for API requests, handling various routes that interact with the application's data. Notably, the `data.ts` file contains hardcoded data that is utilized throughout the application.
113+
```bash
114+
pnpm --filter demo-saas-copilot test:e2e
115+
```

examples/showcases/banking/components.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"rsc": true,
55
"tsx": true,
66
"tailwind": {
7-
"config": "tailwind.config.ts",
7+
"config": "",
88
"css": "src/app/globals.css",
99
"baseColor": "neutral",
1010
"cssVariables": false,
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { test, expect } from "@playwright/test";
2+
import type { ConsoleMessage, Page } from "@playwright/test";
3+
4+
/**
5+
* CI-safe smoke test for the Northwind banking showcase.
6+
*
7+
* What this covers:
8+
* 1. App boots and the dashboard renders credible content (brand + cards UI).
9+
* 2. The CopilotKit v2 popup launcher opens the modal.
10+
* 3. The three configured suggestion pills are visible.
11+
*
12+
* What this intentionally does NOT do:
13+
* - Send any chat message
14+
* - Click any suggestion (which would invoke the agent / hit OpenAI)
15+
* - Exercise any tool
16+
*
17+
* That keeps the test runnable in CI without secrets.
18+
*/
19+
20+
// Console-error filtering: the page may legitimately log network errors for
21+
// the /api/copilotkit endpoint (e.g. if the popup pings it on open and the
22+
// runtime fails because OPENAI_API_KEY is a dummy value), and Next.js dev mode
23+
// prints various non-fatal warnings. We only fail on genuine page/script
24+
// errors that indicate the app itself is broken.
25+
const IGNORED_ERROR_PATTERNS: RegExp[] = [
26+
/favicon/i,
27+
/\/api\/copilotkit/i,
28+
/Failed to load resource/i,
29+
/net::ERR_/i,
30+
/Download the React DevTools/i,
31+
];
32+
33+
function isIgnoredError(message: ConsoleMessage): boolean {
34+
if (message.type() !== "error") return true;
35+
const text = message.text();
36+
return IGNORED_ERROR_PATTERNS.some((re) => re.test(text));
37+
}
38+
39+
function collectConsoleErrors(page: Page): string[] {
40+
const errors: string[] = [];
41+
page.on("console", (msg) => {
42+
if (msg.type() === "error" && !isIgnoredError(msg)) {
43+
errors.push(msg.text());
44+
}
45+
});
46+
return errors;
47+
}
48+
49+
function collectPageErrors(page: Page): string[] {
50+
const errors: string[] = [];
51+
page.on("pageerror", (err) => {
52+
errors.push(err.stack ?? err.message);
53+
});
54+
return errors;
55+
}
56+
57+
test.describe("banking showcase smoke", () => {
58+
test("dashboard renders and popup opens with suggestions", async ({
59+
page,
60+
}) => {
61+
const consoleErrors = collectConsoleErrors(page);
62+
const pageErrors = collectPageErrors(page);
63+
64+
await page.goto("/");
65+
66+
// Brand assertion: the document title is the Northwind Finance brand.
67+
await expect(page).toHaveTitle(/Northwind/);
68+
69+
// The credit-cards page renders an h1 "Credit Cards" and an "Add Card"
70+
// dropdown — that's our credible-content check that the dashboard is up.
71+
await expect(
72+
page.getByRole("heading", { name: "Credit Cards", level: 1 }),
73+
).toBeVisible();
74+
75+
// Open the CopilotKit popup. v2 renders a fixed launcher button with
76+
// data-testid="copilot-chat-toggle" (see CopilotChatToggleButton.tsx).
77+
const launcher = page.getByTestId("copilot-chat-toggle");
78+
await expect(launcher).toBeVisible();
79+
await launcher.click();
80+
81+
// The popup modal exposes the configured assistant title as its aria-label
82+
// (modalHeaderTitle = IDENTITY.assistant = "Northwind Copilot"). We assert
83+
// on the dialog rather than visible text since the title may be rendered
84+
// as aria-only.
85+
await expect(
86+
page.getByRole("dialog", { name: /Northwind Copilot/i }),
87+
).toBeVisible();
88+
89+
// The three suggestion pills configured by BankingSuggestions render as
90+
// buttons with data-testid="copilot-suggestion" (see
91+
// CopilotChatSuggestionPill.tsx). Their labels are the suggestion titles.
92+
const suggestions = page.getByTestId("copilot-suggestion");
93+
await expect(suggestions).toHaveCount(3);
94+
await expect(
95+
suggestions.filter({ hasText: "View transactions" }),
96+
).toBeVisible();
97+
await expect(suggestions.filter({ hasText: "Add a card" })).toBeVisible();
98+
await expect(
99+
suggestions.filter({ hasText: "Assign a policy" }),
100+
).toBeVisible();
101+
102+
// Make sure nothing genuinely broken showed up in the console while the
103+
// page rendered and the popup opened.
104+
expect(
105+
consoleErrors,
106+
`Unexpected console errors:\n${consoleErrors.join("\n")}`,
107+
).toEqual([]);
108+
109+
// Uncaught exceptions don't always surface as console.error — pageerror is
110+
// the canonical "did the app crash?" hook. Any uncaught exception fails
111+
// the smoke test (no filtering).
112+
expect(
113+
pageErrors,
114+
`Unexpected uncaught page errors:\n${pageErrors.join("\n")}`,
115+
).toEqual([]);
116+
});
117+
});
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
/** @type {import('next').NextConfig} */
2-
const nextConfig = {
3-
eslint: {
4-
ignoreDuringBuilds: true,
5-
},
6-
};
2+
const nextConfig = {};
73

84
export default nextConfig;

0 commit comments

Comments
 (0)