Hydra AI

V 0.1.0 Coming Soon

These are draft docs for the upcoming 0.1.0 release. Read more about the upcoming release here. Have a question about anything in the docs? Send us a message.

Interactables

Special components that can be modified through natural language conversation.

Interactables are special components that can be attached to a thread and modified through natural language conversation. They enable real-time updates to component state based on user messages in a thread.

When a user says something like "add a row to the table" or "update the chart title", Hydra will automatically identify which interactable they're referring to and update its state accordingly.

Core Concepts

  • Interactable Components: Regular components with additional capabilities for state management through conversation
  • Thread Attachment: Can be attached to specific messages in a thread
  • State Management: Maintains state that can be updated via natural language
  • Multiple Instances: Multiple interactables can exist in a single thread
  • Mode Transitions: Components can transition between message and interactable modes

Registration

Register an interactable component by adding the isInteractable flag. This tells Hydra that this component can be updated through conversation:

const componentRegistry = createComponentRegistry({
  TodoTable: {
    component: TodoTable,
    propsSchema: TodoTableSchema,
    isInteractable: true,
    description:
      "Interactive todo table that can be modified through conversation",
  },
});

Attaching to Thread

Attach an interactable as message context. Each interactable needs a unique ID that will be used to reference it in conversations and state updates:

function TodoThread() {
  const { attachInteractable } = useThreadInteractables(threadId);
 
  const handleAttach = () => {
    attachInteractable({
      type: "TodoTable",
      id: "main-todos", // Unique ID for referencing in conversation
      initialState: {
        rows: [],
      },
    });
  };
}

Managing State

Access and update interactable state. The state updates automatically when users modify it through conversation, but you can also update it programmatically:

function TodoTable() {
  const { state, setState, mode } = useHydraMessage<TodoTableState>();
 
  // Component can adapt based on mode
  const isInteractive = mode.type === 'interactable';
 
  return (
    <div>
      {state.rows.map(row => (
        <div key={row.id}>
          <input
            value={row.text}
            readOnly={!isInteractive}
            onChange={(e) => {
              setState({
                rows: state.rows.map(r =>
                  r.id === row.id
                    ? { ...r, text: e.target.value }
                    : r
                )
              });
            }}
          />
        </div>
      ))}
      {isInteractive && (
        <button onClick={() => setState({
          rows: [...state.rows, { id: Date.now(), text: '' }]
        })}>
          Add Row
        </button>
      )}
    </div>
  );
}

Managing Mode Transitions

Provider-Based Transition

Start a component in interactable mode using the provider:

function ThreadMessage({ messageId }) {
  return (
    <HydraMessageProvider
      messageId={messageId}
      interactableOptions={{
        id: 'todo-1',  // If provided, starts in interactable mode
        preserveMessageState: true  // Keep existing state when transitioning
      }}
    >
      <TodoTable />
    </HydraMessageProvider>
  );
}

Programmatic Transition

Or transition to interactable mode from within the component:

function TodoTable() {
  const { state, setState, mode } = useHydraMessage<TodoTableState>();
 
  const makeInteractive = () => {
    mode.makeInteractable({
      id: 'todo-1',
      preserveState: true
    });
  };
 
  return (
    <div>
      {/* Regular component content */}
      {mode.type === 'message' && (
        <button onClick={makeInteractive}>
          Make Interactive
        </button>
      )}
    </div>
  );
}

State Management

The useHydraMessage hook provides unified state management:

const { state, setState, mode } = useHydraMessage<TodoTableState>();
 
// State updates work the same in both modes
setState({
  rows: [...state.rows, newRow],
});
 
// Check current mode
if (mode.type === "interactable") {
  // Component is interactive
}

Example Flow

  1. Component starts in message mode
  2. Transition to interactable mode
  3. State is preserved during transition (if configured)
  4. Component continues to work with same API
  5. State updates now persist independently
// Initial message state
{
  messages: [{
    id: 'msg-1',
    component: {
      type: 'TodoTable',
      props: { rows: [...] }
    }
  }]
}
 
// After transition to interactable
{
  messages: [...],  // Original messages preserved
  interactables: {
    'todo-1': {
      type: 'TodoTable',
      state: { rows: [...] }  // Independent state
    }
  }
}

Interactive-Only Mode

Sometimes you want to restrict a thread to only handle component interactions, without generating new content. This is useful for focused UI interactions or when building interactive dashboards.

You can create a thread in interactive-only mode:

function CreateInteractiveThread() {
  const createThread = useHydraCreateThread();
  const handleCreate = async () => {
    // Create a thread that only handles component interactions
    const threadId = await createThread("Interactive Thread", undefined, {
      isAutoTitle: true,
      isInteractiveOnly: true, // This thread will only update interactables
    });
  };
}

You can also toggle interactive-only mode for existing threads:

function ThreadControls({ threadId }: { threadId: string }) {
  const { isInteractiveOnly, setInteractiveOnly } = useHydraInteractiveMode(threadId);
  return (
    <div>
      <Switch
        checked={isInteractiveOnly}
        onCheckedChange={(checked) => setInteractiveOnly(checked)}
        label="Interactive Components Only"
      />
    </div>
  );
}

For individual messages, you can restrict them to only update interactables:

function ThreadMessage({ messageId, threadId }: { messageId: string; threadId: string }) {
  const { setMessageInteractiveOnly } = useHydraInteractiveMode(threadId);
  const { component } = useHydraThreadComponent(messageId);
 
  const makeInteractiveOnly = async () => {
    // This message will only update interactables, not generate new content
    await setMessageInteractiveOnly(messageId, true);
  };
 
  return (
    <div>
      {component && (
        <Button onClick={makeInteractiveOnly}>
          Make Interactive Only
        </Button>
      )}
    </div>
  );
}

Custom Presentation (Canvas Example)

Interactables can be used within your own presentation layer. Here's an example of implementing a Canvas UI that manages layout while letting Hydra handle component state:

function Canvas() {
  const { interactables } = useThreadInteractables(threadId);
 
  // Developer's own state management for layout
  const [layout, setLayout] = useState({
    'chart-1': { x: 100, y: 100 },
    'table-1': { x: 300, y: 200 }
  });
 
  return (
    <CanvasLayout>
      {Object.entries(interactables).map(([id, data]) => (
        <Draggable
          key={id}
          position={layout[id]}
          onDrag={(pos) => setLayout({...layout, [id]: pos})}
        >
          {/* Render component with its Hydra-managed state */}
          <data.type {...data.state} />
        </Draggable>
      ))}
    </CanvasLayout>
  );
}

On this page