HTML / Vanilla AI
The createAIStream function from @absolutejs/ai/client is the framework-agnostic AI client. It works with plain HTML, HTMX, or any environment without a framework: same API surface as the React, Vue, Svelte, and Angular bindings.
The framework hooks (
useAIStream, createAIStream, AIStreamService) are thin wrappers around this same client. The only addition here is a subscribe callback so you can react to state changes without framework reactivity.#createAIStream
Call createAIStream with the WebSocket path. Use subscribe() to re-render when state changes, then read messages, isStreaming, and error as getter properties.
TS
import { createAIStream } from '@absolutejs/ai/client';
const ai = createAIStream('/chat');
// Subscribe to state changes
ai.subscribe(() => {
renderMessages(ai.messages);
toggleSpinner(ai.isStreaming);
showError(ai.error);
});
// Send a message
document.querySelector('#send')!.addEventListener('click', () => {
const input = document.querySelector<HTMLInputElement>('#input')!;
ai.send(input.value);
input.value = '';
});
// Cancel streaming
document.querySelector('#cancel')!.addEventListener('click', () => {
ai.cancel();
});#Return Type
TS
// createAIStream return type
{
send: (content: string, attachments?: AIAttachment[]) => void;
cancel: () => void;
branch: (messageId: string, content: string) => void;
destroy: () => void;
// Subscribe to state changes : returns an unsubscribe function
subscribe: (callback: () => void) => () => void;
get messages(): AIMessage[]; // Current conversation messages
get isStreaming(): boolean; // Whether a response is streaming
get error(): string | null; // Error message or null
}#Rendering Messages
Build your own render loop inside the subscribe callback. Each message has content, thinking, and toolCalls you can display however you want.
TS
import { createAIStream } from '@absolutejs/ai/client';
import type { AIMessage } from '@absolutejs/absolute';
const ai = createAIStream('/chat');
const container = document.querySelector<HTMLElement>('#messages')!;
const renderMessage = (msg: AIMessage) => {
const div = document.createElement('div');
div.className = `message ${msg.role}`;
div.textContent = msg.content;
if (msg.thinking) {
const details = document.createElement('details');
details.innerHTML = `<summary>Thinking</summary><p>${msg.thinking}</p>`;
div.appendChild(details);
}
if (msg.toolCalls) {
for (const tool of msg.toolCalls) {
const details = document.createElement('details');
details.innerHTML = `<summary>Tool: ${tool.name}</summary><pre>${tool.result ?? ''}</pre>`;
div.appendChild(details);
}
}
return div;
};
ai.subscribe(() => {
container.replaceChildren(...ai.messages.map(renderMessage));
});#File Attachments
Send images and PDFs alongside messages by converting files to base64 and passing them as the second argument to send().
TS
import { createAIStream } from '@absolutejs/ai/client';
import type { AIAttachment } from '@absolutejs/absolute';
const ai = createAIStream('/chat');
const sendWithFile = async (text: string, file: File) => {
const buffer = await file.arrayBuffer();
const base64 = btoa(String.fromCharCode(...new Uint8Array(buffer)));
const attachment: AIAttachment = {
data: base64,
media_type: file.type as AIAttachment['media_type'],
name: file.name,
};
ai.send(text, [attachment]);
};