npm package: @ixo/oracles-ui
Description: Client SDK for interacting with IXO Oracles
Latest version: 4.0.3

# `@ixo/oracles-ui`

## Introduction

`@ixo/oracles-ui` is a set of React hooks and utilities designed to integrate conversational AI (e.g., Guru AI) into your applications. It helps developers:

- Manage and switch between multiple AI chat sessions.
- Send messages and receive real-time streamed responses.
- Dynamically render custom UI components (e.g., cards, forms, data viewers) driven by the AI’s output.
- Maintain a seamless and interactive user experience across both web and mobile (React Native) platforms.

## Installation

```bash
pnpm add @ixo/oracles-ui
```

*(You may also use `npm` or `yarn`.)*

## Getting Started

To start, wrap your application in the `UseOraclesProvider` component:

```jsx
import { UseOraclesProvider } from '@ixo/oracles-ui';

function App() {
  return (
   <UseOraclesProvider
      apiKey="key"
      apiURL="url">
      {/* Your application code */}
    </UseOraclesProvider>
  );
}
```

This sets up the necessary context for the hooks and components.

---

## Core Hooks Overview

The package provides several core hooks to handle sessions, messages, and direct communication with the AI.

### 1. `useChatSessionsList`

**Purpose:** Fetch and manage a list of available chat sessions, as well as create new sessions.

**Returned Values:**
- `sessions`: An array of session objects, each containing an `id`, `title`, and `oracleName`.
- `isLoading`: Boolean indicating if sessions are currently loading.
- `error`: Any error that occurred during the session fetch.
- `revalidate`: A function to manually refresh the session list.
- `createSession`: A function to create a new session.

**Endpoints:**
- **POST** `<API_URL>/sessions/list` (fetch sessions)
- **POST** `<API_URL>/sessions/create` (create new session)

**Usage Example:**
```jsx
const {
  sessions,
  isLoading,
  error,
  revalidate,
  createSession,
} = useChatSessionsList({
  apiURL: 'https://api.example.com',
  apiKey: 'your-api-key',
  did: 'your-did',
  matrixAccessToken: 'your-matrix-access-token',
  oracle: 'guru',
});
```

### 2. `useAskOracle`

**Purpose:** Send messages to the AI and receive responses in real-time.

**Returned Values:**
- `sendMessage`: An async function to send a message to the Oracle.
- `isSending`: Boolean indicating if a message is currently being sent.
- `error`: Any error encountered while sending the message.

**Endpoints:**
- **POST** `apiURL/<oracle>/stream` (send messages and receive streamed responses)

**Usage Example:**
```jsx
const { sendMessage, isSending, error } = useAskOracle({
  apiURL: 'https://api.example.com',
  apiKey: 'your-api-key',
  did: 'your-did',
  matrixAccessToken: 'your-matrix-access-token',
  sessionId: 'your-session-id',
  oracle: 'guru',
});
```

Then you can call:
```jsx
await sendMessage('Hello, Guru!');
```

### 3. `useListMessages`

**Purpose:** Retrieve and maintain a list of messages for a specific session, including real-time updates over WebSockets.

**Returned Values:**
- `messages`: An array of messages that may include text or dynamically rendered components.
- `isLoading`: Boolean indicating if messages are being fetched.
- `error`: Any error encountered while fetching messages.
- `connectionId`: The current WebSocket connection ID.

**Endpoints:**
- **GET** `apiURL/<oracle>/messages` (fetch messages)
- **WebSocket**: `socketURL` (connect for live updates)

**Usage Example:**
```jsx
const { messages, isLoading, error, connectionId } = useListMessages({
  apiURL: 'https://api.example.com',
  apiKey: 'your-api-key',
  did: 'your-did',
  matrixAccessToken: 'your-matrix-access-token',
  sessionId: 'your-session-id',
  oracle: 'guru',
  uiComponents: {
    // Register your custom UI components here
    'credential_card': CredentialCard,
    'CoinCard': CoinCard,
  },
  socketURL: 'http://localhost:3000', // Socket.io server URL
});
```

