Zum Hauptinhalt springen
Lassen Sie Sentra Ihren Integrationscode für Sie schreiben.
Verwenden Sie unseren KI-Assistenten in VS Code, Cursor oder Windsurf, um SDK/API-Code, LLM Blueprint-Integrationscode, Webhooks und mehr zu generieren - einfach indem Sie beschreiben, was Sie möchten.
Probieren Sie Sentra aus: KI-gestützte Integration →
In diesem Tutorial erstellen Sie eine KI-Chat-Anwendung mit automatischer nutzungsbasierter Abrechnung. Wir werden alles von Grund auf neu erstellen: den Abrechnungszähler, die Produktkonfiguration und den Anwendungscode, der Gespräche ermöglicht und die Token-Nutzung in Echtzeit verfolgt.
Dieses Tutorial bietet eine vollständig funktionierende Anwendung mit Backend und Frontend. Die Chat-App verwendet Googles Gemini AI und verfolgt automatisch die Token-Nutzung, ohne dass eine manuelle Zählung erforderlich ist.
Am Ende dieses Tutorials haben Sie eine funktionierende Chat-Anwendung, die:
  • KI-Gespräche mit Google Gemini (AI SDK) ermöglicht
  • Automatisch die Token-Nutzung verfolgt (kein manueller Code)
  • Kunden basierend auf dem tatsächlichen Tokenverbrauch belastet
  • Eine schöne Chat-Oberfläche enthält
AI Chat Demo

Was wir erstellen

Lassen Sie uns damit beginnen, unseren KI-Chatdienst zu verstehen:
  • Dienst: KI-gestützter Chat mit Google Gemini (AI SDK)
  • Preismodell: Zahlung pro Token (0,01 $ pro 1.000 Tokens)
  • Kostenloses Kontingent: 10.000 kostenlose Tokens pro Kunde und Monat
  • Funktionen: Gesprächsverlauf, automatische Tokenverfolgung
Bevor wir beginnen, stellen Sie sicher, dass Sie:

Schritt 1: Erstellen Sie Ihren Nutzungsmesser

Wir beginnen damit, einen Zähler in Ihrem Dodo Payments-Dashboard zu erstellen, der die Token-Nutzung der KI verfolgt.
Was wir erstellen: Ein Zähler mit dem Namen “AI Token Usage Meter”, der alle in Chat-Gesprächen verbrauchten Tokens summiert.
1

Öffnen Sie den Abschnitt Zähler

  1. Melden Sie sich in Ihrem Dodo Payments-Dashboard an
  2. Klicken Sie im linken Seitenmenü auf Produkte
  3. Klicken Sie auf Zähler
  4. Klicken Sie auf die Schaltfläche Zähler erstellen
Zähler erstellen
Sie sollten ein Formular sehen, in dem wir unsere Tokenverfolgung konfigurieren werden.
2

Füllen Sie die grundlegenden Zählerinformationen aus

Jetzt geben wir die spezifischen Details für unseren KI-Chatdienst ein:ZählernameAI Token Usage MeterBeschreibungTracks token consumption from AI chat conversations using AI SDKEreignisnameai_chat_usage
Der Ereignisname ai_chat_usage muss genau mit dem übereinstimmen, was wir später aus unserem Anwendungscode senden werden. Ereignisnamen sind groß- und kleinschreibungsempfindlich!
3

Konfigurieren Sie, wie wir Tokens zählen

Richten Sie die Aggregation ein (wie der Zähler unsere Ereignisse zählt):Aggregationstyp: Wählen Sie Summe aus dem Dropdown-MenüAggregieren über: Typ → totalTokensMaßeinheit: Typ → tokens
Wir verwenden “Summe”, weil wir alle verbrauchten Tokens über mehrere Chatnachrichten hinweg addieren möchten. Das SDK sendet automatisch totalTokens in jedem Ereignis.
4

Erstellen Sie Ihren Zähler

  1. Überprüfen Sie, ob alle Ihre Einstellungen mit den oben genannten Werten übereinstimmen
  2. Klicken Sie auf Zähler erstellen
Zählerkonfiguration
Zähler erstellt! Ihr “AI Token Usage Meter” ist jetzt bereit, Tokens zu zählen. Als Nächstes verbinden wir ihn mit einem Abrechnungsprodukt.

