@kitn.ai/chat
Version:
Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML. Authored in SolidJS.
69 lines (57 loc) • 2.23 kB
text/mdx
import { Meta } from '@storybook/addon-docs/blocks';
<Meta title="Docs/Recipes/Streaming (OpenRouter)" />
# Streaming from OpenRouter
[OpenRouter](https://openrouter.ai) exposes an OpenAI-compatible streaming API (Server-Sent Events). Wire it into the `submit` event:
> **Security:** never ship an API key to the browser. In production, point `fetch` at your own backend that proxies to OpenRouter and injects the key.
```js
chat.addEventListener('kc-submit', async (e) => {
const text = e.detail.value.trim();
if (!text) return;
// 1. Show the user message
const history = [...chat.messages, { id: crypto.randomUUID(), role: 'user', content: text }];
chat.messages = history;
chat.loading = true;
// 2. Empty assistant placeholder to stream into
const assistantId = crypto.randomUUID();
chat.messages = [...history, { id: assistantId, role: 'assistant', content: '' }];
// In production, replace this with your own proxy endpoint.
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${OPENROUTER_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'anthropic/claude-sonnet-4',
stream: true,
messages: history.map((m) => ({ role: m.role, content: m.content })),
}),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let answer = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop();
for (const line of lines) {
const s = line.trim();
if (!s.startsWith('data:')) continue;
const payload = s.slice(5).trim();
if (payload === '[DONE]') continue;
try {
const delta = JSON.parse(payload).choices?.[0]?.delta?.content;
if (!delta) continue;
answer += delta;
chat.messages = chat.messages.map((m) =>
m.id === assistantId ? { ...m, content: answer } : m
);
} catch { /* ignore non-JSON keep-alive lines */ }
}
}
chat.loading = false;
});
```