Demo Specs Discussion
Yep, this is very buildable in 3 days. Let’s lock in a concrete, opinionated spec so you don’t lose time re-deciding things mid-sprint.
I’ll cover:
- Demo user flow (end-to-end)
- Tech stack
- Data model
- Routes & APIs
- Key components / UX
- Day-by-day implementation plan
1. Demo user flow (what YC will actually see)
From YC’s perspective:
-
Go to AI Wallet landing/dashboard (
/wallet) -
They click “Sign in with Google”.
-
They land on a simple Wallet dashboard:
- “Hi,
founder@...” - “Balance:
$5.00demo credits” - “Recent activity” table — currently empty or with one seeded item.
- Button: “Try AI Wallet in a demo app” →
/try.
- “Hi,
-
Go to demo chatbot app (
/try) -
UI looks like a different app (simple brand variation).
-
At top:
Login with AI Walletbutton (your “SDK”).- If already logged in on wallet, clicking it just “authorizes” and closes a small modal.
-
Once logged in:
-
Header: “Wallet:
$5.00demo credits remaining”. - Model dropdown: e.g.
gpt-4.1,claude-3-5(from OpenRouter). - Chat box with streaming responses.
-
They send a message
-
You:
- Call OpenRouter via Vercel AI SDK.
- On server, decrement, say,
$0.05in demo credits. - Create a usage event row: who, app, model, amount, timestamp, prompt snippet.
-
Frontend:
-
Streams assistant answer.
- Wallet display updates to
$4.95. - Maybe a tiny inline “Last call: –$0.05 demo credits”.
-
They go back to
/wallet -
The dashboard now shows:
- Balance:
$4.95. -
Activity row:
-
2025-11-08 10:21 | Demo Chatbot | gpt-4.1 | –$0.05. - YC sees: same user, two apps, one wallet, unified history.
- Balance:
That’s the core story. Everything below is in service of that.
2. Tech stack (opinionated but simple)
Frontend / Backend:
- Next.js 14 (App Router) + TypeScript.
- Deployed on Vercel.
Auth:
-
next-auth with:
-
Provider: Google OAuth (only).
- Session strategy: JWT (simple enough for demo).
Database:
-
PostgreSQL via Prisma (or Drizzle if you prefer):
-
Hosted: Supabase / Neon / Railway — whatever you’re familiar with.
LLM:
- OpenRouter HTTP API.
- Vercel AI SDK (
aipackage) for streaming in/api/chat.
Styling:
- Tailwind CSS (fast to iterate).
- Very simple light UI, no design rabbit holes.
3. Data model
Keep it minimal but expressive enough for receipts.
3.1 Entities overview
You really only need:
- User
- App (logical app using AI Wallet)
- WalletTransaction / UsageEvent
Optional but nice:
- ChatSession (to group chat messages)
- ChatMessage (store user & assistant messages)
If pressed for time, you can skip persisting ChatSession/ChatMessage and only store usage events + a prompt snippet.
3.2 Tables
I’ll phrase these as Prisma-style, but you can map to anything.
User
model User {
id String @id @default(cuid())
email String @unique
name String?
image String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
demoCredits Int @default(500) // e.g. 500 = $5.00 if 100 units = $1
usageEvents UsageEvent[]
}
Use an integer credits unit to avoid floating-point headaches:
100 credits = $1. So$5demo =500credits.
App
model App {
id String @id @default(cuid())
slug String @unique // 'wallet-dashboard', 'demo-chatbot'
name String
description String?
usageEvents UsageEvent[]
}
Seed two records:
wallet-dashboarddemo-chatbot
UsageEvent
This is your “consent/usage receipt” for the MVP.
model UsageEvent {
id String @id @default(cuid())
user User @relation(fields: [userId], references: [id])
userId String
app App @relation(fields: [appId], references: [id])
appId String
// Demo “ledger”:
creditsDelta Int // typically negative numbers for debits
creditsAfter Int // balance after this event, for easy display
// LLM details:
model String
tokensEstimate Int?
promptSnippet String? // first 100–200 chars of user prompt
createdAt DateTime @default(now())
}
Every chat turn → one UsageEvent row.
(Optional) ChatSession / ChatMessage
If you want to show past chats later, add:
model ChatSession {
id String @id @default(cuid())
user User @relation(fields: [userId], references: [id])
userId String
app App @relation(fields: [appId], references: [id])
appId String
title String?
createdAt DateTime @default(now())
messages ChatMessage[]
}
model ChatMessage {
id String @id @default(cuid())
session ChatSession @relation(fields: [sessionId], references: [id])
sessionId String
role String // 'user' | 'assistant'
content String
model String?
createdAt DateTime @default(now())
}
But this is optional for the 3-day demo.
4. Routes & APIs
4.1 Pages
Public / main:
-
/→ either: -
redirect to
/wallet, or - simple marketing with CTA “Go to your wallet” →
/wallet.
Wallet dashboard app:
-
/wallet -
Requires auth.
-
Shows:
- User info.
- Current credits (
demoCredits). - “Recent activity” table from
/api/wallet/usage. - Button “Try AI Wallet in a demo app” →
/try.
Demo chatbot app:
-
/try -
Public route.
-
Shows:
- A distinct header (e.g. “Demo Chatbot App” brand).
<LoginWithAIWalletButton />.-
If user authorized:
-
Wallet balance display (pulled from
/api/wallet/me). - Model dropdown.
- Chat UI.
- Chat uses
/api/chatendpoint.
4.2 Auth routes
-
/api/auth/[...nextauth] -
Google provider.
-
On first sign-in:
- Create
Userrow with initialdemoCredits = 500(=$5). - Session includes
userId.
- Create
4.3 Wallet-related APIs
GET /api/wallet/me
Returns current user wallet info.
Input: session from NextAuth. Output:
{
"userId": "xxx",
"email": "user@example.com",
"name": "User",
"demoCredits": 485
}
Used by /wallet and /try to display balance.
GET /api/wallet/usage
Returns recent usage events for the logged-in user.
Query params:
limit(optional, default 20)- maybe
appSlug(optional) if you want to filter by app later.
Response:
[
{
"id": "...",
"createdAt": "...",
"appName": "Demo Chatbot",
"model": "gpt-4.1",
"creditsDelta": -5,
"creditsAfter": 495,
"promptSnippet": "Explain what AI Wallet is..."
},
...
]
Used to populate the “Activity” table on /wallet.
POST /api/wallet/topup-demo (optional)
If you want a “Reset demo balance” button:
- Sets
demoCreditsback to 500. - Adds a
UsageEventwith positivecreditsDeltaas a “top-up” event.
4.4 Chat API
POST /api/chat
Handles:
- Validating the user/session.
- Charging demo credits.
- Logging a
UsageEvent. - Calling OpenRouter via Vercel AI SDK.
- Streaming back the completion.
Request:
{
"appSlug": "demo-chatbot",
"model": "openrouter/gpt-4.1",
"messages": [
{ "role": "user", "content": "Explain what AI Wallet is." },
...
]
}
Server-side steps (simplified):
- Authenticate user via session.
- Fetch
Userand currentdemoCredits. -
Decide cost:
-
For demo: fixed cost per call, e.g.
COST_PER_CALL = 5credits (=$0.05). - Or approximate from
messageslength. - If
demoCredits < COST_PER_CALL, return 402-style error: “Insufficient demo credits”. - Subtract credits, compute
newBalance = demoCredits - COST_PER_CALL. -
Create
UsageEvent: -
appIdfordemo-chatbot. creditsDelta = -COST_PER_CALL.creditsAfter = newBalance.model,promptSnippet(first ~160 chars of last user message).- Save
User.demoCredits = newBalance. - Call OpenRouter with the messages and stream back to client using Vercel’s
StreamingTextResponse/AiStream.
On the client:
- Start streaming response.
- Immediately update wallet display using the new balance from the response (include it in the initial JSON chunk or add a side-channel call to
/api/wallet/mewhen finished).
Response shape (non-streaming version for conceptual clarity):
{
"model": "openrouter/gpt-4.1",
"answer": "… streamed in reality …",
"tokensEstimate": 200,
"creditsCharged": 5,
"newBalance": 495
}
But with streaming, you’d usually:
- Send a small JSON preamble (with
newBalance) then stream tokens.
5. Key components / UX
5.1 <LoginWithAIWalletButton />
This is your “SDK-like” piece.
Props:
type LoginWithAIWalletButtonProps = {
onAuthorized?: () => void;
appSlug: 'demo-chatbot';
};
Behavior:
-
If user isn’t logged in:
-
Triggers NextAuth sign-in with Google (or a small modal that says “We use AI Wallet for login” then sign-in).
-
If user is logged in but app “not authorized”:
-
For demo, you can just treat login = authorization, or
- Write an
AppAuthorizationrow; don’t overcomplicate. - Once done, calls
onAuthorized.
Visually:
- Small button with AI Wallet branding.
- Text:
Login with AI Wallet.
5.2 Wallet Dashboard layout (/wallet)
Sections:
-
Header
-
“AI Wallet”
- User avatar/email
-
Link: “Try AI Wallet in a demo app”.
-
Balance card
-
“Current demo balance: $X.YZ”
-
Optional: “Reset demo credits” button (calls
/api/wallet/topup-demo). -
Activity list
-
Table:
- Date/Time
- App
- Model
- Credits change
- Resulting balance
- Prompt snippet (hover for full)
That’s it. It’ll look legit with very little CSS.
5.3 Demo Chatbot layout (/try)
Sections:
-
Top bar
-
App name: “Demo Chatbot App”
-
On the right:
LoginWithAIWalletButton- If logged in: “Wallet: $X.YZ demo credits”.
-
Main
-
Model dropdown.
- Messages window.
- Input box + “Send” button.
-
Optional small line under messages:
- “Last call: –$0.05 demo credits · model: gpt-4.1”.
6. Day-by-day implementation breakdown
Day 1 – Skeleton, auth, and data model
Goals:
- Project + DB bootstrapped.
- Auth working.
/walletand/trybasic pages rendering.- No AI or credits yet.
Tasks:
-
Project setup
-
Init Next.js App Router + TypeScript.
- Add Tailwind CSS.
-
Set up environment vars scaffolding (
.env.local). -
DB setup
-
Set up Postgres (Supabase/Neon/etc.).
- Install Prisma.
- Define
User,App,UsageEventmodels. prisma migrate devto create tables.-
Seed
Appwithwallet-dashboardanddemo-chatbot. -
Auth
-
Install
next-auth. - Configure Google provider.
-
On first sign-in:
- Create
Userrow withdemoCredits = 500. - Build a simple
<AuthGuard>for/wallet.
- Create
-
Basic pages
-
/wallet:- Protected by auth.
- Render “Hello, [email]” from
useSession(). - Hardcode “Balance: $5.00” for now.
-
/try: -
Public.
-
Render stub:
-
“Demo Chatbot App”
- Placeholder
Login with AI Walletbutton (non-working). - Mock chat layout with no backend.
If you end Day 1 logged in via Google and seeing a wallet page, you’re in good shape.
Day 2 – Wallet logic, chat API, and usage logging
Goals:
- Real credits field wired to DB.
/api/chatcalling OpenRouter and decrementing credits.- Usage events being stored.
/walletshowing real balance and recent activity.
Tasks:
-
Wallet APIs
-
Implement
GET /api/wallet/me:- Returns
demoCreditsand basic user info. -
Implement
GET /api/wallet/usage: -
Fetch last N
UsageEventfor user. - Update
/walletto fetch these viafetch/SWR/react-query.
- Returns
-
Chat API
-
Implement
POST /api/chat:- Validate session.
- Parse
appSlug,model,messages. - Look up user; get
demoCredits. - Compute a fixed
COST_PER_CALL(e.g. 5 credits). - If insufficient credits → return error.
- Decrement balance, write
UsageEvent, saveUser.demoCredits. - Call OpenRouter via Vercel AI SDK and stream back response.
- For now, just log the streaming text in client.
-
Wire chat UI
-
On
/try, build a simple chat component:messagesstate.-
On submit:
-
Add user message to state.
- Call
/api/chat. - Stream assistant tokens into messages.
- On initial response, update wallet balance from
newBalancefield. - Handle basic “insufficient credits” case.
-
Usage table on
/wallet -
Map over
/api/wallet/usageresult. -
Display a simple list/table with:
createdAt(format locally),appName,model,creditsDelta,creditsAfter.
By end of Day 2, you should be able to:
- Log in to wallet → see current balance & empty/seeded activity.
- Go to
/try→ chat once → see balance actually changed on both/tryand/wallet.
Day 3 – “Login with AI Wallet” UX, polish, YC-ready
Goals:
LoginWithAIWalletButtonfeels like an SDK.- Demo is stable and visually coherent.
- Record YC video.
Tasks:
-
Polish
LoginWithAIWalletButton -
Create reusable component that:
- If no session: calls
signIn('google'). - If session exists: show “Connected as [email]”.
- Use it in
/try. -
Optionally show a tiny modal the first time:
-
“This app uses AI Wallet for login and billing.”
- If no session: calls
-
Balance sync UX
-
Ensure:
/tryheader displays wallet balance from/api/wallet/me.- After each chat call, you update balance without a full refresh (use returned
newBalanceor refetch). - On
/wallet, maybe auto-refresh usage list every 5–10s or have a “Refresh” button.
-
Optional extras (only if time allows)
-
POST /api/wallet/topup-demo+ “Reset demo credits” button. -
Very light RAG / FAQ:
- Add a
?button in/trythat opens a panel with Q&A about AI Wallet. - This can just be hard-coded text for the demo.
- Add a
-
Styling pass
-
Make sure layouts don’t look broken:
- Centered content, consistent padding.
- Balance card looks like a card.
- Activity table tidy.
-
Add minimal branding:
-
Logo text “AI Wallet” in both apps, with slightly different accent color so they feel distinct.
-
Record YC video
-
Script:
- Start on
/wallet→ “Here is AI Wallet. Users log in and see their balance and all AI app activity.” - Click “Try AI Wallet in a demo app” →
/try. - Show
Login with AI Wallet→ confirm connected. - Send a message; show streaming + balance drop.
- Switch back to
/wallet→ new activity visible.
- Start on
If you stick to this spec and don’t let yourself get pulled into extra features, this is a very realistic 3-day build and tells the AI Wallet story clearly.
If you’d like, next I can help you translate this into a tiny README / architecture doc you can paste into the repo (for yourself, and later, for a cofounder/YC partner to skim).