@rekhu/assistant-widget
| Install | |
|---|---|
npm install @rekhu/assistant-widget |
|
| Latest Version: | 0.2.1 |
| License: | Custom |
| Last Updated: | Oct 21, 2026 |
| Links: | npm |
@rekhu/assistant-widget
The chat widget of Rekhu Assistant: a custom AI assistant inside your app, for your own customers. It answers from your help, opens the right page, and does things for the signed-in user through the actions your back end allows.
A framework-free web component (<rekhu-assistant>) in its own shadow DOM: it works in Vue, React, Nuxt, Next, Svelte or plain HTML, and your CSS and its CSS never meet. No dependencies, about 7 kB gzipped, TypeScript types included, safe to import during server rendering.
Contents
- How it works
- Keys: secret and publishable
- Install
- Use it: Next.js · Nuxt · Vue · React · Plain HTML
- Signing the user in
- Options · Methods and events
- Security
How it works
Like Stripe: your back end holds the secret key and exposes a chat route; your front end talks only to that route.
Browser Your back end Rekhu
┌──────────────────────┐ your ┌───────────────────────────────┐ secret ┌──────────┐
│ @rekhu/assistant- │ ──route───▶ │ a Rekhu server package │ ─key─▶ │ model, │
│ widget (no key) │ ◀─stream─── │ e.g. @rekhu/assistant-node │ │ metering │
└──────────────────────┘ │ · knows the signed-in user │ └──────────┘
│ · runs your actions as them │
└───────────────────────────────┘
- Back end: install a Rekhu server package, for Node.js
@rekhu/assistant-node. It adds the chat route, knows who is signed in through your own sign-in, offers only the actions and pages that user may use, runs those actions as the user, and calls Rekhu with your secret key. - Front end: install this widget and point it at that route. It streams the answers, shows buttons for pages and confirm cards for actions, and keeps nothing secret.
The two speak protocol 1, so you can also build a chat screen of your own on the same route.
Keys: secret and publishable
| Key | Looks like | Where it belongs | What it can do |
|---|---|---|---|
| Secret key | agk_… |
only on your server, in an environment variable | everything: actions as the signed-in user, pages, help, usage per customer |
| Publishable key (coming) | pk_… |
in the browser, in plain sight | only a public chat hosted by Rekhu, for a website without a back end of its own: answers from the help and FAQ in your Rekhu account, no actions, only on the domains you allow, capped per visitor and per day |
[!WARNING] Never put a secret key (
agk_…) in front-end code: not in a component, not in aNEXT_PUBLIC_,NUXT_PUBLIC_orVITE_variable, not in a mobile app. Anyone who has it can use your Rekhu credit. If it leaks, block it in your Rekhu account (the project's Connection tab) and add a new one.
You create keys in your Rekhu account: make a project for your app (Projects → New project), then add a key per environment on its Connection tab (local, staging, production). The project's assistant has one monthly limit and shows usage per customer.
Install
npm install @rekhu/assistant-widget
pnpm add and yarn add work the same. Or from a CDN, without a build step:
<script src="https://unpkg.com/@rekhu/assistant-widget@0.1/dist/rekhu-assistant.js"></script>
Use it
Call mount() once, in the browser, for a signed-in user. endpoint is the chat route your back end exposes. The examples use /api/rekhu/chat; the server side of each is in the @rekhu/assistant-node README.
Next.js
A client component in the layout of the signed-in part of your app:
'use client';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { mount } from '@rekhu/assistant-widget';
export function Assistant() {
const router = useRouter();
useEffect(() => {
const el = mount({
endpoint: '/api/rekhu/chat',
navigate: (path) => router.push(path), // pages open without a reload
onAction: () => router.refresh(), // an action changed data: refresh the page
});
return () => el?.destroy();
}, [router]);
return null;
}
Nuxt
plugins/rekhu-assistant.client.ts (the .client suffix keeps it in the browser):
import { mount } from '@rekhu/assistant-widget';
export default defineNuxtPlugin(() => {
const { loggedIn } = useUserSession(); // or however your app knows who is signed in
const router = useRouter();
if (!loggedIn.value) return;
mount({
endpoint: '/api/rekhu/chat',
navigate: (path) => router.push(path),
onAction: () => refreshNuxtData(),
});
});
Vue
// main.js
import { createApp } from 'vue';
import { mount } from '@rekhu/assistant-widget';
import App from './App.vue';
import router from './router';
createApp(App).use(router).mount('#app');
if (window.signedIn) { // however your app knows the user is signed in
mount({ endpoint: '/api/rekhu/chat', navigate: (path) => router.push(path) });
}
React
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { mount } from '@rekhu/assistant-widget';
export function Assistant() {
const navigate = useNavigate();
useEffect(() => {
const el = mount({ endpoint: '/api/rekhu/chat', navigate: (path) => navigate(path) });
return () => el?.destroy();
}, [navigate]);
return null;
}
Plain HTML
<script src="https://unpkg.com/@rekhu/assistant-widget@0.1/dist/rekhu-assistant.js"></script>
<script>
RekhuAssistant.mount({ endpoint: '/api/rekhu/chat' });
</script>
or as an element after the script: <rekhu-assistant data-settings='{"endpoint":"/api/rekhu/chat"}'></rekhu-assistant>.
A public chat for visitors
For people who are not signed in, mount it with visitor: true and the public route your back end exposes (it is off there until you turn it on):
mount({ endpoint: '/api/rekhu/visitor/chat', visitor: true, greeting: 'Hi! Questions about our products?' });
Signing the user in
The widget sends the user's own sign-in with every message, the way the rest of your front end does. Your back end decides who that is.
| Your app signs in with | Pass |
|---|---|
| a session cookie, on the same domain | nothing more (credentials: 'same-origin' is the default) |
| a session cookie, from another domain | credentials: 'include', and allow your front end's origin on the back end |
| a token (JWT, or your own) | headers: () => ({ Authorization: 'Bearer …' }) and credentials: 'omit' |
| a CSRF token your back end checks | csrf: '…' (sent as X-CSRF-TOKEN) |
The token is the user's sign-in token for your back end, never your Rekhu key.
Options
| Option | Default | |
|---|---|---|
endpoint |
— | The chat route of your back end. Required. |
headers |
— | Extra headers on every call, or a (async) function returning them. |
credentials |
'same-origin' |
'include' for cookie sign-in from another domain, 'omit' for tokens only. |
csrf |
— | A CSRF token, sent as X-CSRF-TOKEN. |
navigate(path, url) |
— | Opens a page with your router instead of reloading. Return false to let the browser follow the link. |
navigateOrigins |
this page's origin | Other origins whose links go through navigate, for example your API's. |
onAction(card) |
— | Called after an action ran: your data changed, so refresh what you show. |
visitor |
false |
The public chat: an anonymous id per browser is sent as X-Rekhu-Visitor. |
locale |
the page's lang, else the browser's |
The language of the answers and of the widget's own words (English, Arabic and Dutch built in); null sends none. |
quick |
from the server | Quick questions for a fresh chat, set here instead of in the back end. |
texts |
— | Your own words for any of the widget's: { send: 'Go', placeholder: 'Ask us…' }. Keys: new, newTitle, close, message, placeholder, send, open, confirm, cancel, confirmed, cancelled, signedOut, busy, unavailable, failed. |
dir |
the page's | 'rtl' or 'ltr'. By default the widget follows the page: on a right-to-left page (Arabic, Hebrew) it sits on the left and reads from right to left. |
storage |
'session' |
Where the conversation id is kept: 'session' (per tab), 'local', 'none'. |
name, greeting, color, placeholder |
— | The panel's title, its first message (markdown), the accent colour and the input's placeholder. Without greeting, the server's greeting is shown, in the user's language. |
A fresh chat shows the greeting and your quick questions from the back end (quick in config/rekhu-assistant.php, or the quick option of @rekhu/assistant-node): only those the user may act on, in their language. A click sends one. quick here sets them in the front end instead.
Make it look like your app
Everything the widget draws can be restyled from your own CSS, without touching the package.
CSS variables on the element:
rekhu-assistant {
--c: #0f766e; /* the accent: launcher, your messages, buttons */
--rekhu-font: 15px/1.6 "Inter", sans-serif;
--rekhu-text: #0f172a;
--rekhu-bg: #ffffff; /* the panel and the input bar */
--rekhu-log-bg: #f8fafc; /* behind the messages */
--rekhu-surface: #ffffff; /* the assistant's messages, cards, the input */
--rekhu-border: #e2e8f0;
--rekhu-radius: 20px;
--rekhu-width: 440px;
--rekhu-height: 680px;
}
Parts, for anything else: launcher, panel, header, title, restart, close, log, message (with user or bot), step, card, error, quick, quick-button, form, input, send.
rekhu-assistant::part(panel) { inset-inline-end: 0; bottom: 0; height: 100vh; border-radius: 0; } /* a full-height side panel */
rekhu-assistant::part(message user) { background: #0f766e; }
rekhu-assistant::part(quick-button) { border-radius: 8px; }
Design your own chat
For a chat screen that is entirely yours (a side panel in your design system, a page, a mobile web view), use the connection without the widget: createClient speaks the same protocol with the same sign-in.
import { createClient, markdown } from '@rekhu/assistant-widget';
const chat = createClient({
endpoint: 'https://api.example.com/api/rekhu/assistant/chat',
headers: () => ({ Authorization: `Bearer ${token}` }),
credentials: 'omit',
});
const { greeting, quick, messages } = await chat.history(); // a fresh chat: greeting and quick questions
await chat.send('Where is my order?', (event) => {
// start, text (append event.text), step, card (page, confirm, done, failed), error, done: see PROTOCOL.md
});
await chat.confirm(card.id, onEvent); // the user clicked Confirm on a confirm card
await chat.forget(); // start over
markdown(text) turns an answer into safe HTML. What your chat shows when it opens (a greeting with the user's name, cards for the jobs your users do most, your own illustration) is yours to build; the server only offers what the user may do.
Methods and events
mount(options) returns the element (or null during server rendering):
| Method | |
|---|---|
open(), close() |
open or close the panel |
configure(options) |
change options later, for example a new token |
destroy() |
remove the widget |
DOM events, which bubble to document:
| Event | detail |
When |
|---|---|---|
rekhu:action |
the result card | an action ran; your data changed |
rekhu:error |
{ message } |
a turn failed |
rekhu:unauthorized |
{ status } |
the user's sign-in ended (401 or 419) |
markdown(text) is exported too: the widget's safe markdown, for a chat screen of your own.
Security
- No key in the browser. The widget holds no Rekhu key; your secret key stays on your server.
- Your back end decides. Actions run on your server, as the signed-in user, with your own checks. The widget can only ask.
- Clicks, not words. An action that needs confirming runs only after a click on its card, never on a typed "yes".
- Nothing becomes markup. Everything the model writes is escaped; links only go to
http(s)addresses or your own paths. - Cross-site requests. Every message carries the
X-Rekhu-Protocolheader, which a form on another site cannot send;@rekhu/assistant-noderefuses requests without it.
Versions
Follows semantic versioning. The widget speaks protocol 1; any Rekhu server package that speaks protocol 1 works with it.
Licence
Licensed to holders of a Rekhu account under the Rekhu Terms of Service. See LICENSE.txt.
Related Packages
Rekhu Assistant for Node.js: a custom AI assistant inside your app, for your own...
chartsconnect AI Chat Widget - Universal JavaScript library supporting React, Ne...
chartsconnect AI Chat Widget - Universal JavaScript library supporting React, Ne...
Version History
| Version | Released | License |
|---|---|---|
| 0.2.1 | Custom | |
| 0.2.0 | Custom | |
| 0.1.3 | Custom | |
| 0.1.2 | Custom | |
| 0.1.1 | Custom | |
| 0.1.0 | Custom |
Pre-releases (1)
| Version | Released | License |
|---|---|---|
| 0.0.0-stage |