amirsharfu's picture
Inkling Playground: static multimodal demo with OAuth + streamed reasoning
0ef9612 verified
Raw
History Blame Contribute Delete
2.93 kB
// Streaming client for Inkling via the Hugging Face Inference Providers router.
export const MODEL_ID = "thinkingmachines/Inkling";
const ENDPOINT = "https://router.huggingface.co/together/v1/chat/completions";
/** Build the single multimodal user message. */
export function buildMessage(prompt, imageUri, audioB64) {
const parts = [];
if (imageUri) {
parts.push({ type: "image_url", image_url: { url: imageUri } });
}
if (audioB64) {
parts.push({ type: "input_audio", input_audio: { data: audioB64, format: "wav" } });
}
parts.push({ type: "text", text: prompt });
return { role: "user", content: parts };
}
/** Pull a human-readable message out of a provider error body. */
function describeError(status, body) {
try {
const parsed = JSON.parse(body);
const inner = parsed?.error?.message ?? parsed?.message;
if (typeof inner === "string") return inner;
if (inner?.message) return inner.message;
} catch {
/* fall through to the raw body */
}
return body?.slice(0, 400) || `Request failed with status ${status}.`;
}
/**
* Stream a completion, invoking callbacks as tokens arrive.
*
* Inkling is a reasoning model: it emits `reasoning` deltas before committing
* to `content`, so both are surfaced separately.
*/
export async function streamCompletion({
token,
prompt,
imageUri = null,
audioB64 = null,
maxTokens = 2048,
temperature = 0.7,
signal,
onReasoning = () => {},
onContent = () => {},
}) {
const response = await fetch(ENDPOINT, {
method: "POST",
signal,
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: MODEL_ID,
messages: [buildMessage(prompt, imageUri, audioB64)],
max_tokens: maxTokens,
temperature,
stream: true,
}),
});
if (!response.ok) {
throw new Error(describeError(response.status, await response.text()));
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
for (;;) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
// Keep the trailing fragment; it may be half an event.
buffer = lines.pop() ?? "";
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed.startsWith("data:")) continue;
const payload = trimmed.slice(5).trim();
if (payload === "[DONE]") return;
let parsed;
try {
parsed = JSON.parse(payload);
} catch {
continue; // Ignore keep-alives and partial frames.
}
const delta = parsed?.choices?.[0]?.delta;
if (!delta) continue;
const thought = delta.reasoning ?? delta.reasoning_content;
if (thought) onReasoning(thought);
if (delta.content) onContent(delta.content);
}
}
}