@keluhq/react gives you <KeluChat />, a finished chat panel, and useKeluChat, a hook for building your own. It needs react and react-dom 18 or later, depends on @keluhq/sdk, and re-exports its types and KeluError.
Install
npm install @keluhq/react
@keluhq/sdk comes with it as a dependency. import { VERSION } from "@keluhq/react" gives the installed version, for example "0.1.0".
Example
"use client";
import { KeluChat } from "@keluhq/react";
export default function HelpPage() {
return (
<KeluChat
knowledgeBaseId="YOUR_KNOWLEDGE_BASE_ID"
clientKey="kl_pk_..."
welcomeMessage="Hi! Ask me anything about our documentation."
theme={{ primaryColor: "#5b21b6" }}
/>
);
}
Both exports use React hooks, and the package is marked "use client", so in the Next.js App Router you can render KeluChat from a server component too. A function prop such as getCaptchaToken can only be passed from a client component.
The knowledge base’s Integrations → Developer SDKs tab shows this code with your knowledge base ID and a public key filled in.
CAPTCHA
On app.kelu.dev, a public key must send a reCAPTCHA v3 token with every question, search and rating. KeluChat and useKeluChat get one for you in the browser, so there is nothing to set up. What it loads, and what to allow in a Content Security Policy, is in the @keluhq/sdk CAPTCHA section.
To mint tokens yourself, pass getCaptchaToken. It replaces the automatic one.
KeluChat props
| Prop | Type | Default | Description |
|---|
knowledgeBaseId | string | required | The knowledge base to answer from. Find it, with code that has it filled in, under the knowledge base’s Integrations → Developer SDKs. It is also the part of the dashboard address after /knowledge-bases/ |
clientKey | string | required | Public client key (kl_pk_…), from Integrations → Client Keys |
baseUrl | string | https://app.kelu.dev | API base URL |
placeholder | string | "Ask a question…" | Input placeholder |
welcomeMessage | string | none | Shown while the chat is empty |
theme | { primaryColor?, borderRadius? } | #343CED, 12px | Accent colour (buttons, links, citation numbers) and corner radius |
className | string | none | Class name on the outer wrapper |
groupIds | string[] | none | Answer and search only from these source groups |
searchEnabled | boolean | false | Show an Ask AI / Search toggle. Search lists matching pages without an answer |
defaultMode | "ask" | "search" | "ask" | Which mode opens first when searchEnabled is on |
searchPlaceholder | string | "Search sources…" | Placeholder in search mode |
searchLimit | number | 8 | Search results to show |
labels | Partial<ChatLabels> | English | Replace any of the search-mode strings, for example { modeAsk: "Ask", searchEmpty: "Nothing found." } |
getCaptchaToken | () => Promise<string> | automatic | Your own reCAPTCHA v3 token source, replacing the automatic one. See CAPTCHA |
In search mode, the search runs when the reader presses Enter or the search button, with at least 2 characters typed.
ChatLabels keys: modeAsk, modeSearch, searchHint, searchEmpty, searchFailed, searching, fromTheDocs, askAiRow ({query} is replaced with the text typed) and askAiRowSub.
The header reads AI Assistant and the footer shows Powered by Kelu. Neither can be changed. For your own markup, use the hook.
useKeluChat(options)
import { useKeluChat } from "@keluhq/react";
const { messages, ask, isStreaming, error, threadId, reset, sendFeedback, clearFeedback } =
useKeluChat({ knowledgeBaseId: "YOUR_KNOWLEDGE_BASE_ID", clientKey: "kl_pk_..." });
Options
| Option | Type | Default | Description |
|---|
knowledgeBaseId | string | required | The knowledge base to answer from |
clientKey | string | required | Public client key (kl_pk_…) |
baseUrl | string | https://app.kelu.dev | API base URL |
groupIds | string[] | none | Answer only from these source groups |
getCaptchaToken | () => Promise<string> | automatic | Your own reCAPTCHA v3 token source, used for every question and rating instead of the automatic one. See CAPTCHA |
Return value
| Property | Type | Description |
|---|
messages | ChatMessage[] | User and assistant messages in order. The assistant message fills in as the answer streams |
ask | (query: string) => Promise<void> | Send a question on the current thread. Ignored while an answer is streaming or when the query is blank |
isStreaming | boolean | true while an answer is streaming |
error | KeluError | null | The last failed answer or rating. Cleared by the next ask() |
threadId | string | undefined | The conversation that later questions continue |
reset | () => void | Stop any answer and start a new conversation |
sendFeedback | (messageId, rating: "up" | "down", comment?) => Promise<void> | Rate an answer. A second rating replaces the first |
clearFeedback | (messageId) => Promise<void> | Remove the rating |
If a rating fails, sendFeedback and clearFeedback reject and set error to “Your rating could not be saved.”, so you can undo the button. Unmounting stops any answer in progress.
Changing knowledgeBaseId, clientKey or baseUrl starts a new conversation: any answer in progress stops, and messages, threadId and error are cleared.
ChatMessage
| Field | Type | Description |
|---|
id | string | Local id, for React keys |
role | "user" | "assistant" | Who wrote it |
content | string | Message text |
citations | Citation[]? | On assistant messages: the sources the answer cited. Label each with its index, not its position |
messageId | string? | Set on assistant messages once the answer is complete. Pass it to sendFeedback. Empty when the workspace keeps no chat data |
Showing a message
content is Markdown, with [n] markers that match each citation’s index. To show it the way KeluChat does, install @keluhq/sdk next to @keluhq/react and render with its renderMarkdown:
import { renderMarkdown } from "@keluhq/sdk";
<div dangerouslySetInnerHTML={{ __html: renderMarkdown(message.content) }} />
renderMarkdown escapes all text, so its output is safe to insert. What the HTML looks like, and how to make code blocks’ Copy buttons work, is in Showing the answer.
Citation and the error codes on KeluError are in the @keluhq/sdk reference.