Schritt 2: Holen Sie sich Ihre API-Schlüssel

Bevor wir die App erstellen, lassen Sie uns die API-Schlüssel sammeln, die wir benötigen.
1

Holen Sie sich den Dodo Payments API-Schlüssel

  1. Gehen Sie in Ihrem Dodo Payments-Dashboard zu EntwicklerAPI-Schlüssel
  2. Klicken Sie auf API-Schlüssel erstellen
  3. Kopieren Sie den API-Schlüssel - er wird wie test_abc123... aussehen
Speichern Sie diesen API-Schlüssel - wir werden ihn später zu unserer .env Datei hinzufügen.
2

Holen Sie sich den Google AI API-Schlüssel

  1. Besuchen Sie aistudio.google.com
  2. Klicken Sie auf API-Schlüssel erhalten
  3. Erstellen Sie einen neuen API-Schlüssel oder verwenden Sie einen vorhandenen
  4. Kopieren Sie den Schlüssel
Bewahren Sie diesen Schlüssel sicher auf - wir werden ihn ebenfalls zu unserer .env Datei hinzufügen.

Schritt 3: Erstellen Sie Ihr Abrechnungsprodukt

Jetzt müssen wir ein Produkt erstellen, das unsere Preisgestaltung definiert (0,01 $ pro 1.000 Tokens mit 10.000 kostenlosen Tokens). Dies verbindet unseren Zähler mit der tatsächlichen Abrechnung.
Was wir erstellen: Ein Produkt namens “AI Chat Service”, das basierend auf dem Tokenverbrauch Gebühren erhebt und ein großzügiges kostenloses Kontingent bietet.
1

Navigieren Sie zu Produkten

  1. Klicken Sie in Ihrem Dodo Payments-Dashboard auf Produkte im linken Seitenmenü
  2. Klicken Sie auf Produkt erstellen
  3. Wählen Sie Nutzungsbasiert als Produkttyp aus
Dies teilt Dodo Payments mit, dass die Abrechnung auf der Nutzung des Zählers basiert, nicht auf einem festen Abonnement.
2

Geben Sie die Produktdetails ein

Füllen Sie die erforderlichen Details aus:Produktname: → AI Chat ServiceBeschreibung: → AI-powered chat service with automatic token-based billingProduktbild: Laden Sie ein relevantes Bild hoch
Diese werden auf den Rechnungen der Kunden angezeigt, also machen Sie sie klar und professionell.
3

Verbinden Sie Ihren Zähler

Bevor Sie Ihren Zähler verbinden, stellen Sie sicher, dass Sie Nutzungsbasierte Abrechnung als Preisart für Ihr Produkt ausgewählt haben.Zusätzlich setzen Sie den Festpreis auf 0, um sicherzustellen, dass Kunden nur basierend auf ihrer Nutzung belastet werden, ohne Grundgebühr.Jetzt verlinken Sie den Zähler, den Sie gerade erstellt haben:
  1. Scrollen Sie nach unten zum Abschnitt Zugeordneter Zähler
  2. Klicken Sie auf Zähler hinzufügen
  3. Wählen Sie aus dem Dropdown “AI Token Usage Meter” (den, den Sie zuvor erstellt haben)
  4. Bestätigen Sie, dass er in Ihrer Produktkonfiguration angezeigt wird
Ihr Zähler ist jetzt erfolgreich mit diesem Produkt verbunden.
4

Legen Sie Ihre Preisgestaltung fest

Hier definieren wir unser Geschäftsmodell:Preis pro Einheit: Geben Sie ein → 0.00001 (das sind 0,01 pro1.000Tokensoder0,00001pro 1.000 Tokens oder 0,00001 pro Token)Kostenlose Schwelle: Geben Sie ein → 10000 (Kunden erhalten 10.000 kostenlose Tokens pro Monat)
Produktpreisgestaltung
So funktioniert die Abrechnung: Wenn ein Kunde 25.000 Tokens in einem Monat verwendet, wird er für 15.000 Tokens belastet (25.000 - 10.000 kostenlose) = 15.000 × 0,00001 =0,15= 0,15
5

