@hachther/akilio-widget
Akilio Widget
Production React and standalone JavaScript widget for the Akilio backend.
Included modes
floating: compact chat popover anchored above the floating launcher.side-panel: persistent desktop side panel with a mobile drawer fallback.inline: renders inside the supplied container.hybrid: backward-compatible alias for side panel on desktop and drawer on mobile.drawer: full-height overlay drawer opened from the floating launcher.
Build
npm install
npm run typecheck
npm run build
Production files:
dist/index.min.js
dist/index.umd.min.cjs
dist/index.d.ts
dist/style.css
dist/akilio-widget.min.js
React
import { AkilioWidget } from '@hachther/akilio-widget';
import '@hachther/akilio-widget/style.css';
<AkilioWidget
apiBaseUrl='https://api.akilio-ai.com'
projectKey='ask_pk_xxx'
mode='floating'
colorMode='system'
auth={{
getAccessToken: async () => sessionStorage.getItem('akilio_access_token'),
}}
requireConsent
privacyNotice='Questions are processed to provide an AI-generated answer.'
privacyNoticeUrl='https://example.com/privacy'
/>;
Custom launcher and controlled state
Hide the built-in launcher and control the floating popup from any host element, such as a header navigation button:
const [chatOpen, setChatOpen] = useState(false);
<button onClick={() => setChatOpen(true)}>Ask Akilio</button>
<AkilioWidget
apiBaseUrl='https://api.akilio-ai.com'
projectKey='ask_pk_xxx'
mode='floating'
launcher='hidden'
open={chatOpen}
onOpenChange={setChatOpen}
/>
Or supply a custom React launcher:
<AkilioWidget
apiBaseUrl='https://api.akilio-ai.com'
projectKey='ask_pk_xxx'
mode='floating'
launcher={({ open, toggleChat }) => (
<button className='header-assistant' onClick={toggleChat}>
{open ? 'Close assistant' : 'Ask Akilio'}
</button>
)}
/>
Authentication supports:
auth={{ accessToken: 'short-lived-jwt' }}
auth={{ getAccessToken: refreshAndReturnToken }}
auth={{ apiKey: 'widget-key', apiKeyHeader: 'X-Akilio-Key' }}
auth={{ credentials: 'include' }}
The token resolver is evaluated for every request, so a host application can rotate short-lived backend tokens without remounting the widget.
Standalone script
<script
data-akilio-widget
src="/assets/akilio-widget.min.js"
data-api-base-url="https://api.akilio-ai.com"
data-project-key="ask_pk_xxx"
data-mode="floating"
data-color-mode="system"
data-access-token="short-lived-widget-token"
data-require-consent="true"
data-privacy-notice="Questions are processed to provide an AI-generated answer."
data-privacy-notice-url="https://example.com/privacy"
></script>
For an inline widget:
<div id="support-assistant" style="height: 680px"></div>
<script src="/assets/akilio-widget.min.js" data-auto-init="false"></script>
<script>
window.Akilio.mount({
target: '#support-assistant',
apiBaseUrl: 'https://api.akilio-ai.com',
projectKey: 'ask_pk_xxx',
mode: 'inline',
colorMode: 'system',
accessToken: 'short-lived-widget-token',
});
</script>
The standalone API can hide the launcher and control the chat imperatively:
const widget = window.Akilio.mount({
target: '#akilio-root',
apiBaseUrl: 'https://api.akilio-ai.com',
projectKey: 'ask_pk_xxx',
mode: 'floating',
launcher: 'hidden',
});
document.querySelector('#header-chat').addEventListener('click', widget.toggle);
// widget.open(); widget.close(); widget.destroy();
Script-tag configuration also accepts data-launcher="hidden".
Supported authentication attributes are data-access-token, data-widget-token, data-api-key, and data-api-key-header.
Backend contract
GET /api/v1/projects/{project_key}/config/
POST /api/v1/chat/
GET /api/v1/conversations/{conversation_id}/?project_key=...
POST /api/v1/messages/{message_id}/feedback/
Every request uses the configured authentication headers. Chat requests also include consent metadata when available.
Rendering performance
The composer maintains its own local input state. Typing therefore does not update the widget root or re-render the message history. Message bubbles and Markdown answers are memoized, and automatic scrolling depends on message count rather than text input changes.
Local history retains the latest 60 messages. Server history is restored from the persisted conversation ID when the local message cache is empty.
Manual light/dark switch
The widget displays an accessible light/dark toggle in its header by default. The visitor's choice is persisted per project in local storage. When no manual choice exists, colorMode="system" follows the operating-system preference.
<AkilioWidget
apiBaseUrl='https://api.example.com'
projectKey='project-key'
colorMode='system'
allowColorModeToggle
/>
For the standalone build, use data-allow-color-mode-toggle="true". Set the React prop or data attribute to false to hide the control.
Widget authentication flow
By default, the widget now implements the Akilio Public API bootstrap flow automatically:
- Load public configuration from
GET /api/v1/projects/{public_key}/config/. - Create or reuse a stable browser subject.
- Request a short-lived token from
POST /api/v1/projects/{public_key}/widget-token/. - Send
Authorization: Bearer <token>to chat, conversation, and feedback endpoints. - Refresh the token before
expires_inelapses, and retry once after an HTTP 401.
No token configuration is required for a normal public widget:
<AkilioWidget
apiBaseUrl='https://api.akilio-ai.com'
projectKey='your-public-project-key'
/>
Use a known subject when the host application has one:
<AkilioWidget
apiBaseUrl='https://api.akilio-ai.com'
projectKey='your-public-project-key'
externalUserId='customer-123'
/>
Host-managed tokens remain supported and take precedence over automatic issuance:
<AkilioWidget
apiBaseUrl='https://api.akilio-ai.com'
projectKey='your-public-project-key'
auth={{ getAccessToken: refreshAndReturnToken }}
/>
Advanced token controls:
<AkilioWidget
apiBaseUrl='https://api.akilio-ai.com'
projectKey='your-public-project-key'
auth={{
autoIssueWidgetToken: true,
tokenRefreshLeewaySeconds: 30,
onWidgetToken: ({ expiresAt }) =>
console.debug('Akilio token expires at', expiresAt),
}}
/>
Next.js (App Router)
Render the widget from a Client Component because it uses browser functionality after hydration:
'use client';
import { AkilioWidget } from '@hachther/akilio-widget';
import '@hachther/akilio-widget/style.css';
export function AkilioAssistant() {
return (
<AkilioWidget
apiBaseUrl={process.env.NEXT_PUBLIC_AKILIO_API_URL!}
projectKey={process.env.NEXT_PUBLIC_AKILIO_PROJECT_KEY!}
/>
);
}
The package itself is now safe to import in an SSR module. The component must still be rendered beneath a use client boundary. A dynamic import with ssr: false is optional, not required.
Ecommerce responses
When the chat API returns a commerce capability, the widget automatically displays its products below the assistant answer. No additional configuration is required.
<AkilioWidget
apiBaseUrl="https://api.example.com"
projectKey="public-project-key"
onProductClick={(product, variant) => {
console.log('Product selected', product, variant);
}}
/>
Product links open in a new tab. The widget also sends an authenticated commerce event to /api/v1/commerce/events/ using the response capability (search or recommendation).
Assistant mode
Use assistantMode to pass the optional assistant_mode routing hint to the chat endpoint:
<AkilioWidget
apiBaseUrl="https://api.example.com"
projectKey="public-project-key"
assistantMode="ecommerce"
/>
Supported values are auto, general, knowledge, ecommerce, and hybrid. The project configuration remains authoritative; this option only narrows behavior permitted by the backend.
For the standalone build:
<script
src="/akilio-widget.min.js"
data-akilio-widget
data-api-base-url="https://api.example.com"
data-project-key="public-project-key"
data-assistant-mode="ecommerce"
></script>
Follow-up suggestions
When /api/v1/chat/ returns a suggestions array, the widget displays accessible suggestion chips under that assistant response. Selecting a chip sends it as the next user message. Suggestions are retained in local history and restored from server conversation message metadata or structured content when supplied by the API.