Skip to content

Chat History

ChatHistory provides a high-level API for managing conversation history with branching support. Edit messages to create new branches, regenerate responses, and navigate between alternatives — like Claude's conversation UI.

Basic Usage

SvelteKit

svelte
<script lang="ts">
  import { ChatHistory } from '@aibind/sveltekit/history';

  type Msg = { role: 'user' | 'assistant'; content: string };
  const chat = new ChatHistory<Msg>();
</script>

<button onclick={() => chat.append({ role: 'user', content: 'Hello!' })}>
  Add message
</button>

{#each chat.messages as msg, i}
  <div>
    <strong>{msg.role}:</strong> {msg.content}

    {#if chat.hasAlternatives(chat.nodeIds[i])}
      <button onclick={() => chat.prevAlternative(chat.nodeIds[i])}></button>
      <span>
        {chat.alternativeIndex(chat.nodeIds[i]) + 1}
        / {chat.alternativeCount(chat.nodeIds[i])}
      </span>
      <button onclick={() => chat.nextAlternative(chat.nodeIds[i])}></button>
    {/if}
  </div>
{/each}

Next.js / React

tsx
"use client";

import { ChatHistory } from "@aibind/nextjs/history";
import { useState } from "react";

type Msg = { role: "user" | "assistant"; content: string };
const chat = new ChatHistory<Msg>();

function ChatView() {
  const { messages, nodeIds } = chat.useSnapshot();

  return (
    <div>
      <button onClick={() => chat.append({ role: "user", content: "Hello!" })}>
        Add message
      </button>
      {messages.map((msg, i) => (
        <div key={nodeIds[i]}>
          <strong>{msg.role}:</strong> {msg.content}
          {chat.hasAlternatives(nodeIds[i]) && (
            <span>
              <button onClick={() => chat.prevAlternative(nodeIds[i])}></button>
              {chat.alternativeIndex(nodeIds[i]) + 1}
              /{chat.alternativeCount(nodeIds[i])}
              <button onClick={() => chat.nextAlternative(nodeIds[i])}></button>
            </span>
          )}
        </div>
      ))}
    </div>
  );
}

Nuxt / Vue

vue
<script setup lang="ts">
import { ChatHistory } from "@aibind/nuxt/history";

type Msg = { role: "user" | "assistant"; content: string };
const chat = new ChatHistory<Msg>();
</script>

<template>
  <button @click="chat.append({ role: 'user', content: 'Hello!' })">
    Add message
  </button>
  <div v-for="(msg, i) in chat.messages.value" :key="chat.nodeIds.value[i]">
    <strong>{{ msg.role }}:</strong> {{ msg.content }}
    <span v-if="chat.hasAlternatives(chat.nodeIds.value[i])">
      <button @click="chat.prevAlternative(chat.nodeIds.value[i])"></button>
      {{ chat.alternativeIndex(chat.nodeIds.value[i]) + 1 }}
      /{{ chat.alternativeCount(chat.nodeIds.value[i]) }}
      <button @click="chat.nextAlternative(chat.nodeIds.value[i])"></button>
    </span>
  </div>
</template>

SolidStart

tsx
import { ChatHistory } from "@aibind/solidstart/history";
import { For, Show } from "solid-js";

type Msg = { role: "user" | "assistant"; content: string };
const chat = new ChatHistory<Msg>();

function ChatView() {
  return (
    <div>
      <button onClick={() => chat.append({ role: "user", content: "Hello!" })}>
        Add message
      </button>
      <For each={chat.messages()}>
        {(msg, i) => {
          const nodeId = () => chat.nodeIds()[i()];
          return (
            <div>
              <strong>{msg.role}:</strong> {msg.content}
              <Show when={chat.hasAlternatives(nodeId())}>
                <button onClick={() => chat.prevAlternative(nodeId())}></button>
                {chat.alternativeIndex(nodeId()) + 1}
                /{chat.alternativeCount(nodeId())}
                <button onClick={() => chat.nextAlternative(nodeId())}></button>
              </Show>
            </div>
          );
        }}
      </For>
    </div>
  );
}

TanStack Start

tsx
import { ChatHistory } from "@aibind/tanstack-start/history";

type Msg = { role: "user" | "assistant"; content: string };
const chat = new ChatHistory<Msg>();

function ChatView() {
  const { messages, nodeIds } = chat.useSnapshot();

  return (
    <div>
      <button onClick={() => chat.append({ role: "user", content: "Hello!" })}>
        Add message
      </button>
      {messages.map((msg, i) => (
        <div key={nodeIds[i]}>
          <strong>{msg.role}:</strong> {msg.content}
          {chat.hasAlternatives(nodeIds[i]) && (
            <span>
              <button onClick={() => chat.prevAlternative(nodeIds[i])}></button>
              {chat.alternativeIndex(nodeIds[i]) + 1}
              /{chat.alternativeCount(nodeIds[i])}
              <button onClick={() => chat.nextAlternative(nodeIds[i])}></button>
            </span>
          )}
        </div>
      ))}
    </div>
  );
}

API

Properties (Reactive)

PropertyTypeDescription
messagesM[]Linear message path (root → active leaf)
nodeIdsstring[]Node IDs for each message
isEmptybooleanWhether the history has messages
sizenumberTotal messages across all branches

Mutation Methods

MethodDescription
append(message)Add message to current path
edit(messageId, newMessage)Create a branch with edited message
regenerate(messageId, newMsg)Same as edit — creates sibling branch
MethodDescription
hasAlternatives(nodeId)Whether message has sibling branches
alternativeCount(nodeId)Number of alternatives
alternativeIndex(nodeId)0-based index among siblings
nextAlternative(nodeId)Switch to next sibling branch
prevAlternative(nodeId)Switch to previous sibling branch

Persistence

ts
// Save
const json = chat.toJSON();
localStorage.setItem("chat", json);

// Restore
const restored = ChatHistory.fromJSON<Msg>(json);

How Branching Works

User: "Hello"
├── Assistant: "Hi!"           ← original
│   └── User: "How are you?"
└── Assistant: "Hey there!"    ← edit creates sibling
    └── User: "What's up?"

When you call edit() or regenerate(), a new sibling node is created. The active path switches to the new branch. Use prevAlternative() / nextAlternative() to navigate between branches.

Reactivity by Framework

FrameworkAccess patternExample
SvelteDirect propertychat.messages
ReactVia hookconst { messages } = chat.useSnapshot()
Vue.valuechat.messages.value
SolidFunction callchat.messages()

Released under the MIT License.