Skip to content

useChatHistory Hook ​

Hook for managing conversation history.

Import ​

tsx
import { useChatHistory } from "@cognipeer/chat-ui";

Usage ​

tsx
const {
  conversations,
  isLoading,
  error,
  hasMore,
  load,
  loadMore,
  refresh,
  deleteConversation,
  updateConversation,
  // portal experience
  canRename,
  canPin,
  renameConversation,
  pinConversation,
  deleteConversations,
} = useChatHistory({
  baseUrl: "/api/agents",
  agentId: "assistant",
});

Options ​

typescript
interface UseChatHistoryOptions {
  baseUrl?: string;       // or `transport`
  transport?: ChatTransport;
  agentId?: string;       // filters the list; without it all conversations are listed
  authorization?: string;
  headers?: Record<string, string>;
  getHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
  fetch?: typeof fetch;

  pageSize?: number;      // items per page (default: 20)
  autoLoad?: boolean;     // load on mount (default: true)
}

There is no onError option: read error.

Return Value ​

typescript
interface UseChatHistoryReturn {
  // Data
  conversations: ConversationListItem[];

  // State
  isLoading: boolean;
  error: Error | null;          // a localized message for well-known server codes
  hasMore: boolean;

  // Actions
  load: (options?: { showLoading?: boolean }) => Promise<void>;   // showLoading: false is a silent refresh
  loadMore: () => Promise<void>;
  refresh: () => Promise<void>;                                   // silent: no loading indicator
  deleteConversation: (id: string) => Promise<void>;
  updateConversation: (id: string, data: Partial<ConversationListItem>) => void;   // local only, no request

  // Portal experience: what the transport can do, and the optimistic actions
  canRename: boolean;
  canPin: boolean;
  renameConversation: (id: string, title: string) => Promise<boolean>;
  pinConversation: (id: string, pinned: boolean) => Promise<boolean>;
  deleteConversations: (ids: string[]) => Promise<{ deleted: string[]; failed: string[] }>;
}

renameConversation and pinConversation show the change at once and put the row back if the server refuses (or answers with a conversation that is not in the state that was asked for); error says why, and the promise resolves false (also for an empty or unchanged title). deleteConversations uses the transport's bulk route (at most 100 ids per call) or deletes one by one; the list drops the ids that are gone and error says how many could not be deleted. Pinned conversations are listed first by ChatHistory (isPinned and pinnedFirst are exported if you build your own list).

Examples ​

Basic Usage ​

tsx
function HistoryList() {
  const { conversations, isLoading } = useChatHistory({
    baseUrl: "/api/agents",
    agentId: "assistant",
  });

  if (isLoading) {
    return <div>Loading...</div>;
  }

  return (
    <ul>
      {conversations.map(conv => (
        <li key={conv.id}>{conv.title || "Untitled"}</li>
      ))}
    </ul>
  );
}

With Authentication ​

tsx
const history = useChatHistory({
  baseUrl: "/api/agents",
  agentId: "assistant",
  authorization: `Bearer ${token}`,
});

Infinite Scroll ​

tsx
function InfiniteHistory() {
  const { conversations, hasMore, loadMore, isLoading } = useChatHistory({
    baseUrl: "/api/agents",
    agentId: "assistant",
    pageSize: 20,
  });

  const handleScroll = (e) => {
    const { scrollTop, scrollHeight, clientHeight } = e.target;
    if (scrollHeight - scrollTop === clientHeight && hasMore && !isLoading) {
      loadMore();
    }
  };

  return (
    <div onScroll={handleScroll} className="h-full overflow-y-auto">
      {conversations.map(conv => (
        <div key={conv.id}>{conv.title}</div>
      ))}
      
      {isLoading && <div>Loading more...</div>}
    </div>
  );
}

With Delete ​

