Rekhu Assistant
The web widget
Put the chat in any web front end with @rekhu/assistant-widget, sign the user in, style it like your app, or build a chat screen of your own.
What the widget is for
@rekhu/assistant-widget is the chat in the browser. It is a web component, <rekhu-assistant>, in its own shadow DOM, so it works in Vue, React, Nuxt, Next, Svelte or plain HTML, and your CSS and its CSS never meet. It has no dependencies, is about 17 kB gzipped, and is safe to import during server rendering.
The widget talks only to the chat route of your back end, from rekhu/assistant-laravel or @rekhu/assistant-node. It holds no Rekhu key.
Note: a Blade or Livewire app does not need this package. The
<x-rekhu-assistant />tag serves the same widget, without npm. Use the package for Inertia with router navigation, or for a front end of its own.
Add the widget in four steps
- Install the package with npm, pnpm or yarn.
- Find the chat route of your back end:
/rekhu/assistant/chatfor the Laravel package, or the path you mounted the Node.js package on, such as/api/rekhu/chat. - Mount it once, in the browser, for a signed-in user, with
endpointset to that route. - Hand it your router, with
navigate, so pages open without a reload, andonAction, so your data refreshes after an action.
npm install @rekhu/assistant-widget
A client component in Next.js:
'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;
}
A Nuxt plugin, plugins/rekhu-assistant.client.ts:
import { mount } from '@rekhu/assistant-widget';
export default defineNuxtPlugin(() => {
const { loggedIn } = useUserSession();
const router = useRouter();
if (!loggedIn.value) return;
mount({ endpoint: '/api/rekhu/chat', navigate: (path) => router.push(path), onAction: () => refreshNuxtData() });
});
Plain HTML, from a CDN, without a build step:
<script src="https://unpkg.com/@rekhu/assistant-widget@0.8/dist/rekhu-assistant.js"></script>
<script>
RekhuAssistant.mount({ endpoint: '/api/rekhu/chat' });
</script>
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 | headers: () => ({ Authorization: 'Bearer …' }) and credentials: 'omit' |
| a CSRF token your back end checks | csrf: '…', sent as X-CSRF-TOKEN |
mount({
endpoint: 'https://api.example.com/rekhu/assistant/chat',
headers: () => ({ Authorization: `Bearer ${token.value}` }),
credentials: 'omit',
navigateOrigins: ['https://api.example.com'],
navigate: (path, url) => router.push(url.pathname + url.search),
});
The token is the user's sign-in token for your back end, never your Rekhu key.
Warning: never put a secret key (
agk_…) in front-end code: not in a component, not in aNEXT_PUBLIC_,NUXT_PUBLIC_orVITE_variable. Anyone who has it can use your Rekhu credit.
Options
| Option | Default | |
|---|---|---|
endpoint |
the chat route of your back end; required | |
headers, credentials, csrf |
'same-origin' |
how the user signs in, see above |
navigate(path, url) |
opens a page with your router; return false to let the browser follow the link |
|
navigateOrigins |
this page's origin | other origins whose links go through navigate |
onAction(card) |
called after an action ran: refresh what you show | |
visitor |
false |
the public chat; point endpoint at the …/visitor/chat route |
scope |
a chat of its own on the server, with its own actions | |
locale |
the page's lang |
the language of the answers and of the widget's own words (English, Arabic and Dutch built in) |
dir |
the page's | 'rtl' or 'ltr'; on a right-to-left page the widget sits on the left |
name, greeting, color, placeholder |
the title, the first message, the accent colour, the input's placeholder | |
quick |
from the server | quick questions for a fresh chat |
texts |
your own words for any of the widget's, such as { send: 'Go' } |
|
files |
on | false hides the paperclip |
history |
on | false hides the list of earlier conversations |
report |
true |
false hides "Report a problem" under the answers |
storage |
'session' |
where the conversation id is kept: 'session', 'local' or 'none' |
mount() returns the element, with open(), close(), configure(options) (for example a new token) and destroy(). It returns null during server rendering.
The widget sends DOM events that bubble to document: rekhu:action (an action ran), rekhu:error, rekhu:unauthorized (the sign-in ended, 401 or 419) and rekhu:report.
Make it look like your app
Everything the widget draws can be restyled from your own CSS. Set variables on the element, and reach any part with ::part():
rekhu-assistant {
--c: #0f766e; /* the accent: launcher, your messages, buttons */
--rekhu-font: 15px/1.6 "Inter", sans-serif;
--rekhu-bg: #ffffff;
--rekhu-radius: 20px;
--rekhu-width: 440px;
--rekhu-height: 680px;
}
rekhu-assistant::part(panel) { inset-inline-end: 0; bottom: 0; height: 100vh; border-radius: 0; }
rekhu-assistant::part(message user) { background: #0f766e; }
The parts include launcher, panel, header, title, log, message (with user or bot), step, card, quick-button, input, send, chart and file-link.
A chat screen of your own
For a chat that is entirely yours, use the connection without the widget. createClient speaks the same protocol with the same sign-in:
import { createClient, renderMessage } from '@rekhu/assistant-widget';
const chat = createClient({ endpoint: '/api/rekhu/chat' });
const { greeting, quick, messages } = await chat.history();
await chat.send('Where is my order?', (event) => {
// start, text (append event.text), step, card (page, confirm, done, failed, file), error, done
});
await chat.confirm(card.id, onEvent); // the user clicked Confirm on a confirm card
renderMessage(text) turns an answer into safe HTML with everything the widget draws: paragraphs, lists, tables, code, links and charts. Call it on the whole text each time a piece arrives. Everything the model writes is escaped, and links only go to http(s) addresses or your own paths.
Next
- Conversation history: the list of earlier conversations, and the client calls behind it.
- Flutter: the same chat in a mobile app.