Speichern Sie Ihr Produkt

  1. Überprüfen Sie alle Ihre Einstellungen:
    • Name: AI Chat Service
    • Zähler: AI Token Usage Meter
    • Preis: 0,01 $ pro 1.000 Tokens
    • Kostenloses Kontingent: 10.000 Tokens
  2. Klicken Sie auf Änderungen speichern
Produkt erstellt! Ihre Abrechnung ist jetzt konfiguriert. Kunden werden automatisch basierend auf ihrer Token-Nutzung belastet.

Schritt 4: Machen Sie einen Testkauf

Bevor wir mit dem Erstellen der App beginnen, lassen Sie uns einen Testkunden erstellen, indem wir einen Kauf tätigen.
1

Holen Sie sich Ihren Zahlungslink

  1. Gehen Sie in Ihrem Dodo Payments-Dashboard zu Produkte
  2. Suchen Sie Ihr Produkt “AI Chat Service”
  3. Klicken Sie auf die Schaltfläche Teilen neben Ihrem Produkt
  4. Kopieren Sie den Zahlungslink, der angezeigt wird
2

Schließen Sie einen Testkauf ab

  1. Öffnen Sie den Zahlungslink in einem neuen Browser-Tab
  2. Geben Sie Test-Zahlungsdetails ein und schließen Sie den Kauf ab
Nach erfolgreicher Zahlung haben Sie eine Kunden-ID, die wir in unserem Anwendungscode verwenden werden.
3

Finden Sie Ihre Kunden-ID

  1. Gehen Sie zurück zu Ihrem Dodo Payments-Dashboard
  2. Navigieren Sie zu Verkäufe -> Kunden im linken Seitenmenü
  3. Suchen Sie den Kunden, den Sie gerade erstellt haben (mit der Test-E-Mail)
  4. Kopieren Sie die Kunden-ID - sie wird wie cus_123 aussehen
Speichern Sie diese Kunden-ID - wir werden sie beim Testen unserer Chat-Anwendung verwenden.

Schritt 5: Erstellen Sie die Chat-Anwendung

Jetzt haben wir unsere Abrechnung eingerichtet und einen Testkunden erstellt. Lassen Sie uns die KI-Chat-Anwendung mit automatischer Tokenverfolgung erstellen.
1

Richten Sie Ihr Projekt ein

Erstellen Sie ein neues Verzeichnis und initialisieren Sie das Projekt:
mkdir ai-chat-app
cd ai-chat-app
npm init -y
2

Installieren Sie Abhängigkeiten

Installieren Sie die benötigten Pakete:
npm install express ai @ai-sdk/google @dodopayments/ingestion-blueprints dotenv
npm install --save-dev typescript @types/express @types/node tsx
3

Konfigurieren Sie TypeScript

Erstellen Sie tsconfig.json:
tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true,
    "outDir": "./dist",
    "rootDir": "./src"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
Aktualisieren Sie package.json, um den Modultyp und die Skripte hinzuzufügen:
package.json
{
  "type": "module",
  "scripts": {
    "dev": "tsx src/server.ts",
    "build": "tsc",
    "start": "node dist/server.js"
  }
}
4

Erstellen Sie die Projektstruktur

Erstellen Sie die Ordner und Dateien:
mkdir src public
5

Richten Sie Umgebungsvariablen ein

Erstellen Sie eine .env Datei in Ihrem Projektstamm:
.env
DODO_PAYMENTS_API_KEY=your_dodo_api_key_here
DODO_ENVIRONMENT=test_mode
GOOGLE_GENERATIVE_AI_API_KEY=your_google_api_key_here
PORT=3000
Ersetzen Sie die Platzhalterwerte durch Ihre tatsächlichen API-Schlüssel aus Schritt 2.
6

Erstellen Sie den Backend-Server

Erstellen Sie src/server.ts und kopieren Sie diesen vollständigen Servercode:
Hier ist der vollständige KI-Chat-Server mit integrierter Abrechnung:
import express, { Request, Response } from 'express';
import { generateText } from 'ai';
import { google } from '@ai-sdk/google';
import { createLLMTracker } from '@dodopayments/ingestion-blueprints';
import 'dotenv/config';

