AbsoluteJS

React AI

The useAIStream hook and AIStreamProvider connect your React components to the AI streaming WebSocket. Import from @absolutejs/ai/react.

#useAIStream

The primary hook for AI streaming. Pass the WebSocket path and an optional conversation ID. It manages the connection, message state, and streaming lifecycle.

TSX
import { useAIStream } from '@absolutejs/ai/react';

export const Chat = ({ conversationId }: { conversationId?: string }) => {
  const { messages, send, cancel, branch, isStreaming, error } =
    useAIStream('/chat', conversationId);

  const handleSend = (text: string) => {
    send(text);
  };

  return (
    <div>
      {messages.map((msg) => (
        <div key={msg.id}>
          <strong>{msg.role}:</strong> {msg.content}
          {msg.thinking && (
            <details>
              <summary>Thinking</summary>
              <p>{msg.thinking}</p>
            </details>
          )}
          {msg.toolCalls?.map((tool) => (
            <details key={tool.name}>
              <summary>Tool: {tool.name}</summary>
              <pre>{tool.result}</pre>
            </details>
          ))}
        </div>
      ))}

      {isStreaming && <button onClick={cancel}>Stop</button>}
      {error && <p style={{ color: 'red' }}>{error}</p>}
    </div>
  );
};

#Return Type

TS
// useAIStream return type
{
  // Send a message with optional file attachments
  send: (content: string, attachments?: AIAttachment[]) => void;

  // Cancel the current streaming response
  cancel: () => void;

  // Branch conversation from a specific message
  branch: (messageId: string, content: string) => void;

  // Current conversation messages
  messages: AIMessage[];

  // Whether a response is currently streaming
  isStreaming: boolean;

  // Error message or null
  error: string | null;
}

#AIStreamProvider

Wrap your component tree with AIStreamProvider to share a single WebSocket connection across multiple components. Child components can call useAIStream() without a path argument to use the shared connection.

TSX
import { AIStreamProvider } from '@absolutejs/ai/react';

// Wrap your app to share a single WebSocket connection
export const App = ({ children }: { children: React.ReactNode }) => (
  <AIStreamProvider path="/chat">
    {children}
  </AIStreamProvider>
);

// Any descendant can call useAIStream() without a path
import { useAIStream } from '@absolutejs/ai/react';

const ChatWidget = () => {
  // Uses the connection from AIStreamProvider
  const { messages, send } = useAIStream();
  // ...
};

#File Attachments

Send images and PDFs alongside messages by converting files to base64 and passing them as the second argument to send().

TSX
import { useAIStream } from '@absolutejs/ai/react';
import type { AIAttachment } from '@absolutejs/absolute';

const ChatWithFiles = () => {
  const { send } = useAIStream('/chat');

  const handleFileUpload = async (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,
    };

    send('Describe this image', [attachment]);
  };

  return <input type="file" onChange={(e) => {
    if (e.target.files?.[0]) handleFileUpload(e.target.files[0]);
  }} />;
};