AI Feature Suite New in v5.0.0
Luxuria ships with a complete, bring-your-own-key AI layer built on top of the template's mock-data architecture. There is no bundled backend and no Luxuria-owned AI service — instead, each admin connects their own OpenAI, Anthropic (Claude), Google Gemini, or Custom / local (Ollama, LM Studio, vLLM) API key from a dedicated settings page, and that key powers AI actions across the app.
When no key is configured, every AI surface automatically falls back to Demo Mode and shows realistic canned responses instead of an error. This means the template is fully explorable and demo-ready out of the box, while still being a real, working starting point for wiring in your own provider.
fetch(). There is no
server-side proxy included in the template — this is by design for a
frontend-only admin template, and is documented in detail in the
Security & Key Storage section below.What's included
| Piece | Description |
|---|---|
| AI Settings hub | A single admin page (/ai-settings) to choose a provider,
paste an API key, pick a model, test the connection, and save
— encrypted at rest. |
| AI Assistant | A full chat page (/ai-assistant) and a header
quick-access popover panel, both sharing one conversation, for asking
free-form questions about bookings, revenue, guests, or maintenance.
|
| Flagship AI integrations | Three real, working examples wired into existing pages: Bookings anomaly detection, Maintenance priority triage, and "Explain this chart" on the Revenue Report. |
| Reusable AI building blocks | <app-ai-insight-card> and
<app-ai-explain-button> shared components so you
can add an AI action to any other page in a few lines. |
| Demo Mode | Canned JSON fixtures under assets/data/ai-demo/ so every
AI action works with zero configuration and zero network calls. |
Supported AI Providers
Only one provider is active at a time (chosen with a select on the Settings page), but credentials for all four are stored independently so you can switch providers without re-entering keys.
| Provider | Endpoint called | Default model | Notes |
|---|---|---|---|
| Google Gemini Recommended | generativelanguage.googleapis.com/v1beta/models/{model}:generateContent |
gemini-2.0-flash |
Free tier available from Google AI Studio. Supports an "Auto-Detect My Key's Models" button that queries Google for the models available on that key. |
| OpenAI | api.openai.com/v1/chat/completions |
gpt-4o-mini |
Key from platform.openai.com. |
| Anthropic (Claude) | api.anthropic.com/v1/messages |
claude-3-5-sonnet-latest |
Key from console.anthropic.com. Uses Anthropic's direct-browser-access header since there is no backend proxy. |
| Custom / Local | {baseUrl}/chat/completions (OpenAI-compatible) |
llama3 |
Point it at Ollama, LM Studio, vLLM, or any OpenAI-compatible reverse proxy. API key is optional for keyless local endpoints. |
AI Settings Page
Route: /ai-settings · Menu icon: smart_toy · Access: Admin only (API keys are sensitive/billable, so this page is gated the same way as Security and Hotel Settings).
How to configure a provider
- Open AI & LLM Configuration from the sidebar (under Hotel Settings / Security).
- Click a provider card (Gemini, OpenAI, Anthropic, or Custom) — the card grid shows a "Key Saved" or "Active" badge once configured.
- Paste the API key into the masked input (toggle the eye icon to reveal it) and pick a model from the quick-preset chips, or type a custom model name.
- Optionally click Test Connection — this pings the provider without saving anything, and reports latency and the resolved model on success.
- Click Save AI Configuration. The key is encrypted and the provider becomes active immediately — the status pill in the panel header switches from "Demo Mode" to "Live AI Active".
- Clear Key removes the stored credentials for the current provider and drops the app back into Demo Mode.
Security & Key Storage
Because Luxuria has no backend, an API key has to live somewhere in the browser. The template takes the strongest practical approach available client-side:
| Mechanism | Detail |
|---|---|
| Encryption | Every key is encrypted with AES-GCM 256-bit via the browser's native Web Crypto API before it ever touches storage. |
| Key material | The AES key itself is generated as non-extractable
and stored as a native CryptoKey object inside an
IndexedDB database (luxuria-ai-crypto) — it can
never be exported as raw bytes. |
| What's persisted | Only ciphertext ({cipher, iv}) is written to
localStorage. The plaintext API key exists only
in-memory for the current session. |
| Logout | Logging out clears both the persisted ciphertext (normal storage clear) and the in-memory decrypted key, so a shared/public machine never keeps a live key after sign-out. |
| Fallback | If IndexedDB or Web Crypto is unavailable in a given browser, the template falls back to plaintext storage with a visible console warning + UI banner, so the feature still works everywhere. |
Demo Mode
Every AI action checks whether an active key is configured before doing anything.
If it isn't, no network request is made at all — the
action resolves instantly using a canned fixture from
src/assets/data/ai-demo/ and the result is visually tagged with a
"Demo" badge/pill (never a colored left-border, per the
template's card styling convention).
This makes Demo Mode the default, safe experience: reviewers, clients, or anyone previewing the template see fully working AI features immediately, with zero setup, zero cost, and zero risk of a stray API call.
| Fixture file | Used by |
|---|---|
chat-demo.json |
AI Assistant (full page & header panel) |
booking-insight-demo.json |
Bookings anomaly detection |
maintenance-triage-demo.json |
Maintenance "AI Suggest Priority" |
chart-explain-demo.json |
Revenue Report "Explain with AI" |
AI Assistant Concierge
The AI Assistant lets any signed-in user (Admin or Employee) ask free-form questions and get an answer in a chat interface, in two places that share the exact same conversation:
| Surface | Description |
|---|---|
| Full page | Route /ai-assistant. A dedicated "Luxuria Executive
Assistant" page with suggested-prompt chips, a full message history,
and a live/demo status badge in the header. Available to
Admin and Employee roles. |
| Header quick-panel | A small "AI Concierge" icon in the top header opens a compact popover (same widget pattern as the Ctrl+K Command Palette) for a quick question without leaving the current page, with a link to open the full page. |
Because both surfaces read from one shared, in-memory conversation service, a question asked in the popover is still visible if the user then opens the full Assistant page.
Flagship AI Integrations
Beyond the Assistant, three existing admin pages have a real, working AI action wired directly into their UI. These double as the reference pattern for adding AI to any other page — see the developer guide further down.
| Page | Trigger | What it does |
|---|---|---|
| Bookings All Bookings |
"AI Anomaly Detection" button above the bookings table | Summarizes the currently loaded bookings into a short narrative and bullet list flagging unpaid, overdue, or otherwise unusual reservations. |
| Maintenance Service Requests |
"AI Suggest Priority" in each row's action menu | Looks at one maintenance request (category, description, room) and suggests an urgency/priority with a short justification, shown inline for that row. |
| Reports Revenue Report |
"Explain with AI" icon on the Revenue Trend chart header | Generates a plain-language narrative of the chart's series data — what's trending up/down and why it might matter — rendered as a callout under the chart. |
Adding AI to Another Page
Two shared components make it a small amount of glue code to add a similar AI action to any other page:
<app-ai-insight-card>— a full result card (loading / success with bullets & a Demo/AI badge / inline error with a link back to Settings). Good for a summary block above a table or dashboard.<app-ai-explain-button>— a small icon button you drop next to an existing card/chart header; you own where the result is rendered.
// 1. Inject the provider service in your component
private aiProvider = inject(AiProviderService);
private aiSettings = inject(AiSettingsService);
private cdr = inject(ChangeDetectorRef);
aiLoading = false;
aiResult: AiInsightResponse | null = null;
aiError: AiErrorKind | null = null;
async loadAiInsights(): Promise<void> {
this.aiLoading = true;
this.cdr.markForCheck(); // zoneless -- required after any async state change
const res = await this.aiProvider.insight({
kind: 'summary', // or 'anomaly' | 'sentiment' | 'triage' | 'explain-chart'
prompt: 'Summarize this data for a hotel manager',
data: this.dataSource.data,
});
if (res.ok) {
this.aiResult = res.data; // res.data.demo tells you if it was a canned response
} else {
this.aiError = res.error.kind; // 'no-key' | 'network' | 'auth' | 'rate-limit' | ...
}
this.aiLoading = false;
this.cdr.markForCheck();
}
<app-ai-insight-card
title="My Section Insights"
[loading]="aiLoading"
[result]="aiResult"
[error]="aiError"
(refresh)="loadAiInsights()">
</app-ai-insight-card>
AiResult<T> ({ ok: true, data, demo } or
{ ok: false, error }) and never throws — you
never need a try/catch around the AI call itself, only a demo/error/success
branch in your template. Add a new kind and a matching JSON fixture
under assets/data/ai-demo/ if your use case doesn't fit the existing
kinds.Routes, Menu & Access
| Route | Menu icon | Allowed roles |
|---|---|---|
/ai-settings |
smart_toy |
Role.Admin only |
/ai-assistant |
auto_awesome |
Role.Admin, Role.Employee |
Both entries are defined in src/assets/data/menu.json and their
routes in src/app/modules/app.routes.ts, following the exact same
AuthGuard + data: { role: [...] } pattern used by every
other route in the app — see the Role
Configurations page for how that works.