const app = express();
app.use(express.json());
app.use(express.static('public'));

// Replace with your test customer ID
const CUSTOMER_ID = 'cus_123';

// Create tracker once with your meter event name
const llmTracker = createLLMTracker({
  apiKey: process.env.DODO_PAYMENTS_API_KEY!,
  environment: process.env.DODO_ENVIRONMENT as 'test_mode' | 'live_mode',
  eventName: 'ai_chat_usage', // Must match your meter configuration
});

// Chat endpoint with conversation support
app.post('/chat', async (req: Request, res: Response) => {
  try {
    const { messages } = req.body;

    if (!messages || !Array.isArray(messages)) {
      return res.status(400).json({ 
        error: 'Missing required field: messages (array)' 
      });
    }

    // Wrap AI SDK with automatic token tracking
    const trackedClient = llmTracker.wrap({
      client: { generateText },
      customerId: CUSTOMER_ID
    });

    // Generate AI response - tokens are automatically tracked!
    const response = await trackedClient.generateText({
      model: google('gemini-2.5-flash'),
      messages: messages
    });

    res.json({
      message: response.text,
      usage: {
        totalTokens: response.usage.totalTokens
      }
    });

  } catch (error: any) {
    console.error('Chat error:', error);
    res.status(500).json({ 
      error: 'Failed to process chat',
      details: error.message 
    });
  }
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`🚀 Server running at http://localhost:${PORT}`);
  console.log(`📊 Tracking event: ai_chat_usage`);
  console.log(`👤 Customer ID: ${CUSTOMER_ID}`);
  console.log(`🔧 Environment: ${process.env.DODO_ENVIRONMENT}`);
});

Schritt 6: Fügen Sie die Chat-Oberfläche hinzu

