astro-chat
v0.1.0
Published
Streaming AI chat widget + API handler for Astro (OpenAI-compatible chat completions).
Maintainers
Readme
astro-chat
Streaming AI chat widget and API helper for Astro sites. Uses an OpenAI-compatible POST /chat/completions endpoint (default base URL https://api.x.ai/v1).
Install
npm install astro-chatPeer dependencies: astro ^4 || ^5 || ^6, zod ^4.
Icons default to Bootstrap Icons (bi bi-*). No extra icon library is required. Add the stylesheet once (many templates already ship it):
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css"
/>Icons (Bootstrap by default)
- Omit
icons→ the widget uses built-in defaults (bi bi-chat-dots-fill,bi bi-x-lg,bi bi-send-fill,bi bi-building). - Customize with strings →
icons={{ launcher: 'bi bi-chat-heart-fill', ... }}. Any name from the Bootstrap Icons site works. - Autocomplete / curated list → import
CHAT_BOOTSTRAP_ICON_SUGGESTIONS(readonly list of common chat UI classes) orCHAT_BOOTSTRAP_ICON_DEFAULTS(the same defaults the widget uses) fromastro-chat:
import {
CHAT_BOOTSTRAP_ICON_DEFAULTS,
CHAT_BOOTSTRAP_ICON_SUGGESTIONS,
type ChatBootstrapIconSuggestion,
} from 'astro-chat';
// Spread defaults, override one key:
const icons = { ...CHAT_BOOTSTRAP_ICON_DEFAULTS, launcher: 'bi bi-stars' };
// Optional: type a variable as a curated suggestion (IDE autocomplete):
const glyph: ChatBootstrapIconSuggestion = 'bi bi-chat-dots-fill';CHAT_BOOTSTRAP_ICON_SUGGESTIONS is not the full Bootstrap catalog—browse the site for every bi bi-* glyph.
Optional: Lucide or other Astro components
If you npm i @lucide/astro (or use your own .astro icon), you can pass component references on icons or use named slots for full prop control. This package does not depend on Lucide.
| Approach | How |
|----------|-----|
| icons + component | icons={{ close: CloseIcon }} — pass CloseIcon, not <CloseIcon /> (frontmatter isn’t JSX). |
| Named slot | <CloseIcon slot="close-icon" size={20} /> in the template. |
If you pass both a slot and icons for the same control, the slot wins. launcher.iconClass overrides icons.launcher only (string or component).
Custom icons (Astro slots)
Optional named slots replace the icons fallback for that control:
| Slot | Replaces |
|------|----------|
| launcher-icon | Floating trigger icon (when launcher.mode is icon or both) |
| close-icon | Header close button |
| send-icon | Send button |
| avatar-fallback | Header placeholder when assistantAvatarUrl is missing |
With Bootstrap only, you can still use slots to drop custom markup (e.g. inline SVG). If a slot is empty, the widget uses icons, then the built-in Bootstrap defaults.
Screenshots
Add preview images under assets/ in this package (for example assets/chat-widget.png). Reference them from this README for npm/GitHub, e.g. .
Environment
| Variable | Required | Description |
|----------|----------|-------------|
| LLM_API_KEY | Yes | Bearer token for the chat API |
| LLM_MODEL | Yes | Model id |
| LLM_API_BASE_URL | No | Override base URL (no trailing slash); default is xAI |
API route
Create src/pages/api/chat.ts (or .js). Disable prerendering so the route runs on the server:
import type { APIRoute } from 'astro';
import { chatPost, type ChatbotSiteConfig } from 'astro-chat';
import { loadConfig } from '../lib/loadConfig'; // your project
export const prerender = false;
function getEnv(key: string): string | undefined {
const v = import.meta.env[key];
return typeof v === 'string' && v.length > 0 ? v : undefined;
}
export const POST: APIRoute = async ({ request }) => {
return chatPost(request, {
loadConfig: () => loadConfig() as ChatbotSiteConfig,
getEnv,
});
};Your loaded config must match ChatbotSiteConfig (see astro-chat/types): sections for hero, about, services, process, cta, faq, contact, testimonials, trustIndicators, plus optional chatbot with enabled, businessContext, welcomeMessage.
Widget
In a layout or page:
---
import ChatWidget from 'astro-chat/components/ChatWidget.astro';
import { CHAT_BOOTSTRAP_ICON_DEFAULTS } from 'astro-chat';
import { loadConfig } from '../lib/loadConfig';
const config = loadConfig();
const suggestionQueries = [
'What services do you offer?',
'How can I contact you?',
'How does your process work?',
];
---
{config.chatbot?.enabled && (
<ChatWidget
headerLine1="Online"
headerLine2={config.businessName}
welcomeMessage={config.chatbot.welcomeMessage ?? 'Hi! How can I help you today?'}
suggestionQueries={suggestionQueries}
actionLinks={[
{ title: 'Contact us', url: '#contact-section', primary: true },
{ title: 'FAQ', url: '#faq-section' },
{ title: 'Book a call', url: '/book' },
]}
assistantAvatarUrl={config.images?.logo?.dark ?? config.images?.logo?.light}
apiPath="/api/chat"
icons={{
...CHAT_BOOTSTRAP_ICON_DEFAULTS,
launcher: 'bi bi-chat-heart-fill',
}}
launcher={{
mode: 'both',
text: 'Help',
button: { borderRadius: '12px' },
buttonHover: { filter: 'brightness(1.12)' },
}}
styles={{
header: { title: { fontSize: '1.05rem' }, subtitle: { fontSize: '0.7rem' } },
body: { userMessage: { backgroundColor: '#1e3a5f' } },
}}
/>
)}Use camelCase CSS keys in each block (fontSize, backgroundColor, padding, borderRadius, …). Empty strings are ignored when you use helpers like blockToInline yourself; the widget skips empty values when applying styles.
For config-driven suggestion pills (e.g. FAQ questions from your site config), you can still use buildChatbotSuggestionQueries from astro-chat/context instead of a hardcoded array.
Props
| Prop | Description |
|------|-------------|
| headerLine1 | Small header line (e.g. status: “Online”). |
| headerLine2 | Main header title (e.g. business name). |
| welcomeMessage | First assistant message when the panel opens. |
| suggestionQueries | string[] — quick-send chip labels (shown before link chips). |
| actionLinks | Optional { title, url, primary? }[]. Outline chips by default; set primary: true on one link for the accent CTA (e.g. contact). |
| assistantAvatarUrl | Optional image URL for the header avatar. |
| apiPath | POST endpoint for SSE chat. Default /api/chat. |
| class | Extra classes on the root element (merged with astro-chat-root). |
| style | Extra root styles (string or object), merged with internal theme CSS variables from styles / launcher. |
| styles | Nested partials: header.title, header.subtitle (alias subTitle), body.chatBotContainer, body.messagesArea, body.assistantMessage, body.userMessage (alias userMessageContainer), footer.pills, footer.footerInput, footer.footerSendButton, teaser, launcherWrap. Assistant/user bubbles use CSS variables on the root (any camelCase keys you set are forwarded). |
| icons | { launcher?, close?, send?, avatarFallback? } — usually bi bi-* strings (see CHAT_BOOTSTRAP_ICON_* exports). Optional Astro icon components if you add Lucide, etc. launcher.iconClass wins over icons.launcher. |
| (slots) | launcher-icon, close-icon, send-icon, avatar-fallback — overrides icons for that control when slot content is present. |
| launcher | mode: 'icon' | 'text' | 'both'; text; iconClass (overrides icons.launcher); ariaLabel; button / buttonHover (base + hover, mapped to --astro-chat-launcher-* vars); icon / label inline blocks for the icon and label. |
The default skin still reads your site tokens (--color-accent, --color-primary, --font-body, …). Override via styles, launcher, root style, or global CSS on .astro-chat-root.
apiPath defaults to /api/chat if omitted.
Exports
astro-chat—chatPost, context builders, validation schema, typesastro-chat/api—chatPostonlyastro-chat/context—buildChatbotSiteKnowledge,buildChatbotSuggestionQueries,buildChatbotSystemPromptastro-chat/types— config typesastro-chat/validation—chatRequestSchemaastro-chat/chat-widget-styles—resolveChatWidgetAppearance,blockToInline, etc.astro-chat/bootstrap-icons—CHAT_BOOTSTRAP_ICON_DEFAULTS,CHAT_BOOTSTRAP_ICON_SUGGESTIONSastro-chat/components/ChatWidget.astro— UI component
Before publishing (maintainers)
- Name — Confirm
astro-chatis still available on the registry (npm view astro-chat), or switch to a scoped name (@your-org/astro-chat) and set"name"+"publishConfig.access"accordingly. - Repository — Replace
your-org/brick-frontend-templatesinpackage.json→repository.urlwith the real repo; keep"directory": "astro-chat"if this package lives in a monorepo subfolder. - Copyright — Update the year or holder in
LICENSEif needed. - Dry run —
npm pack --dry-runand skim the file list (onlysrc,components,assets,README.md,LICENSEshould ship). - Release —
npm run typecheck(also runs automatically viaprepublishOnly), thennpm publish.
This package ships TypeScript and .astro sources; consumers need Astro (and a bundler that compiles dependencies, which Astro/Vite does by default).
License
MIT — see LICENSE.
