@kitn.ai/ui
Version:
Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML. Authored in SolidJS.
89 lines (80 loc) • 3.63 kB
text/typescript
import type { Integration } from '../types';
const cloudflare: Integration = {
id: 'cloudflare',
title: 'Cloudflare AI',
category: 'provider',
language: 'ts',
streamFormat: 'openai-sse',
envVars: ['CF_ACCOUNT_ID', 'CF_API_TOKEN'],
routeTemplates: {
next: `// app/api/chat/route.ts — proxy Workers AI, keep the token server-side
export async function POST(req: Request) {
const { messages } = await req.json();
const upstream = await fetch(
\`https://api.cloudflare.com/client/v4/accounts/\${process.env.CF_ACCOUNT_ID}/ai/v1/chat/completions\`,
{
method: 'POST',
headers: {
Authorization: \`Bearer \${process.env.CF_API_TOKEN}\`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: '@cf/meta/llama-3.1-8b-instruct',
messages,
stream: true,
}),
},
);
// Workers AI returns OpenAI-format SSE — pass it straight through.
return new Response(upstream.body, { headers: { 'Content-Type': 'text/event-stream' } });
}`,
worker: `// Worker handler — env.AI is bound in wrangler.toml
// env.AI.run emits Cloudflare-native SSE (data: {"response":"<token>"}).
// The TransformStream below re-frames each chunk to OpenAI-format SSE so
// kai-chat's reader works without any client-side changes.
export default {
async fetch(req: Request, env: Env): Promise<Response> {
const { messages } = await req.json();
const nativeStream = await env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
messages,
stream: true,
});
// Re-frame Cloudflare-native SSE → OpenAI-format SSE
const { readable, writable } = new TransformStream();
const writer = writable.getWriter();
const encoder = new TextEncoder();
const decoder = new TextDecoder();
(async () => {
const reader = (nativeStream as ReadableStream<Uint8Array>).getReader();
let buffer = '';
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 { response } = JSON.parse(payload) as { response?: string };
if (response == null) continue;
const openaiChunk = JSON.stringify({ choices: [{ delta: { content: response } }] });
await writer.write(encoder.encode(\`data: \${openaiChunk}\\n\\n\`));
} catch { /* skip malformed lines */ }
}
}
await writer.write(encoder.encode('data: [DONE]\\n\\n'));
await writer.close();
})();
return new Response(readable, { headers: { 'Content-Type': 'text/event-stream' } });
},
};`,
},
streamMapping: "Workers AI via the OpenAI-compatible HTTP endpoint returns OpenAI-format SSE — pipe upstream.body straight to the browser; kai-chat's reader handles it. The native env.AI binding streams Cloudflare's own format (data: {\"response\":\"...token...\"}); the worker route template re-frames these chunks to OpenAI-format SSE via a TransformStream before returning.",
runNote: 'Set CF_ACCOUNT_ID and CF_API_TOKEN. Model ids are prefixed with @cf/, e.g. @cf/meta/llama-3.1-8b-instruct. For the AI binding (worker key), add an [ai] block with binding = "AI" in wrangler.toml.',
docsSlug: 'integrations/cloudflare-ai',
};
export default cloudflare;