Jetzt fügen wir eine schöne Chat-Oberfläche mit vollem Gesprächsverlauf hinzu! Erstellen Sie public/index.html:
public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>AI Chat with Usage Billing</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      body {
        font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
          Roboto, sans-serif;
        background: #0f0f1e;
        background-image: radial-gradient(
            at 0% 0%,
            rgba(102, 126, 234, 0.15) 0px,
            transparent 50%
          ),
          radial-gradient(
            at 100% 100%,
            rgba(118, 75, 162, 0.15) 0px,
            transparent 50%
          ),
          radial-gradient(
            at 50% 50%,
            rgba(102, 126, 234, 0.05) 0px,
            transparent 50%
          );
        height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
        padding: 0;
        position: relative;
        overflow: hidden;
        margin: 0;
      }

      .chat-container {
        background: rgba(22, 22, 35, 0.95);
        border: 1px solid rgba(102, 126, 234, 0.2);
        border-radius: 0;
        box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
        width: 100%;
        max-width: 100%;
        height: 100vh;
        display: flex;
        flex-direction: column;
        overflow: hidden;
        position: relative;
        z-index: 1;
      }

      .chat-header {
        background: linear-gradient(
          135deg,
          rgba(102, 126, 234, 0.15) 0%,
          rgba(118, 75, 162, 0.15) 100%
        );
        border-bottom: 1px solid rgba(102, 126, 234, 0.2);
        color: white;
        padding: 24px 28px;
        position: relative;
        overflow: hidden;
      }

      .chat-header h1 {
        font-size: 26px;
        margin-bottom: 6px;
        font-weight: 700;
        letter-spacing: -0.5px;
        color: #fff;
      }

      .chat-header p {
        font-size: 13px;
        opacity: 0.6;
        font-weight: 500;
        letter-spacing: 0.3px;
      }

      .chat-messages {
        flex: 1;
        overflow-y: auto;
        padding: 32px 10%;
        background: transparent;
        will-change: scroll-position;
        scroll-behavior: smooth;
      }

      .chat-messages::-webkit-scrollbar {
        width: 6px;
      }

      .chat-messages::-webkit-scrollbar-track {
        background: rgba(255, 255, 255, 0.05);
      }

      .chat-messages::-webkit-scrollbar-thumb {
        background: rgba(102, 126, 234, 0.3);
        border-radius: 3px;
      }

      .chat-messages::-webkit-scrollbar-thumb:hover {
        background: rgba(102, 126, 234, 0.5);
      }

      .message {
        margin-bottom: 20px;
        display: flex;
        gap: 12px;
        animation: slideIn 0.2s ease-out;
      }

      @keyframes slideIn {
        from {
          opacity: 0;
          transform: translateY(10px);
        }

        to {
          opacity: 1;
          transform: translateY(0);
        }
      }

      .message.user {
        flex-direction: row-reverse;
      }

      .message-avatar {
        width: 40px;
        height: 40px;
        border-radius: 12px;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 20px;
        flex-shrink: 0;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
      }

      .message.user .message-avatar {
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      }

      .message.assistant .message-avatar {
        background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
      }

      .message-content {
        max-width: 65%;
      }

      .message-bubble {
        padding: 14px 18px;
        border-radius: 18px;
        line-height: 1.6;
        word-wrap: break-word;
        font-size: 15px;
        position: relative;
      }

      .message.user .message-bubble {
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        color: white;
        border-bottom-right-radius: 6px;
        box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
      }

      .message.assistant .message-bubble {
        background: rgba(255, 255, 255, 0.05);
        color: rgba(255, 255, 255, 0.95);
        border: 1px solid rgba(255, 255, 255, 0.1);
        border-bottom-left-radius: 6px;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
      }

      .message-meta {
        display: flex;
        gap: 10px;
        margin-top: 8px;
        font-size: 11px;
        color: rgba(255, 255, 255, 0.4);
        font-weight: 500;
      }

      .message.user .message-meta {
        justify-content: flex-end;
      }

      .token-badge {
        background: rgba(102, 126, 234, 0.2);
        color: #a8b9ff;
        padding: 4px 10px;
        border-radius: 12px;
        font-weight: 600;
        border: 1px solid rgba(102, 126, 234, 0.3);
      }

      .chat-input-area {
        padding: 24px 10% 32px;
        background: rgba(22, 22, 35, 0.95);
        border-top: 1px solid rgba(102, 126, 234, 0.2);
      }

      .input-wrapper {
        display: flex;
        gap: 12px;
        align-items: flex-end;
      }

      #messageInput {
        flex: 1;
        background: rgba(255, 255, 255, 0.05);
        border: 2px solid rgba(102, 126, 234, 0.2);
        border-radius: 16px;
        padding: 14px 20px;
        font-size: 15px;
        font-family: inherit;
        resize: none;
        max-height: 120px;
        transition: border-color 0.2s ease, background 0.2s ease;
        color: white;
        will-change: border-color;
        overflow: hidden;
        scrollbar-width: none;
        /* Firefox */
      }

      #messageInput::-webkit-scrollbar {
        display: none;
      }

      #messageInput::placeholder {
        color: rgba(255, 255, 255, 0.3);
      }

      #messageInput:focus {
        outline: none;
        border-color: #667eea;
        background: rgba(255, 255, 255, 0.08);
      }

      #sendBtn {
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        color: white;
        border: none;
        width: 52px;
        height: 52px;
        border-radius: 16px;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 22px;
        transition: transform 0.1s ease, box-shadow 0.1s ease;
        flex-shrink: 0;
        box-shadow: 0 2px 8px rgba(102, 126, 234, 0.4);
        position: relative;
      }

      #sendBtn:hover:not(:disabled) {
        transform: translateY(-1px);
        box-shadow: 0 4px 12px rgba(102, 126, 234, 0.5);
      }

      #sendBtn:active:not(:disabled) {
        transform: translateY(0);
      }

      #sendBtn:disabled {
        opacity: 0.4;
        cursor: not-allowed;
        box-shadow: none;
      }

      .typing-indicator {
        display: none;
        padding: 14px 18px;
        background: rgba(255, 255, 255, 0.05);
        border: 1px solid rgba(255, 255, 255, 0.1);
        border-radius: 18px;
        border-bottom-left-radius: 6px;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
        width: fit-content;
      }

      .typing-indicator.show {
        display: block;
      }

      .typing-dots {
        display: flex;
        gap: 6px;
      }

      .typing-dots span {
        width: 10px;
        height: 10px;
        border-radius: 50%;
        background: #667eea;
        animation: typing 1.4s infinite ease-in-out;
        will-change: transform, opacity;
      }

      .typing-dots span:nth-child(2) {
        animation-delay: 0.2s;
      }

      .typing-dots span:nth-child(3) {
        animation-delay: 0.4s;
      }

      @keyframes typing {
        0%,
        60%,
        100% {
          transform: translateY(0) scale(1);
          opacity: 0.6;
        }

        30% {
          transform: translateY(-12px) scale(1.1);
          opacity: 1;
        }
      }

      .error-message {
        background: rgba(239, 68, 68, 0.15);
        color: #fca5a5;
        padding: 14px 18px;
        border-radius: 12px;
        margin-bottom: 12px;
        display: none;
        border: 1px solid rgba(239, 68, 68, 0.3);
        font-size: 14px;
        font-weight: 500;
      }

      .error-message.show {
        display: block;
        animation: slideIn 0.3s ease;
      }

      .empty-state {
        text-align: center;
        padding: 80px 20px;
        color: rgba(255, 255, 255, 0.5);
      }

      .empty-state-icon {
        font-size: 72px;
        margin-bottom: 20px;
        animation: float 3s ease-in-out infinite;
      }

      @keyframes float {
        0%,
        100% {
          transform: translateY(0px);
        }

        50% {
          transform: translateY(-10px);
        }
      }

      .empty-state h2 {
        font-size: 24px;
        margin-bottom: 10px;
        color: rgba(255, 255, 255, 0.9);
        font-weight: 700;
        letter-spacing: -0.5px;
      }

      .empty-state p {
        font-size: 15px;
        color: rgba(255, 255, 255, 0.4);
        font-weight: 500;
      }
    </style>
  </head>

  <body>
    <div class="chat-container">
      <div class="chat-header">
        <h1>🤖 AI Chat Assistant</h1>
        <p>Powered by AI-SDK & Dodo Payments</p>
      </div>

      <div class="chat-messages" id="chatMessages">
        <div class="empty-state" id="emptyState">
          <div class="empty-state-icon">💬</div>
          <h2>Start a Conversation</h2>
          <p>Ask me anything! Your token usage is automatically tracked.</p>
        </div>
      </div>

      <div class="chat-input-area">
        <div class="error-message" id="errorMessage"></div>
        <div class="input-wrapper">
          <textarea
            id="messageInput"
            placeholder="Type your message here..."
            rows="1"
          ></textarea>
          <button id="sendBtn" onclick="sendMessage()"></button>
        </div>
      </div>
    </div>

    <script>
      let conversationHistory = [];

      const messageInput = document.getElementById("messageInput");
      let resizeTimeout;
      messageInput.addEventListener("input", function () {
        clearTimeout(resizeTimeout);
        resizeTimeout = setTimeout(() => {
          this.style.height = "auto";
          this.style.height = Math.min(this.scrollHeight, 120) + "px";
        }, 10);
      });

      // Send message on Enter (Shift+Enter for new line)
      messageInput.addEventListener("keydown", function (e) {
        if (e.key === "Enter" && !e.shiftKey) {
          e.preventDefault();
          sendMessage();
        }
      });

      async function sendMessage() {
        const input = document.getElementById("messageInput");
        const message = input.value.trim();

        if (!message) return;

        // Hide empty state
        document.getElementById("emptyState").style.display = "none";

        // Hide error
        document.getElementById("errorMessage").classList.remove("show");

        // Add user message to UI
        addMessage("user", message);

        // Add to conversation history
        conversationHistory.push({
          role: "user",
          content: message,
        });

        // Clear input
        input.value = "";
        input.style.height = "auto";

        // Show typing indicator
        showTypingIndicator();

        // Disable send button
        const sendBtn = document.getElementById("sendBtn");
        sendBtn.disabled = true;

        try {
          const response = await fetch("/chat", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              messages: conversationHistory,
            }),
          });

          const data = await response.json();

          if (!response.ok) {
            throw new Error(data.error || "Failed to get response");
          }

          // Hide typing indicator
          hideTypingIndicator();

          // Add assistant response to UI
          addMessage("assistant", data.message, data.usage);

          // Add to conversation history
          conversationHistory.push({
            role: "assistant",
            content: data.message,
          });
        } catch (error) {
          hideTypingIndicator();
          showError(error.message);
          // Remove the last user message from history since it failed
          conversationHistory.pop();
        } finally {
          sendBtn.disabled = false;
        }
      }

      function addMessage(role, content, usage = null) {
        const messagesDiv = document.getElementById("chatMessages");

        const messageDiv = document.createElement("div");
        messageDiv.className = `message ${role}`;

        const avatar = role === "user" ? "👤" : "🤖";

        let metaHTML = "";
        if (usage) {
          metaHTML = `
                    <div class="message-meta">
                        <span class="token-badge">📊 ${usage.totalTokens} tokens</span>
                    </div>
                `;
        }

        messageDiv.innerHTML = `
                <div class="message-avatar">${avatar}</div>
                <div class="message-content">
                    <div class="message-bubble">${escapeHtml(content)}</div>
                    ${metaHTML}
                </div>
            `;

        messagesDiv.appendChild(messageDiv);
        requestAnimationFrame(() => {
          messagesDiv.scrollTop = messagesDiv.scrollHeight;
        });
      }

      function showTypingIndicator() {
        const messagesDiv = document.getElementById("chatMessages");

        const typingDiv = document.createElement("div");
        typingDiv.className = "message assistant";
        typingDiv.id = "typingIndicator";
        typingDiv.innerHTML = `
                <div class="message-avatar">🤖</div>
                <div class="typing-indicator show">
                    <div class="typing-dots">
                        <span></span>
                        <span></span>
                        <span></span>
                    </div>
                </div>
            `;

        messagesDiv.appendChild(typingDiv);
        requestAnimationFrame(() => {
          messagesDiv.scrollTop = messagesDiv.scrollHeight;
        });
      }

      function hideTypingIndicator() {
        const typingIndicator = document.getElementById("typingIndicator");
        if (typingIndicator) {
          typingIndicator.remove();
        }
      }

      function showError(message) {
        const errorDiv = document.getElementById("errorMessage");
        errorDiv.textContent = "❌ " + message;
        errorDiv.classList.add("show");
      }

      function escapeHtml(text) {
        const div = document.createElement("div");
        div.textContent = text;
        return div.innerHTML.replace(/\n/g, "<br>");
      }
    </script>
  </body>
