ChimerAI Chat Widget — Developer Integration Manual
Version: 1.1
Stand: April 2026
Zielgruppe: Entwickler, die den ChimerAI-Chat in eine bestehende externe Applikation einbetten möchten (React, Vue, Angular, Blazor, WordPress, statisches HTML, etc.)
Inhaltsverzeichnis
- Übersicht
- Voraussetzungen
- API-Key erstellen
- Integration: Script-Tag (einfachste Variante)
- Integration: Programmatisch mit ChimerAI.mount()
- Integration: Async Loader (Performance)
- Integration: React / Next.js
- Konfiguration & Optionen
- Programmatische Steuerung
- Theming (Dark / Light / Auto)
- CORS & Netzwerk-Setup
- Sicherheit & Best Practices
- Backend-Proxy-Pattern (Key verstecken)
- Troubleshooting
- API-Referenz
1. Übersicht
Das ChimerAI Chat Widget ist ein selbstständiges JavaScript-Bundle (Web Component mit Shadow DOM), das in jede beliebige Webseite eingebettet werden kann — unabhängig vom verwendeten Framework.
Eigenschaften:
- ✅ Kein React, kein Tailwind, keine externen Abhängigkeiten nötig
- ✅ Shadow DOM — kein CSS-Leak in deine bestehende App
- ✅ SSE-Streaming (Server-Sent Events) für Echtzeit-Antworten
- ✅ Dark/Light/Auto Theme
- ✅ API-Key-basierte Authentifizierung
- ✅ Programmatische Steuerung (Nachrichten senden, Chat leeren, Model wechseln)
- ✅ Callbacks für Events (onReady, onError, onMessageSent, onResponseReceived)
Architektur:
┌──────────────────────────┐ ┌──────────────────────────────┐
│ Deine externe App │ │ ChimerAI App (Next.js) │
│ (beliebiges Framework) │ │ http://your-chimerai.com │
│ │ │ │
│ ┌────────────────────┐ │ HTTPS │ /widget/chat.js (Bundle) │
│ │ <chimerai-chat> │──┼────────►│ /api/v1/chat/stream (SSE) │
│ │ Web Component │ │ API │ /api/v1/api-keys (Mgmt) │
│ │ Shadow DOM │ │ Key │ │
│ └────────────────────┘ │ │ Auth: x-api-key Header │
│ │ │ CORS: CORS_ALLOWED_ORIGINS │
└──────────────────────────┘ └──────────────────────────────┘
Provider-Routing: Die Widget-Route /api/v1/chat/stream ruft LLM-Provider direkt an — ohne Umweg über den Python AI-Service. Nur für Provider, die Next.js nicht direkt kennt, wird der Python-Service als Fallback genutzt:
Widget-Request → Next.js /api/v1/chat/stream
│
provider.type?
│
├── "openai" / "custom" → direkt api.openai.com (oder custom baseUrl)
├── "anthropic" → direkt api.anthropic.com
└── alles andere → Python AI-Service (AI_SERVICE_URL)
| Provider | Typ im System | Direkter Call? | Python-Service nötig? |
|---|---|---|---|
| OpenAI | openai | ✅ Ja | Nein |
| Claude | anthropic | ✅ Ja | Nein |
| DeepSeek | custom | ✅ Ja | Nein (OpenAI-kompatible API) |
| Groq | groq | ❌ Nein | Ja, muss laufen |
| Ollama | ollama | ❌ Nein | Ja, muss laufen |
google | ❌ Nein | Ja, muss laufen |
💡 DeepSeek hat eine OpenAI-kompatible API. Einfach als "Custom"-Provider mit
baseUrl: https://api.deepseek.com/v1einrichten — funktioniert ohne Python-Service.
2. Voraussetzungen
ChimerAI-App (Backend-Seite)
- Eine laufende ChimerAI-App (erstellt mit
chimerai create) - Datenbank: SQLite (Default für Entwicklung) oder PostgreSQL (Production)
- SQLite:
chimerai create my-app --sqlite— kein Docker nötig, setzt automatischDATABASE_URL=file:./dev.db+provider = "sqlite" - PostgreSQL:
chimerai create my-app(Default) — benötigt Docker oder lokale PostgreSQL-Instanz
- SQLite:
- Mindestens ein konfigurierter AI-Provider (z.B. OpenAI, Anthropic)
- Widget installiert:
chimerai add chat-widget(oder beichimerai createautomatisch dabei)- Erzeugt automatisch:
middleware.ts(CORS-Headers),CORS_ALLOWED_ORIGINSin.env - Wichtig: Nach Installation
pnpm buildausführen, damit die Middleware aktiv wird
- Erzeugt automatisch:
Externe App (Client-Seite)
- Beliebige Webseite/App (HTML, React, Vue, Angular, Blazor, WordPress, etc.)
- Zugriff auf die ChimerAI-App-URL (Netzwerk/Internet)
3. API-Key erstellen
Über die Web-UI
- Öffne deine ChimerAI-App im Browser
- Navigiere zu Einstellungen → API Keys (
/settings/api-keys) - Klicke + Create Key
- Konfiguriere:
- Name: z.B. "Blog Chat Widget"
- Scope: "Chat Widget" (empfohlen) oder Custom-Scopes
- Expiration: 90 Tage (empfohlen)
- Kopiere den Key sofort — er wird nur einmal angezeigt!
Über die API
# API-Key erstellen (als eingeloggter User)
curl -X POST https://your-chimerai-app.com/api/v1/api-keys \
-H "Content-Type: application/json" \
-H "Cookie: next-auth.session-token=YOUR_SESSION" \
-d '{
"name": "Blog Widget",
"scopes": ["chat"],
"expiresInDays": 90
}'
# Response:
# { "key": "sk_live_a1b2c3d4...", "message": "Key created..." }
⚠️ Wichtig: Der vollständige Key (
sk_live_...) wird nur einmal bei Erstellung angezeigt. Danach ist nur noch der Prefix sichtbar. Verloren = neuen Key erstellen.
💡 Hinweis: Der
sk_live_...Key ist ein interner ChimerAI API-Key — er authentifiziert das Widget gegenüber dem Next.js Backend. Er hat nichts mit dem Provider-Key (z.B. OpenAIsk-proj-...) zu tun. Der Provider-Key bleibt sicher im Backend, verschlüsselt in der Datenbank.Widget (sk_live_...) → Next.js Backend (sk-proj-...) → OpenAI/Anthropic/etc.
4. Integration: Script-Tag
Die einfachste Variante — funktioniert in jeder HTML-Seite:
<!DOCTYPE html>
<html>
<head>
<title>Meine Webseite</title>
</head>
<body>
<h1>Willkommen auf meiner Seite</h1>
<!-- 1. Container für den Chat -->
<div id="chimerai-chat" style="width: 400px; height: 600px;"></div>
<!-- 2. Widget-Script laden -->
<script src="https://your-chimerai-app.com/widget/chat.js"></script>
<!-- 3. Widget initialisieren -->
<script>
ChimerAI.mount('#chimerai-chat', {
apiKey: 'sk_live_DEIN_API_KEY',
endpoint: 'https://your-chimerai-app.com',
theme: 'dark',
title: 'AI Assistant',
});
</script>
</body>
</html>
Auto-Mount mit Data-Attributen
Alternativ ohne JavaScript — rein deklarativ:
<div
data-chimerai-chat
data-api-key="sk_live_DEIN_API_KEY"
data-endpoint="https://your-chimerai-app.com"
data-theme="dark"
data-title="AI Assistant"
style="width: 400px; height: 600px;"
></div>
<script src="https://your-chimerai-app.com/widget/chat.js"></script>
Das Widget erkennt data-chimerai-chat automatisch und mountet sich selbst.
5. Integration: Programmatisch
ChimerAI.mount() gibt ein Control-Handle zurück:
<div id="chat" style="width: 400px; height: 600px;"></div>
<script src="https://your-chimerai-app.com/widget/chat.js"></script>
<script>
const chat = ChimerAI.mount('#chat', {
apiKey: 'sk_live_DEIN_API_KEY',
endpoint: 'https://your-chimerai-app.com',
theme: 'auto',
title: 'Support Chat',
model: 'gpt-4',
placeholder: 'Frag mich etwas...',
// Callbacks
onReady: () => {
console.log('Widget geladen');
},
onError: (error) => {
console.error('Widget-Fehler:', error.message);
},
onMessageSent: (msg) => {
console.log('User schrieb:', msg.content);
},
onResponseReceived: (msg) => {
console.log('AI antwortete:', msg.content);
},
});
// Programmatische Steuerung
chat.sendMessage('Hallo!');
chat.setModel('claude-3-haiku');
chat.clearMessages();
chat.destroy(); // Widget entfernen
</script>
6. Integration: Async Loader
Für optimale Performance — das Widget wird asynchron nachgeladen und erscheint als Floating-Button unten rechts:
<!-- Ein einziger Script-Tag — das war's -->
<script
src="https://your-chimerai-app.com/widget/loader.js"
data-api-key="sk_live_DEIN_API_KEY"
data-theme="dark"
data-title="Hilfe"
></script>
Der Loader:
- Lädt
chat.jsasynchron nach - Erstellt einen Container (fixed, bottom-right, 380×520px)
- Mountet das Widget automatisch
Ideal für: Blogs, Landing Pages, Marketing-Seiten.
7. Integration: React / Next.js
Next.js (App Router)
// app/page.tsx
import Script from 'next/script';
export default function Home() {
return (
<div>
<h1>Meine Next.js App</h1>
{/* Chat Container */}
<div id="chimerai-chat" style={{ width: 400, height: 600 }} />
{/* Widget laden */}
<Script
src="http://localhost:3000/widget/chat.js"
strategy="afterInteractive"
onLoad={() => {
(window as any).ChimerAI.mount('#chimerai-chat', {
apiKey: 'sk_live_DEIN_API_KEY',
endpoint: 'http://localhost:3000',
theme: 'dark',
title: 'AI Chat',
});
}}
/>
</div>
);
}
React (Vite / CRA)
// src/ChatWidget.tsx
import { useEffect, useRef } from 'react';
declare global {
interface Window {
ChimerAI: {
mount: (
selector: string | Element,
config: Record<string, any>
) => {
sendMessage: (msg: string) => void;
clearMessages: () => void;
setModel: (model: string) => void;
destroy: () => void;
};
version: string;
};
}
}
export function ChatWidget() {
const containerRef = useRef<HTMLDivElement>(null);
const chatRef = useRef<ReturnType<typeof window.ChimerAI.mount> | null>(null);
useEffect(() => {
// Script laden
const script = document.createElement('script');
script.src = 'https://your-chimerai-app.com/widget/chat.js';
script.onload = () => {
if (containerRef.current && window.ChimerAI) {
chatRef.current = window.ChimerAI.mount(containerRef.current, {
apiKey: 'sk_live_DEIN_API_KEY',
endpoint: 'https://your-chimerai-app.com',
theme: 'auto',
title: 'AI Chat',
});
}
};
document.head.appendChild(script);
return () => {
chatRef.current?.destroy();
script.remove();
};
}, []);
return <div ref={containerRef} style={{ width: 400, height: 600 }} />;
}
Vue 3
<!-- ChatWidget.vue -->
<template>
<div ref="chatContainer" style="width: 400px; height: 600px;" />
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
const chatContainer = ref<HTMLElement | null>(null);
let chatHandle: any = null;
onMounted(() => {
const script = document.createElement('script');
script.src = 'https://your-chimerai-app.com/widget/chat.js';
script.onload = () => {
if (chatContainer.value && (window as any).ChimerAI) {
chatHandle = (window as any).ChimerAI.mount(chatContainer.value, {
apiKey: 'sk_live_DEIN_API_KEY',
endpoint: 'https://your-chimerai-app.com',
theme: 'dark',
});
}
};
document.head.appendChild(script);
});
onUnmounted(() => {
chatHandle?.destroy();
});
</script>
Angular
// chat-widget.component.ts
import { Component, ElementRef, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';
@Component({
selector: 'app-chat-widget',
template: '<div #chatContainer style="width: 400px; height: 600px;"></div>',
})
export class ChatWidgetComponent implements AfterViewInit, OnDestroy {
@ViewChild('chatContainer') container!: ElementRef;
private chatHandle: any;
private script!: HTMLScriptElement;
ngAfterViewInit() {
this.script = document.createElement('script');
this.script.src = 'https://your-chimerai-app.com/widget/chat.js';
this.script.onload = () => {
this.chatHandle = (window as any).ChimerAI.mount(this.container.nativeElement, {
apiKey: 'sk_live_DEIN_API_KEY',
endpoint: 'https://your-chimerai-app.com',
theme: 'auto',
});
};
document.head.appendChild(this.script);
}
ngOnDestroy() {
this.chatHandle?.destroy();
this.script?.remove();
}
}
Blazor (C# / .NET)
✅ Getestet mit: .NET 8 Blazor Server (Interactive Server Rendering), Port 3002 → ChimerAI auf Port 3001
@* Components/Pages/Chat.razor *@
@page "/chat"
@inject IJSRuntime JSRuntime
<h3>ChimerAI Chat</h3>
<div id="chimerai-chat" style="width: 100%; max-width: 500px; height: 600px;"></div>
@code {
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await JSRuntime.InvokeVoidAsync("loadChimerAI");
}
}
}
<!-- App.razor oder _Host.cshtml (Blazor Server) bzw. wwwroot/index.html (Blazor WASM) -->
<script>
function loadChimerAI() {
if (window.ChimerAI) {
ChimerAI.mount('#chimerai-chat', {
apiKey: 'sk_live_DEIN_API_KEY',
endpoint: 'http://localhost:3001', // ChimerAI Next.js App
theme: 'dark',
});
return;
}
var s = document.createElement('script');
s.src = 'http://localhost:3001/widget/chat.js'; // Widget-Bundle von ChimerAI laden
s.onload = function () {
ChimerAI.mount('#chimerai-chat', {
apiKey: 'sk_live_DEIN_API_KEY',
endpoint: 'http://localhost:3001',
theme: 'dark',
});
};
document.head.appendChild(s);
}
</script>
Blazor-Setup Checkliste:
- ChimerAI-App (
helloworld) auf Port 3001 starten:pnpm build && pnpm start - In der ChimerAI-App unter
/settings/api-keyseinen API-Key erstellen CORS_ALLOWED_ORIGINS=http://localhost:3002in.envder ChimerAI-App setzen- ChimerAI-App neu starten (nach
.env-Änderung!) - Blazor-App auf Port 3002 starten:
dotnet run - Im Browser
http://localhost:3002/chatöffnen
8. Konfiguration & Optionen
mount() Optionen
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
apiKey | string | — | Pflicht. API-Key (sk_live_... oder sk_test_...) |
endpoint | string | '' (same-origin) | URL der ChimerAI-App. Leer = same-origin Requests |
theme | 'light' | 'dark' | 'auto' | 'auto' | Theme. auto folgt dem System (prefers-color-scheme) |
model | string | '' | AI-Model (z.B. 'gpt-4o', 'claude-sonnet-4-20250514'). Optional — leer = Provider-Default (s.u.) |
title | string | 'AI Chat' | Titel in der Header-Leiste |
placeholder | string | 'Type a message...' | Placeholder-Text im Input-Feld |
width | string | — | CSS-Breite des Widgets (z.B. '400px', '100%') |
height | string | — | CSS-Höhe des Widgets (z.B. '600px', '100vh') |
onReady | () => void | — | Callback wenn Widget geladen ist |
onError | (error: Error) => void | — | Callback bei Fehlern |
onMessageSent | (msg: {role, content}) => void | — | Callback wenn User Nachricht sendet |
onResponseReceived | (msg: {role, content}) => void | — | Callback wenn AI-Antwort komplett |
Data-Attribute (für Auto-Mount)
| Attribut | Entspricht |
|---|---|
data-chimerai-chat | Aktiviert Auto-Mount (Pflicht) |
data-api-key | apiKey |
data-endpoint | endpoint |
data-theme | theme |
data-model | model |
data-title | title |
data-placeholder | placeholder |
9. Programmatische Steuerung
const chat = ChimerAI.mount('#container', { ... });
// Nachricht senden (löst Chat-Streaming aus)
chat.sendMessage('Erkläre mir Quantencomputing');
// Alle Nachrichten löschen
chat.clearMessages();
// Model wechseln (für nächste Nachricht)
chat.setModel('claude-3-haiku');
// Widget komplett entfernen
chat.destroy();
// Version prüfen
console.log(ChimerAI.version); // "1.0.0"
10. Theming
Dark Theme
ChimerAI.mount('#chat', { theme: 'dark', ... });
Light Theme
ChimerAI.mount('#chat', { theme: 'light', ... });
Auto (System-Preference)
ChimerAI.mount('#chat', { theme: 'auto', ... });
Folgt prefers-color-scheme — schaltet automatisch um wenn der User sein OS-Theme ändert.
CSS-Isolation
Das Widget nutzt Shadow DOM — deine CSS-Styles beeinflussen das Widget nicht und umgekehrt. Es gibt kein CSS-Leak in beide Richtungen.
11. CORS & Netzwerk-Setup
Middleware & CORS (automatisch)
chimerai add chat-widget generiert automatisch:
middleware.tsim Projekt-Root — setzt CORS-Headers für/api/v1/*Routen + Security-Headers für alle anderen RoutenCORS_ALLOWED_ORIGINSin.env— komma-getrennte Liste erlaubter Origins (Default:*)
⚠️ Wichtig: Nach
chimerai add chat-widgetmuss die App neu gebaut werden (pnpm build), damit die Middleware aktiv wird.pnpm dev(Dev-Server) kompiliert on-the-fly,pnpm start(Production) braucht ein vorherigespnpm build.
ChimerAI-App konfigurieren
Damit das Widget von einer externen Domain API-Requests machen kann, muss die ChimerAI-App die Domain erlauben.
In .env der ChimerAI-App:
# Komma-getrennte Liste erlaubter Widget-Origins (primäre Variable)
CORS_ALLOWED_ORIGINS=https://my-blog.com,https://my-shop.com
# Legacy-Alias (wird ebenfalls gelesen, beide werden gemergt)
# WIDGET_ALLOWED_ORIGINS=http://localhost:3002
💡 Die Middleware liest beide Variablen (
CORS_ALLOWED_ORIGINS+WIDGET_ALLOWED_ORIGINS) und mergt sie. Für neue Projekte empfehlen wirCORS_ALLOWED_ORIGINS.
Nach Änderung: ChimerAI-App neu starten (pnpm build && pnpm start oder pnpm dev neustarten).
Was passiert bei fehlender CORS-Konfiguration?
Browser blockiert den Request mit:
Access to fetch at 'https://chimerai-app.com/api/v1/chat/stream'
from origin 'https://my-blog.com' has been blocked by CORS policy
Lösung: Domain zu CORS_ALLOWED_ORIGINS in .env hinzufügen und App neustarten.
Lokale Entwicklung
Für lokales Testen:
CORS_ALLOWED_ORIGINS=http://localhost:3002,http://localhost:5173,http://localhost:4200
| Port | Typisches Framework |
|---|---|
| 3002 | Blazor (getestet ✅) |
| 5173 | Vite (React/Vue/Svelte) |
| 4200 | Angular |
| 5000 | Blazor (alternativ) |
| 8080 | WordPress (Local) |
Provider-Kompatibilität & Besonderheiten
Die Widget-Route /api/v1/chat/stream unterstützt verschiedene LLM-Provider mit unterschiedlichen Eigenheiten:
| Provider | Einrichtung | Besonderheiten |
|---|---|---|
OpenAI (gpt-4, gpt-4o-mini, ...) | Provider-Typ: openai, API-Key: sk-proj-... | Standard-Referenz, keine Besonderheiten |
Anthropic/Claude (claude-3.5-sonnet, ...) | Provider-Typ: anthropic, API-Key: sk-ant-... | max_tokens ist Pflichtfeld (intern auf 4096 gesetzt); System-Messages werden separat behandelt |
DeepSeek (deepseek-chat, deepseek-coder) | Provider-Typ: custom, baseUrl: https://api.deepseek.com/v1 | OpenAI-kompatible API; höhere Latenz (Server in Asien); manche Features wie json_mode eingeschränkt |
| Groq, Ollama, Google, etc. | Provider-Typ: groq/ollama/google | Benötigen den Python AI-Service (AI_SERVICE_URL), der über LiteLLM alle Provider abstrahiert |
⚠️ Wichtig: Für OpenAI, Claude und DeepSeek wird der Python AI-Service nicht benötigt. Die Next.js-App kommuniziert direkt mit dem Provider. Nur für andere Provider-Typen muss der Python-Service laufen (
http://localhost:8001).
💡 ChatGPT ≠ OpenAI API: Ein ChatGPT Plus/Pro-Abo gibt dir keinen API-Key. Du brauchst einen separaten OpenAI Platform API-Key (
sk-proj-...) von platform.openai.com mit eigener Abrechnung.
Model-Auflösung (Prioritätskette)
Wenn das Widget einen Chat-Request sendet, wird das Model wie folgt bestimmt:
1. model aus Widget-Config → Widget-Einbetter hat explizit gewählt
z.B. ChimerAI.mount({ model: 'gpt-4o' })
↓ (falls leer)
2. provider.config.defaultModel → Provider-Admin hat Default konfiguriert
z.B. "gpt-4o-mini" in Provider-Settings
↓ (falls leer)
3. 400 Error → Klare Fehlermeldung:
"No model specified and no default model
configured for this provider"
💡 Kein hardcodierter Modellname — da der Provider OpenAI, Claude, DeepSeek, Ollama oder ein beliebiges LLM sein kann, gibt es keinen universellen Default. Der Provider-Admin legt das Default-Model in den Provider-Settings fest.
Default-Model im Provider setzen:
- Öffne deine ChimerAI-App im Browser
- Navigiere zu Providers (
/providers) - Klicke auf Edit bei deinem Provider (z.B. OpenAI)
- Trage im Feld Default Model den Modellnamen ein (z.B.
gpt-4o-mini) - Save klicken
💡 Beim Erstellen eines Projekts mit
chimerai createwird das Default-Model automatisch gesetzt: OpenAI →gpt-4o-mini, Anthropic →claude-sonnet-4-20250514.
Beispiel: Widget ohne Model (nutzt Provider-Default):
// Minimal-Konfiguration — Server wählt das Default-Model des Providers
ChimerAI.mount('#chat', {
apiKey: 'sk_live_...',
endpoint: 'http://localhost:3001',
});
Beispiel: Widget mit explizitem Model (überschreibt Provider-Default):
// Explizit — überschreibt den Provider-Default
ChimerAI.mount('#chat', {
apiKey: 'sk_live_...',
endpoint: 'http://localhost:3001',
model: 'gpt-4o',
});
12. Sicherheit & Best Practices
API-Key Sichtbarkeit
⚠️ API-Keys im Client-Side JavaScript sind sichtbar (DevTools, View Source). Das ist ein akzeptiertes Risiko — vergleichbar mit Google Maps Keys oder Firebase Client-Keys.
Empfohlene Schutzmaßnahmen
| Maßnahme | Warum | Wie |
|---|---|---|
| Minimale Scopes | Begrenzt was der Key kann | Nur chat Scope, nie * |
| allowedOrigins | Blockiert Nutzung von fremden Domains | In API-Key-Management setzen |
| Kurze Expiration | Begrenzt Zeitfenster bei Leak | 30-90 Tage, dann rotieren |
| Separate Keys | Ein Key pro Domain | Bei Kompromittierung nur eine Domain betroffen |
| Rate-Limiting | Begrenzt Missbrauch | Automatisch: 60 req/min pro API-Key |
| Monitoring | Ungewöhnliche Nutzung erkennen | lastUsedAt in Key-Management prüfen |
Was allowedOrigins schützt (und was nicht)
| Szenario | Geschützt? |
|---|---|
| Fremde Website macht Cross-Origin-Request | ✅ Ja (Browser setzt Origin-Header) |
| Script-Kiddie nutzt Key in Postman | ✅ Teilweise (Standard-Origin wird blockiert) |
| Angreifer nutzt cURL mit gefälschtem Origin | ❌ Nein (Origin ist server-seitig frei setzbar) |
| Key in öffentlichem Git-Repo | ❌ Nein (Key muss revoked werden) |
Für höchste Sicherheit: Backend-Proxy
Wenn der Key komplett unsichtbar sein muss → siehe Kapitel 13.
13. Backend-Proxy-Pattern
Für interne Tools, B2B-Apps oder wenn der API-Key nicht im Client-Code erscheinen darf.
Prinzip: Dein eigenes Backend hält den Key als Environment Variable und leitet Requests weiter.
Browser (KEIN Key) → Dein Backend /api/proxy/chat → ChimerAI API (Key als Env-Var)
Widget-Konfiguration (kein apiKey!)
ChimerAI.mount('#chat', {
endpoint: '/api/proxy', // Dein eigenes Backend
theme: 'dark',
// KEIN apiKey — der liegt auf dem Server
});
Proxy-Implementierung
Node.js / Express
// server.js
const express = require('express');
const app = express();
const CHIMERAI_URL = process.env.CHIMERAI_URL; // z.B. https://my-chimerai.com
const CHIMERAI_KEY = process.env.CHIMERAI_API_KEY; // sk_live_...
app.post('/api/proxy/api/v1/chat/stream', async (req, res) => {
const response = await fetch(`${CHIMERAI_URL}/api/v1/chat/stream`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': CHIMERAI_KEY,
},
body: JSON.stringify(req.body),
});
// SSE-Stream weiterleiten
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
response.body.pipeTo(
new WritableStream({
write(chunk) {
res.write(chunk);
},
close() {
res.end();
},
})
);
});
ASP.NET Core (Blazor)
[ApiController]
[Route("api/proxy")]
public class ChatProxyController : ControllerBase
{
private readonly IConfiguration _config;
private readonly HttpClient _http;
public ChatProxyController(IConfiguration config, HttpClient http)
{
_config = config;
_http = http;
}
[HttpPost("api/v1/chat/stream")]
public async Task ProxyChat()
{
var apiKey = _config["ChimerAI:ApiKey"];
var baseUrl = _config["ChimerAI:Url"];
using var reader = new StreamReader(Request.Body);
var body = await reader.ReadToEndAsync();
var request = new HttpRequestMessage(HttpMethod.Post, $"{baseUrl}/api/v1/chat/stream")
{
Content = new StringContent(body, Encoding.UTF8, "application/json"),
};
request.Headers.Add("x-api-key", apiKey);
var response = await _http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
Response.ContentType = "text/event-stream";
await response.Content.CopyToAsync(Response.Body);
}
}
Python (FastAPI)
import httpx
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
app = FastAPI()
CHIMERAI_URL = os.environ["CHIMERAI_URL"]
CHIMERAI_KEY = os.environ["CHIMERAI_API_KEY"]
@app.post("/api/proxy/api/v1/chat/stream")
async def proxy_chat(request: Request):
body = await request.body()
async with httpx.AsyncClient() as client:
response = await client.post(
f"{CHIMERAI_URL}/api/v1/chat/stream",
content=body,
headers={
"Content-Type": "application/json",
"x-api-key": CHIMERAI_KEY,
},
timeout=120,
)
async def stream():
async for chunk in response.aiter_bytes():
yield chunk
return StreamingResponse(stream(), media_type="text/event-stream")
14. Troubleshooting
Widget wird nicht angezeigt
| Symptom | Ursache | Lösung |
|---|---|---|
| Leerer Container | Script nicht geladen | DevTools → Network → Prüfe ob chat.js geladen wird (200 OK) |
ChimerAI is not defined | Script noch nicht geladen | strategy="afterInteractive" nutzen oder auf onload warten |
| Widget ohne Inhalt | Container hat keine Höhe | Explizit height setzen: style="height: 600px" |
CORS-Fehler
| Fehlermeldung | Lösung |
|---|---|
blocked by CORS policy | Domain zu CORS_ALLOWED_ORIGINS in .env der ChimerAI-App hinzufügen |
No 'Access-Control-Allow-Origin' header | App neu bauen (pnpm build) und neustarten — nicht nur Hot-Reload! |
| Preflight (OPTIONS) schlägt fehl | Middleware der ChimerAI-App prüfen — x-api-key muss in Allow-Headers stehen |
| Middleware wird nicht erkannt | pnpm build vergessen? pnpm start braucht ein vorheriges pnpm build |
| Middleware-Manifest leer | .next-Ordner löschen, pnpm build erneut ausführen |
Chat-Fehler
| Symptom | Ursache | Lösung |
|---|---|---|
⚠ Request failed (401) | Ungültiger oder fehlender API-Key | Key in API-Key-Management prüfen, nicht abgelaufen/revoked? |
⚠ Request failed (403) | Key hat nicht den richtigen Scope | Key muss chat Scope haben |
⚠ Request failed (429) | Rate-Limit erreicht | Warten (60 req/min pro API-Key) |
⚠ Connection failed | ChimerAI-App nicht erreichbar | URL/Port prüfen, ChimerAI-App gestartet? |
| Keine Antwort (Streaming stoppt) | Kein AI-Provider konfiguriert | In ChimerAI-App mindestens einen Provider einrichten |
Debug-Modus
Öffne die Browser DevTools:
- Console — Fehler-Meldungen des Widgets
- Network —
chat.jsladen (200?) +chat/streamRequest (200? Headers?) - Elements —
<chimerai-chat>suchen → Shadow DOM aufklappen → DOM inspizieren
15. API-Referenz
Globales Objekt: window.ChimerAI
ChimerAI.mount(selector, config)
Mountet ein Chat-Widget in den angegebenen Container.
Parameter:
| Parameter | Typ | Beschreibung |
|---|---|---|
selector | string | Element | CSS-Selector oder DOM-Element |
config | object | Konfiguration (siehe Kapitel 8) |
Returns: ChatHandle
interface ChatHandle {
sendMessage(content: string): void;
clearMessages(): void;
setModel(model: string): void;
destroy(): void;
}
ChimerAI.version
Gibt die Widget-Version zurück (string).
SSE-Stream-Format
Das Widget kommuniziert via Server-Sent Events. Jede Zeile:
data: {"type":"token","content":"Hallo"}
data: {"type":"token","content":" Welt"}
data: {"type":"done","conversationId":"abc123","model":"gpt-4","tokens":42}
data: [DONE]
type | Beschreibung |
|---|---|
token | Ein Streaming-Token (Textfragment) |
done | Stream abgeschlossen — enthält Metadaten |
error | Fehler — enthält message |
info | Info-Nachricht (optional) |
HTTP-Endpunkte
| Endpunkt | Methode | Header | Beschreibung |
|---|---|---|---|
/widget/chat.js | GET | — | Widget-Bundle (statische Datei) |
/api/v1/chat/stream | POST | x-api-key, Content-Type: application/json | Chat-Streaming (SSE) |
/api/v1/api-keys | GET | Session-Cookie | API-Keys auflisten |
/api/v1/api-keys | POST | Session-Cookie | Neuen Key erstellen |
/api/v1/api-keys/[id] | DELETE | Session-Cookie | Key revoken |
Request-Body: /api/v1/chat/stream
{
"messages": [{ "role": "user", "content": "Hallo!" }],
"model": "gpt-4",
"conversationId": "optional-existing-id"
}
Rate-Limiting Headers
Bei jedem API-Response:
| Header | Beschreibung |
|---|---|
X-RateLimit-Remaining | Verbleibende Requests im Fenster |
X-RateLimit-Reset | Unix-Timestamp wann das Fenster zurückgesetzt wird |
Retry-After | Sekunden bis zum nächsten erlaubten Request (nur bei 429) |
Schnellstart-Checkliste
- ChimerAI-App läuft und hat mindestens einen AI-Provider
-
chimerai add chat-widgetausgeführt (Widget-Dateien +middleware.tsvorhanden) -
pnpm buildin der ChimerAI-App ausgeführt (damit Middleware aktiv wird) - API-Key erstellt unter
/settings/api-keys -
CORS_ALLOWED_ORIGINSin.enventhält deine externe Domain (z.B.http://localhost:3002) - ChimerAI-App nach
.env-Änderung neu gestartet (pnpm build && pnpm start) -
<script src=".../widget/chat.js">in deine externe App eingebaut -
ChimerAI.mount()mit echtem API-Key undendpointkonfiguriert - Im Browser testen: Widget sichtbar, Chat funktioniert ✅