9.4 KiB
AI Chat Page — Routstr Protocol Integration
Overview
Build ai.html as a chat interface for AI models, paying per-request with Cashu ecash via the Routstr protocol. This page follows the same template pattern as all other pages in the project (header/body/footer/sidenav shell from template.html) and reuses the existing cashu-wallet.mjs controller for wallet operations.
The goal is to replicate the core functionality of chat.routstr.com — model selection, streaming chat, cashu payment — but in our vanilla JS/HTML style with no React/Next.js dependencies.
Architecture
flowchart TD
subgraph Browser
AI[ai.html]
CW[cashu-wallet.mjs]
NDK[init-ndk.mjs / ndk-worker.js]
end
subgraph Routstr Provider
API[/v1/chat/completions]
MODELS[/v1/models]
INFO[/v1/info]
BAL[/v1/balance/*]
end
subgraph Nostr Relays
DISC[kind:38421 Provider Events]
end
AI -->|imports| CW
AI -->|imports| NDK
AI -->|fetch| API
AI -->|fetch| MODELS
AI -->|fetch| INFO
AI -->|fetch| BAL
NDK -->|subscribe| DISC
CW -->|sendToken| AI
AI -->|receiveToken refund| CW
Payment Flow per Request
sequenceDiagram
participant User
participant AI as ai.html
participant Wallet as cashu-wallet.mjs
participant Provider as Routstr Provider
User->>AI: Type message and send
AI->>Provider: sendChatMessage with history and model
AI->>Provider: GET /v1/models - get pricing
AI->>AI: Calculate required sats from max_cost
AI->>Wallet: sendToken with required amount
Wallet-->>AI: cashuToken string
AI->>Provider: POST /v1/chat/completions with Authorization: Bearer cashuToken
Provider-->>AI: SSE stream chunks
AI-->>AI: onStreamChunk callbacks - render incrementally
Provider-->>AI: Stream complete with usage stats
AI->>Provider: POST /v1/balance/refund
Provider-->>AI: Refund cashu token for unspent balance
AI->>Wallet: receiveToken with refund token
Wallet-->>AI: Updated balance
AI-->>AI: onComplete with cost info
AI->>AI: Update balance display and cost per message
File Structure
| File | Purpose |
|---|---|
www/ai.html |
Page HTML + inline styles + inline script wiring |
www/ai.html (inline module script) |
Routstr API client logic - provider management, model fetching, chat requests, SSE streaming, refund handling |
www/js/cashu-wallet.mjs |
Existing - Cashu wallet controller, used for sendToken/receiveToken |
www/js/init-ndk.mjs |
Existing - NDK page init, subscribe for provider discovery |
www/js/marked.min.js |
Existing - Markdown rendering |
Detailed Plan
1. Implement Routstr API Client Logic in www/ai.html
This is the core implementation block and encapsulates all Routstr protocol logic directly in the page module script.
Exports:
createRoutstrClient({ walletController, subscribe })— Factory that returns a client object- Client methods:
discoverProviders()— Subscribe to kind:38421 Nostr events + fetch from hardcoded defaultsfetchModels(providerUrl)— GET/v1/modelsand cache pricingfetchProviderInfo(providerUrl)— GET/v1/infogetAvailableModels()— Merged list from all discovered providerssendChatMessage({ messages, model, providerUrl, callbacks })— Full request lifecycle:- Look up model pricing from cache
- Calculate required sats (use
max_costfrom model or estimate fromsats_pricing) - Call
walletController.sendToken(amount)to get a cashu token - POST to
/v1/chat/completionswithAuthorization: Bearer <token>,stream: true - Parse SSE stream, call
callbacks.onChunk(text)for each delta - On stream complete, call
/v1/balance/refundto get change token - Call
walletController.receiveToken(refundToken)to store change - Call
callbacks.onComplete({ content, usage, cost })with final stats
getBalance(providerUrl)— GET/v1/balance/info
Key design decisions:
- Use the simpler cashu-as-bearer-token approach from RIP-01 (send cashu token in Authorization header, get refund after)
- Support both
Authorization: Bearer <cashuToken>andX-Cashu: <cashuToken>modes - Default providers:
https://api.routstr.com/,https://api.nonkycai.com/,https://privateprovider.xyz/ - SSE parsing: Read
response.bodyas a ReadableStream, split on newlines, parsedata: {...}JSON chunks, extractchoices[0].delta.content
2. Create ai.html Page Layout
Based on template.html shell + chat UI inspired by msg.html bubble layout.
Layout structure:
#divBody
#divAiLayout (flex row, full height)
#divAiConversationsPane (left sidebar - conversation list, 30% width)
#divAiConversationsTitle
#divAiConversationsList (scrollable)
#divAiNewChatBtn
#divAiChatPane (right main area, flex column)
#divAiChatHeader (model selector + provider info + cost display)
#divAiMessages (scrollable message area)
#divAiInputArea (input box + send button)
Message rendering:
- User messages: right-aligned bubble (like
msg.htmloutgoing) - AI messages: left-aligned bubble (like
msg.htmlincoming) - Markdown rendered via
marked.min.js+ sanitized with DOMPurify - Code blocks with syntax highlighting via marked
- Streaming: AI bubble updates in real-time as chunks arrive
- Typing indicator: pulsing dots while waiting for first chunk
3. Wire Cashu Wallet
- Import
createCashuWalletControllerfromcashu-wallet.mjs - Initialize wallet on page load (same pattern as
cashu.html) - Use
walletController.sendToken(amount, memo, mint)to create payment tokens - Use
walletController.receiveToken(token)to receive refund tokens - Display balance in footer
#divFooterBalance(already in template) - Subscribe to balance updates via
walletController.onWalletEvent('balance_updated', ...)
4. Model and Provider Selection UI
In #divAiChatHeader:
- Dropdown/select for model (populated from
getAvailableModels()) - Shows: model name, provider, price per 1M tokens
- Small provider badge showing which provider is selected
In sidenav:
- Provider list with enable/disable toggles
- Add custom provider URL
- Mint URL configuration (which mint to pay from)
5. Provider Discovery
Two discovery methods:
-
Hardcoded defaults — Start with known providers:
https://api.routstr.com/https://api.nonkycai.com/https://privateprovider.xyz/
-
Nostr discovery — Subscribe to kind:38421 events via NDK:
subscribe({ kinds: [38421] }, { closeOnEose: true, cacheUsage: 'CACHE_FIRST' });Parse
utags for HTTP endpoints,minttags for accepted mints.
6. Streaming Response Display
- Create AI message bubble immediately when request starts
- Show typing indicator (3 pulsing dots)
- As SSE chunks arrive, append to bubble content and re-render markdown
- Auto-scroll to bottom on each update
- On complete, finalize the message and show cost metadata
7. Cost Tracking
- Display per-message cost in
msgBubbleMetaarea (e.g., "2 sats") - Track total session spend
- Show wallet balance in footer (updates after each refund)
- Show estimated cost before sending (based on model pricing)
8. Conversation Persistence
- Store conversations in localStorage keyed by conversation ID
- Each conversation:
{ id, title, model, provider, messages[], createdAt, updatedAt } - Auto-generate title from first user message
- List conversations in left sidebar
- New chat button creates fresh conversation
- Delete conversation option
9. System Prompt Configuration
- Collapsible section in sidenav or chat header
- Default system prompt: "You are a helpful assistant."
- Custom system prompt textarea
- System prompt saved per-conversation
- Prepended to message history on each request
Implementation Order
The work should be done in this sequence, with each step building on the previous:
- ai.html Routstr logic — Core request/payment logic first, testable in-page
- ai.html layout — Page structure with CSS
- Wire wallet — Connect cashu-wallet.mjs
- Model fetching — Populate model selector from providers
- Chat send/receive — Basic non-streaming first, then streaming
- Markdown rendering — Rich message display
- Cost tracking — Balance and per-message costs
- Conversation persistence — Save/load conversations
- Provider discovery — Nostr-based discovery
- System prompt — Configuration UI
- Polish — Error handling, loading states, edge cases
Key References
- RIP-01 (
routstr/RIP-01.md): Proxy/Payments — Authorization header, endpoints, payment flow, X-Cashu header - RIP-02 (
routstr/RIP-02.md): Discovery — Kind 38421 provider events - RIP-03 (
routstr/RIP-03.md): Clients — Wallet management, overpay-then-refund cycle - RIP-05 (
routstr/RIP-05.md): Pricing — Cost calculation, max_cost, client verification - routstr-chat/utils/apiUtils.ts: Reference implementation of
routstrRequest()andfetchAIResponse() - routstr-chat/sdk/client/StreamProcessor.ts: SSE stream parsing reference
- routstr-chat/sdk/wallet/CashuSpender.ts: Token spending logic reference
- www/msg.html: Chat bubble UI patterns to reuse
- www/cashu.html + www/js/cashu-wallet.mjs: Existing wallet integration to reuse