</html>

Schritt 7: Testen Sie Ihre Chat-Anwendung

Es ist Zeit, unsere KI-Chat-App zu testen und die Abrechnung in Aktion zu sehen! Lassen Sie uns sicherstellen, dass alles von Anfang bis Ende funktioniert.
Was wir testen: Wir werden einige Gespräche mit der KI führen, überprüfen, ob die Tokenereignisse Dodo Payments erreichen, und bestätigen, dass die Abrechnungsberechnungen korrekt sind.
1

Starten Sie den Server

Zuerst stellen Sie sicher, dass alles eingerichtet ist:
  1. Überprüfen Sie, ob Ihre .env Datei alle API-Schlüssel aus Schritt 2 enthält
  2. Starten Sie den Entwicklungsserver:
npm run dev
Sie sollten sehen:
🚀 Server running at http://localhost:3000
📊 Tracking event: ai_chat_usage
👤 Customer ID: {YOUR CUSTOMER_ID}
🔧 Environment: test_mode
Server läuft! Zeit zum Chatten.
2

Öffnen Sie die Chat-Oberfläche

  1. Öffnen Sie Ihren Browser
  2. Navigieren Sie zu http://localhost:3000
  3. Sie sollten die schöne Chat-Oberfläche sehen
Stellen Sie sicher, dass Sie CUSTOMER_ID in server.ts mit Ihrer tatsächlichen Testkunden-ID aus Schritt 4 aktualisieren.
3

