Files
client/plans/ai-chat-routstr.md
2026-04-17 16:52:51 -04:00

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 defaults
    • fetchModels(providerUrl) — GET /v1/models and cache pricing
    • fetchProviderInfo(providerUrl) — GET /v1/info
    • getAvailableModels() — Merged list from all discovered providers
    • sendChatMessage({ messages, model, providerUrl, callbacks }) — Full request lifecycle:
      1. Look up model pricing from cache
      2. Calculate required sats (use max_cost from model or estimate from sats_pricing)
      3. Call walletController.sendToken(amount) to get a cashu token
      4. POST to /v1/chat/completions with Authorization: Bearer <token>, stream: true
      5. Parse SSE stream, call callbacks.onChunk(text) for each delta
      6. On stream complete, call /v1/balance/refund to get change token
      7. Call walletController.receiveToken(refundToken) to store change
      8. 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> and X-Cashu: <cashuToken> modes
  • Default providers: https://api.routstr.com/, https://api.nonkycai.com/, https://privateprovider.xyz/
  • SSE parsing: Read response.body as a ReadableStream, split on newlines, parse data: {...} JSON chunks, extract choices[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.html outgoing)
  • AI messages: left-aligned bubble (like msg.html incoming)
  • 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 createCashuWalletController from cashu-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:

  1. Hardcoded defaults — Start with known providers:

    • https://api.routstr.com/
    • https://api.nonkycai.com/
    • https://privateprovider.xyz/
  2. Nostr discovery — Subscribe to kind:38421 events via NDK:

    subscribe({ kinds: [38421] }, { closeOnEose: true, cacheUsage: 'CACHE_FIRST' });
    

    Parse u tags for HTTP endpoints, mint tags 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 msgBubbleMeta area (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:

  1. ai.html Routstr logic — Core request/payment logic first, testable in-page
  2. ai.html layout — Page structure with CSS
  3. Wire wallet — Connect cashu-wallet.mjs
  4. Model fetching — Populate model selector from providers
  5. Chat send/receive — Basic non-streaming first, then streaming
  6. Markdown rendering — Rich message display
  7. Cost tracking — Balance and per-message costs
  8. Conversation persistence — Save/load conversations
  9. Provider discovery — Nostr-based discovery
  10. System prompt — Configuration UI
  11. 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() and fetchAIResponse()
  • 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