Der Endpunkt /v1/chat/completions streamt mit "stream": true Server-Sent Events im OpenAI-Format. Jeder Frame ist data: <chat.completion.chunk>; die Orchestrierungs-Schritte (Routing, Decomposition, Subtasks, Synthese) kommen live als delta.content, danach die Antwort, ein usage-Chunk und data: [DONE].
enk_…) niemals in Client-/Browser-Code — er wäre für jeden sichtbar. Für die Produktion den Aufruf über deinen eigenen Server proxen (siehe unten). Das Browser-Snippet ist nur zum Prototyping.Reines fetch + ReadableStream — parst die SSE-Frames und rendert Orchestrierungs-Events und Antwort in dein eigenes Element:
// Prototyping only — see the security note below before shipping.
async function askStream(prompt, onDelta, onEvent) {
const res = await fetch("https://enigmanet.eu/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": "Bearer enk_…", // do NOT ship a real key in client code
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gemma3:4b",
stream: true,
messages: [{ role: "user", content: prompt }],
}),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const frames = buffer.split("\n\n");
buffer = frames.pop() ?? ""; // keep the last, possibly-partial frame
for (const frame of frames) {
const line = frame.replace(/^data: /, "").trim();
if (!line) continue;
if (line === "[DONE]") return;
const chunk = JSON.parse(line);
const delta = chunk.choices?.[0]?.delta?.content;
if (delta) onDelta(delta); // orchestration lines, then the answer
if (chunk.enigma) onEvent?.(chunk.enigma); // structured: { event, area, task }
}
}
}
// Usage: render into your own element
const out = document.getElementById("answer");
askStream(
"Vergleiche Rust und Go für Backend-Services.",
(text) => { out.textContent += text; },
(ev) => { console.log("orchestration:", ev.event, ev.area ?? ev.task ?? ""); },
);Dein Backend hält den Key und reicht den Stream durch — der Browser ruft nur deinen eigenen Endpunkt auf:
// Recommended for production: keep the API key on YOUR server and proxy the
// stream, so it never reaches the browser. (Next.js App Router example.)
// app/api/ask/route.ts
export async function POST(req: Request) {
const body = await req.json();
const upstream = await fetch("https://enigmanet.eu/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.ENIGMA_API_KEY}`, // server-side env
"Content-Type": "application/json",
},
body: JSON.stringify({ ...body, stream: true }),
});
// Pass the SSE stream straight through to the browser.
return new Response(upstream.body, {
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache, no-transform" },
});
}
// The browser then fetches YOUR /api/ask with the same SSE-parsing loop above.data: { id, object:"chat.completion.chunk", created, model, choices:[{ index, delta, finish_reason }] }delta.role = "assistant". Event-/Antwort-Chunks: delta.content.enigma: { event, area, task } — z. B. routing_started, routing_decision, decompose_started, subtask_created, synthesize_started. Strikte OpenAI-Clients ignorieren dieses Feld.finish_reason:"stop" + usage. Danach data: [DONE].Volle Referenz: API-Doku.