npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@keluhq/react

v0.1.1

Published

React components and hooks for Kelu — RAG chat with citations over crawled documentation.

Readme

@keluhq/react

React components and hooks for Kelu — an AI knowledge assistant that answers questions with RAG-based chat and citations over your crawled documentation.

Full reference: https://docs.kelu.dev/sdks/react

Install

npm install @keluhq/react

@keluhq/sdk comes with it as a dependency.

react and react-dom (>=18) are peer dependencies and must be installed in your project.

You need a knowledge base ID and a public client key (kl_pk_…). Open the knowledge base in the Kelu dashboard and go to Integrations: Client Keys creates the key, and Developer SDKs shows starter code with your knowledge base ID and a public key filled in. The ID is also the part of the dashboard address after /knowledge-bases/.

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 README. To mint tokens yourself, pass getCaptchaToken.

Quickstart

Inline chat panel

Drop the KeluChat component anywhere in your app for a fully-styled, zero-configuration chat panel:

import { KeluChat } from "@keluhq/react";

export default function DocsPage() {
  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. A function prop such as getCaptchaToken can only be passed from a client component.

Headless hook

Use useKeluChat to build your own UI while the hook handles all streaming, state management, and error handling. Each message's content is Markdown with [n] citation markers; render it with renderMarkdown from @keluhq/sdk (install it next to this package with npm install @keluhq/sdk). Its output escapes all text, so it is safe to insert:

"use client";
import { useState } from "react";
import { useKeluChat } from "@keluhq/react";
import { renderMarkdown } from "@keluhq/sdk";

function MyChat() {
  const { messages, ask, isStreaming, error, reset } = useKeluChat({
    knowledgeBaseId: "YOUR_KNOWLEDGE_BASE_ID",
    clientKey: "kl_pk_...",
  });
  const [input, setInput] = useState("");

  return (
    <div>
      <button onClick={reset}>Clear</button>
      <ul>
        {messages.map((m) => (
          <li
            key={m.id}
            style={{ textAlign: m.role === "user" ? "right" : "left" }}
            dangerouslySetInnerHTML={{ __html: renderMarkdown(m.content) }}
          />
        ))}
      </ul>
      {isStreaming && <p>Thinking…</p>}
      {error && <p style={{ color: "red" }}>{error.message}</p>}
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyDown={(e) => {
          if (e.key === "Enter") {
            void ask(input);
            setInput("");
          }
        }}
      />
    </div>
  );
}

Submitting feedback

Both the component and the hook support per-message thumbs up/down:

const { sendFeedback, clearFeedback } = useKeluChat({ knowledgeBaseId, clientKey });

// After the user clicks thumbs up on a message:
await sendFeedback(messageId, "up");
await sendFeedback(messageId, "down", "Wrong"); // replaces the earlier vote, with comment
await clearFeedback(messageId);                 // withdraws it

messageId is the server's id on an assistant ChatMessage. It is "" when the workspace keeps no data (zero retention); KeluChat then shows no thumbs.

KeluChat props

| Prop | Type | Default | Description | |-------------------|-----------------------|-----------------------|-----------------------------------------------------| | knowledgeBaseId | string | required | Your Kelu knowledge base ID. | | clientKey | string | required | Public client key (kl_pk_…). | | baseUrl | string | https://app.kelu.dev | Override the API base URL. | | placeholder | string | "Ask a question…" | Input placeholder text. | | welcomeMessage | string | — | Greeting shown before the first message. | | theme | KeluTheme | — | Visual customisation (see table below). | | className | string | — | Extra class name(s) on the outer wrapper. | | getCaptchaToken | () => Promise<string> | automatic | Your own reCAPTCHA v3 token source, replacing the automatic one (see CAPTCHA). | | groupIds | string[] | — | Restrict answers and search to these source groups. | | searchEnabled | boolean | false | Show the Ask / Search toggle. | | defaultMode | "ask" \| "search" | "ask" | Mode the panel opens in when searchEnabled. | | searchPlaceholder | string | "Search sources…" | Placeholder of the search field. | | searchLimit | number | 8 | How many search results to request. | | labels | Partial<ChatLabels> | — | Override the search-mode strings; unset keys stay English. |

KeluTheme

| Token | Type | Default | Description | |-----------------|----------|-------------|---------------------------------------------------------| | primaryColor | string | #343CED | Brand colour for buttons, accents, and citation badges. | | borderRadius | string | 12px | Border radius applied to the panel and bubbles. |

ChatLabels

Keys you can pass in labels, for example labels={{ modeAsk: "Ask", searchEmpty: "Nothing found." }}:

| Key | Default | |---|---| | modeAsk | Ask AI | | modeSearch | Search | | searchHint | Type a query and press Enter to search. | | searchEmpty | No matching pages. | | searchFailed | Search is unavailable right now. | | searching | Searching… | | fromTheDocs | From the docs (heading over the results) | | askAiRow | Ask AI: “{query}” ({query} is replaced with the text typed) | | askAiRowSub | Press Enter for a full answer |

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

| Option | Type | Description | |-------------------|-----------------------|-------------------------------------------------| | knowledgeBaseId | string | Your Kelu knowledge base ID. | | clientKey | string | Public client key (kl_pk_…). | | baseUrl | string | Override the API base URL. | | getCaptchaToken | () => Promise<string> | Your own reCAPTCHA v3 token source for every question and rating, replacing the automatic one. See CAPTCHA. | | groupIds | string[] | Restrict retrieval to these source groups. |

useKeluChat return value

| Property | Type | Description | |-----------------|-----------------------------------------------------------|---------------------------------------------------------------| | messages | ChatMessage[] | Ordered list of user and assistant messages. | | ask | (query: string) => Promise<void> | Send a query; streams tokens into the last assistant message. | | isStreaming | boolean | true while a response is being streamed. | | error | KeluError \| null | Last error, if any. | | threadId | string \| undefined | Active conversation thread ID. | | reset | () => void | Clear messages, abort any in-flight stream, reset thread. | | sendFeedback | (id: string, rating: "up" \| "down", comment?: string) => Promise<void> | Submit message feedback. | | clearFeedback | (id: string) => Promise<void> | Withdraw the rating on a message. |

ChatMessage is { id, role: "user" | "assistant", content, citations?, messageId? }. content is Markdown. Label each citation with its index, not its position in the list. error is a KeluError; its codes are listed in the @keluhq/sdk README.

Changing knowledgeBaseId, clientKey or baseUrl starts a new conversation: any answer in progress stops, and messages, threadId and error are cleared.

VERSION (import { VERSION } from "@keluhq/react") is the installed version, for example "0.1.0".

License

MIT