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>
);
}With Search
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"]);