Führen Sie Ihr erstes Gespräch

Lassen Sie es uns ausprobieren! Versuchen Sie diese Nachrichten:
  1. “Was ist künstliche Intelligenz?”
  2. “Wie funktioniert maschinelles Lernen?”
  3. “Können Sie neuronale Netzwerke erklären?”
Beobachten Sie, wie sich die Token-Nutzung nach jeder Antwort aktualisiert!
Wenn Sie sehen, dass die KI antwortet und die Tokenanzahl angezeigt wird, funktioniert Ihre App!
4

Überprüfen Sie Ihr Dodo Payments-Dashboard

Lassen Sie uns jetzt überprüfen, ob die Ereignisse empfangen werden:
  1. Öffnen Sie Ihr Dodo Payments-Dashboard
  2. Gehen Sie zu NutzungsabrechnungAI Token Usage Meter
  3. Klicken Sie auf die Registerkarte Ereignisse
  4. Sie sollten Ihre Chat-Ereignisse aufgelistet sehen
Worauf Sie achten sollten:
  • Ereignisnamen: ai_chat_usage
  • Kunden-ID: Ihre Testkunden-ID
Zählerereignisse
Sie sollten ein Ereignis für jede gesendete Nachricht sehen!
5

Überprüfen Sie die Token-Zählung