tsx
function ManageableHistory() {
  const { conversations, deleteConversation } = useChatHistory({
    baseUrl: "/api/agents",
    agentId: "assistant",
  });

  const handleDelete = async (id: string) => {
    if (confirm("Delete this conversation?")) {
      await deleteConversation(id);
    }
  };

  return (
    <ul>
      {conversations.map(conv => (
        <li key={conv.id}>
          {conv.title}
          <button onClick={() => handleDelete(conv.id)}>[delete]</button>
        </li>
      ))}
    </ul>
  );
}
tsx
function SearchableHistory() {
  const { conversations } = useChatHistory({
    baseUrl: "/api/agents",
    agentId: "assistant",
  });
  
  const [search, setSearch] = useState("");

  const filtered = useMemo(() => {
    if (!search) return conversations;
    return conversations.filter(c =>
      c.title?.toLowerCase().includes(search.toLowerCase())
    );
  }, [conversations, search]);

  return (
    <div>
      <input
        value={search}
        onChange={(e) => setSearch(e.target.value)}
        placeholder="Search..."
      />
      
      <ul>
        {filtered.map(conv => (
          <li key={conv.id}>{conv.title}</li>
        ))}
      </ul>
    </div>
  );
}

Manual Load ​

tsx
function ManualLoadHistory() {
  const { conversations, load, isLoading } = useChatHistory({
    baseUrl: "/api/agents",
    agentId: "assistant",
    autoLoad: false,
  });

  return (
    <div>
      <button onClick={load} disabled={isLoading}>
        {isLoading ? "Loading..." : "Load History"}
      </button>
      
      {conversations.map(conv => (
        <div key={conv.id}>{conv.title}</div>
      ))}
    </div>
  );
}

Refresh History ​

tsx
function RefreshableHistory() {
  const { conversations, refresh, isLoading } = useChatHistory({
    baseUrl: "/api/agents",
    agentId: "assistant",
  });

  return (
    <div>
      <button onClick={refresh} disabled={isLoading}>
        Refresh
      </button>
      
      {conversations.map(conv => (
        <div key={conv.id}>{conv.title}</div>
      ))}
    </div>
  );
}

With useChat Integration ​

tsx
function IntegratedChat() {
  const chat = useChat({
    baseUrl: "/api/agents",
    agentId: "assistant",
  });
  
  const history = useChatHistory({
    baseUrl: "/api/agents",
    agentId: "assistant",
  });

  const handleSelect = (conv: ConversationListItem) => {
    chat.loadConversation(conv.id);
  };

  const handleNew = async () => {
    await chat.createConversation();
    history.refresh();
  };

  return (
    <div className="flex">
      <aside>
        <button onClick={handleNew}>New Chat</button>
        
        {history.conversations.map(conv => (
          <div
            key={conv.id}
            onClick={() => handleSelect(conv)}
            className={conv.id === chat.conversation?.id ? "active" : ""}
          >
            {conv.title}
          </div>
        ))}
      </aside>
      
      <main>
        {/* Chat content */}
      </main>
    </div>
  );
}

List item type ​

typescript
interface ConversationListItem {
  id: string;
  title?: string;
  agentName?: string;
  agentId: string;
  runtime?: ConversationRuntime;     // the status badge
  budget?: ConversationBudget | null;
  spentUsd?: number;
  pinned?: boolean;
  metadata?: Record<string, unknown>;
  createdAt: Date;
  updatedAt: Date;
}

Error Handling ​

tsx
const history = useChatHistory({
  baseUrl: "/api/agents",
  agentId: "assistant",
});

if (history.error) {
  return <p role="alert">{history.error.message}</p>;   // already localized when the server sent a known code
}

Rename, pin and select ​

tsx
const history = useChatHistory({ baseUrl: "/api/agents" });

if (history.canRename) await history.renameConversation("c1", "Quarterly report");
if (history.canPin) await history.pinConversation("c1", true);
const { deleted, failed } = await history.deleteConversations(["c2", "c3"]);

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub