@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

PropTypeDefaultDescription
knowledgeBaseIdstringrequiredThe 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/
clientKeystringrequiredPublic client key (kl_pk_…), from Integrations → Client Keys
baseUrlstringhttps://app.kelu.devAPI base URL
placeholderstring"Ask a question…"Input placeholder
welcomeMessagestringnoneShown while the chat is empty
theme{ primaryColor?, borderRadius? }#343CED, 12pxAccent colour (buttons, links, citation numbers) and corner radius
classNamestringnoneClass name on the outer wrapper
groupIdsstring[]noneAnswer and search only from these source groups
searchEnabledbooleanfalseShow an Ask AI / Search toggle. Search lists matching pages without an answer
defaultMode"ask" | "search""ask"Which mode opens first when searchEnabled is on
searchPlaceholderstring"Search sources…"Placeholder in search mode
searchLimitnumber8Search results to show
labelsPartial<ChatLabels>EnglishReplace any of the search-mode strings, for example { modeAsk: "Ask", searchEmpty: "Nothing found." }
getCaptchaToken() => Promise<string>automaticYour 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

OptionTypeDefaultDescription
knowledgeBaseIdstringrequiredThe knowledge base to answer from
clientKeystringrequiredPublic client key (kl_pk_…)
baseUrlstringhttps://app.kelu.devAPI base URL
groupIdsstring[]noneAnswer only from these source groups
getCaptchaToken() => Promise<string>automaticYour own reCAPTCHA v3 token source, used for every question and rating instead of the automatic one. See CAPTCHA

Return value

PropertyTypeDescription
messagesChatMessage[]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
isStreamingbooleantrue while an answer is streaming
errorKeluError | nullThe last failed answer or rating. Cleared by the next ask()
threadIdstring | undefinedThe conversation that later questions continue
reset() => voidStop 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

FieldTypeDescription
idstringLocal id, for React keys
role"user" | "assistant"Who wrote it
contentstringMessage text
citationsCitation[]?On assistant messages: the sources the answer cited. Label each with its index, not its position
messageIdstring?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:
npm install @keluhq/sdk
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.