Lassen Sie uns noch einige Nachrichten senden und überprüfen, ob die Token-Aggregation funktioniert:
  1. Gehen Sie in Ihrem Zähler zur Registerkarte Kunden
  2. Finden Sie Ihren Testkunden
  3. Überprüfen Sie die Spalte “Verbrauchte Einheiten” - sie sollte die insgesamt verwendeten Tokens anzeigen
Zähler Kunden Tokens
Der Zähler summiert automatisch alle totalTokens Werte!
6

Testen Sie das kostenlose Kontingent

Lassen Sie uns genügend Tokens verwenden, um das kostenlose Kontingent zu überschreiten:
  1. Führen Sie mehrere weitere Gespräche (streben Sie ~15.000+ insgesamt an)
  2. Überprüfen Sie erneut Ihre Kunden-Registerkarte im Zähler-Dashboard
  3. Sie sollten jetzt sehen:
    • Verbrauchte Einheiten: 15.000+ Tokens
    • Abrechenbare Einheiten: 5.000 (10.000 kostenlose Tokens angewendet)
    • Gesamtpreis: ~0,05 $
Kostenloses Kontingent Test
Erfolg! Ihre nutzungsbasierte Abrechnung funktioniert perfekt. Kunden werden automatisch basierend auf ihrem tatsächlichen Tokenverbrauch belastet.

Fehlersuche

Häufige Probleme und deren Lösungen:
Mögliche Ursachen:
  • Der Ereignisname stimmt nicht genau mit der Zählerkonfiguration überein
  • Kunden-ID existiert nicht in Ihrem Konto
  • API-Schlüssel ist ungültig oder abgelaufen
  • Netzwerkverbindungsprobleme
Lösungen:
  1. Überprüfen Sie, ob der Ereignisname genau mit der Zählerkonfiguration übereinstimmt (groß- und kleinschreibungsempfindlich: ai_chat_usage)
  2. Überprüfen Sie, ob die Kunden-ID im Dodo Payments-Dashboard vorhanden ist
  3. Testen Sie den API-Schlüssel mit einem einfachen API-Aufruf
  4. Überprüfen Sie die Serverprotokolle auf Fehlermeldungen
Mögliche Ursachen:
  • Modell gibt keine Nutzungsinformationen zurück
  • Falsche SDK-Version
Lösungen:
  1. Testen Sie, ob das Modell die Nutzung zurückgibt:
const response = await generateText({...});
console.log('Usage:', response.usage);
  1. Aktualisieren Sie auf die neueste Blueprints SDK: npm install @dodopayments/ingestion-blueprints@latest
Mögliche Ursachen:
  • Falscher API-Schlüssel für die Umgebung
  • Zusätzliche Leerzeichen oder Anführungszeichen in .env Datei
Lösungen:
  • Stellen Sie sicher, dass der Testschlüssel mit test_ beginnt, der Live-Schlüssel mit live_
  • Entfernen Sie alle Anführungszeichen um die Schlüssel in der .env Datei
  • Generieren Sie bei Bedarf einen neuen Schlüssel
Brauchen Sie Hilfe?

Herzlichen Glückwunsch! Sie haben eine KI-Chat-App erstellt

Sie haben jetzt eine voll funktionsfähige KI-Chat-Anwendung mit automatischer Token-Nutzungsverfolgung und Abrechnung, die von Dodo Payments unterstützt wird. 🎉

Erfahren Sie mehr