---

## Dynamic UI Components

One of the core features of `@ixo/oracles-ui` is the ability to dynamically render custom UI components in response to AI events (tool calls).

- When the AI emits a tool call (e.g., `CoinCard`), the `resolveUIComponent` function returns a React node that can be displayed directly in your UI.
- If the tool call is “running,” the component can indicate a loading state if it supports it. Otherwise, a placeholder UI will be shown until it’s complete.

**How to Integrate Components:**

1. **Create a Custom UI Component** that matches the tool’s name. For instance, a `CoinCard` component that displays cryptocurrency data.

2. **Export a Loading-Aware Component (Optional)**:  
   If your component can handle loading states, set `Component.prototype.canHandleLoadingState = true;`. This tells the resolver to render the component with an `isLoading` prop when the AI tool is still running.

3. **Register the Component** in `uiComponents` prop passed to `useListMessages`:
   ```jsx
   const components = {
     'CoinCard': CoinCard,
   };
   
   const { messages } = useListMessages({
     ...config,
     uiComponents: components,
   });
   ```

**Note on Rendering:**  
You don’t need to manually call `resolveUIComponent` yourself. The `useListMessages` hook handles it internally. When a tool call message appears, it will automatically resolve the appropriate component and return a React node in `messages`.

### Example of Rendering Messages and Components

Below is a simplified web-based chat interface example. Notice that we directly render `message.content`. If `message.content` is a React node (from a resolved UI component), it will be displayed as such.

```jsx
function ChatInterface({ messages, ...props }) {
  return (
    <div className="chat-container">
      <div className="messages">
        {messages.map((message) => (
          <div key={message.id} className={message.type === 'human' ? 'human' : 'ai'}>
            {/* If the content is a string, show text. If it's a component, it's already a React node. */}
            {typeof message.content === 'string' ? (
              <Markdown>{message.content}</Markdown>
            ) : (
              message.content
            )}
          </div>
        ))}
      </div>
      {/* Input and other UI elements */}
    </div>
  );
}
```

---

## Building Components for the Library

To leverage the library’s dynamic rendering:

1. **Define Props and Loading States:**
   Your component should accept props corresponding to the tool’s `args`, and optionally an `isLoading` prop if it can show a loading skeleton or spinner.

2. **Handle Loading Gracefully:**
   If `isLoading` is true, return a loading state. If false, render the full component UI.

3. **Export Your Component and Register it:**
   ```jsx
   CoinCard.prototype.canHandleLoadingState = true;
   export function CoinCard(props) {
     if (props.isLoading) {
       return <CoinCardSkeleton />;
     }
     return <div>Your card content here...</div>;
   }
   ```
   
   Then pass it in `uiComponents`:
   ```jsx
   useListMessages({
     ...config,
     uiComponents: {
       'CoinCard': CoinCard,
     },
   });
   ```

---

## Platform Differences (Web vs. Mobile)

**Code and Hooks Are the Same:**  
The hooks (`useAskOracle`, `useChatSessionsList`, `useListMessages`) and core logic are platform-agnostic. They work the same in React DOM and React Native/Expo environments.

**UI Components and Rendering:**  
- **Web (React DOM)**: Use standard web components like `<div>`, `<button>`, and `<input>`.
- **Mobile (React Native / Expo)**: Use `<View>`, `<Text>`, `<TouchableOpacity>`, `<TextInput>`, and so forth.

**Example for Mobile:**
```jsx
// Similar logic as web, just different UI components
const { messages } = useListMessages({...config, uiComponents: { 'CoinCard': CoinCard }});

// When rendering messages:
messages.map((message) => (
  <View key={message.id}>
    {typeof message.content === 'string' ? (
      <Text>{message.content}</Text>
    ) : (
      // message.content is a React node (e.g., <CoinCard ... />)
      message.content
    )}
  </View>
));
```