Angular Library¶
The chat UI is a standalone Angular component, <ast-assistant-chat>, published on npm as
@aeontronix/aeon-assistant. It works with any backend that implements the REST API, such as the
Spring Boot integration.
Installation¶
Install the library along with the markdown renderer it relies on:
Peer dependencies:
| Package | Version |
|---|---|
@angular/core, @angular/common, @angular/forms |
^21.2.0 or ^22.0.0 |
ngx-markdown |
^21.3.0 or ^22.0.0 |
marked |
^17.0.0 or ^18.0.0 |
The component uses FormsModule for its prompt box. Angular CLI projects include all three Angular packages by default.
No extra setup
- Styles are self-contained. The component ships its own CSS, so you don't need Tailwind or a global stylesheet.
- Markdown is set up for you. The component registers
provideMarkdown()itself, so you don't have to add it to your application providers.
Alpha, beta and snapshot versions¶
Stable releases are published on npmjs.com. Alpha and beta
releases (for example 1.0.0-beta2), and the snapshots built from the default branch (for example
1.0.0-snapshot.2, dist-tag snapshot), are only published to the package registry of the
aeontronix GitLab group. The registry is public, so you don't need a token.
1. Add the registry for the @aeontronix scope. Create an .npmrc file next to your package.json:
5527027 is the id of the aeontronix group. You can also write the same line with
npm config set @aeontronix:registry https://gitlab.com/api/v4/groups/5527027/-/packages/npm/ --location project.
2. Install the version you want:
Scoped registry
While the line is in .npmrc, npm downloads every @aeontronix package from the GitLab registry, and other
packages from npmjs.com as before. Remove the line to install stable releases from npmjs.com again. Keep the exact
pre-release version in package.json, because npm only installs pre-releases when you ask for them explicitly.
Quick start¶
1. Point the library at your backend. Provide ASSISTANT_CONFIG in your application config:
import { ApplicationConfig } from '@angular/core';
import { ASSISTANT_CONFIG, AssistantConfig } from '@aeontronix/aeon-assistant';
export const appConfig: ApplicationConfig = {
providers: [
{
provide: ASSISTANT_CONFIG,
useValue: {
apiUrl: 'http://localhost:8080/assistant/chat',
} satisfies AssistantConfig,
},
],
};
2. Import the standalone component wherever you want to show the chat:
import { Component } from '@angular/core';
import { AssistantChat } from '@aeontronix/aeon-assistant';
@Component({
selector: 'app-root',
imports: [AssistantChat],
templateUrl: './app.html',
})
export class App {}
3. Add it to the template. The chat fills its host element, so give the host a height:
That's all you need. If you don't set an agent, the chat uses the first agent the backend returns when you send the first message.
Sizing the chat
The host element is a flex column (display: flex) that stretches the message list between the header and the
prompt box. Don't override its display. If the host has no fixed height (for example 100vh, or a sized parent
with height: 100%), the chat grows with its content instead of scrolling.
Configuration¶
ASSISTANT_CONFIG is optional and holds the defaults for every <ast-assistant-chat> in the application. It is an
AssistantConfig object:
| Field | Type | Description |
|---|---|---|
apiUrl |
string |
URL of the chat endpoint. All other endpoints are derived from it (see Backend URLs). |
agentId |
string |
Agent selected at startup. |
sessionId |
string |
Declared on the interface, but not read by the component. Use the sessionId input instead. |
title |
string |
Header title shown before the backend sends one. |
headers |
Record<string, string> |
Extra HTTP headers sent with every request, for example Authorization. |
Each setting is resolved in this order:
- The component input, for example
<ast-assistant-chat apiUrl="/other/chat" />. - The matching field of
ASSISTANT_CONFIG. - The built-in default (see Inputs).
headers has no input. It is read from the config once, when the component is created. You can change it later with
setHeader() or setHeaders() (see Programmatic control).
Inputs¶
| Input | Type | Default | Description |
|---|---|---|---|
apiUrl |
string |
config apiUrl, else /assistant/chat |
Chat endpoint URL. Relative URLs are resolved against the current origin. |
agentId |
string |
config agentId |
Agent to talk to. Changing it resets the selection. |
sessionId |
string |
none | Existing session to resume. Without it, the backend starts a new session. |
loadHistory |
boolean |
true |
Load the conversation of sessionId from the backend when it is set. |
title |
string |
config title, else new prompt |
Header title shown until the backend sends a title for the conversation. |
subtitle |
string |
none | Secondary line shown under the title. |
placeholder |
string |
Type your task |
Placeholder text of the prompt box. |
size |
'small', 'medium' or 'large' |
'medium' |
Overall scale of the UI: header, bubbles, controls and fonts. |
autoScroll |
boolean |
true |
Scroll to the latest message when messages change. |
showClearButton |
boolean |
true |
Show the button that clears the conversation. It only appears once there are messages. |
showAgentSelector |
boolean |
false |
Load the agents from the backend and show a dropdown to switch between them, if there are any. |
showStatus |
boolean |
true |
Show the status indicator (idle, loading, streaming, error). |
showTimestamp |
boolean |
true |
Show the time of each message. |
renderMarkdown |
boolean |
true |
Render assistant and system messages as markdown. User input is always plain text. |
showThinking |
boolean |
true |
Show the reasoning the agent reports while it answers. |
showTools |
boolean |
true |
Show the tools the agent runs while it answers. |
promptSuggestions |
array of string or PromptSuggestion |
['What can you help me with?'] |
Chips shown while the chat is empty. Clicking one sends its prompt. Pass [] to hide them. |
A PromptSuggestion is { label: string; prompt?: string }. The chip shows label and sends prompt, or label
if prompt is missing. A plain string is used as both.
<ast-assistant-chat
style="height: 600px"
agentId="support"
title="Support"
subtitle="Ask anything about your account"
size="small"
[showAgentSelector]="true"
[promptSuggestions]="['Reset my password', { label: 'Billing', prompt: 'Show my last invoice' }]"
/>
Outputs¶
| Output | Payload | Emitted when |
|---|---|---|
messageSent |
ChatMessage |
The user sends a message. A message with both an image and text emits twice, once for each. |
messageReceived |
ChatMessage |
The agent's answer is complete. Not emitted if the request fails or is aborted. |
chatCleared |
void |
The conversation is cleared, from the clear button or clearMessages(). |
errorOccurred |
string |
A request fails. The payload is the error message, taken from the response body when there is one. |
Programmatic control¶
Get the component instance with viewChild to call its public methods:
import {Component, viewChild} from '@angular/core';
import {AssistantChat} from '@aeontronix/aeon-assistant';
@Component({
selector: 'app-support',
imports: [AssistantChat],
template: `<ast-assistant-chat #chat style="height: 100%" />`,
})
export class Support {
readonly chat = viewChild.required(AssistantChat);
reset(): void {
this.chat().clearMessages();
}
}
| Method | Description |
|---|---|
setHeader(key, value) |
Adds or replaces one HTTP header sent with every request. |
setHeaders(headers) |
Replaces all custom headers, including the ones from ASSISTANT_CONFIG. |
setAgentId(agentId) |
Selects another agent. The next message starts a new session, because a session belongs to a single agent. |
setAgents(agents) |
Sets the list of agents shown in the selector, instead of loading it from the backend. |
loadAgents() |
Loads the agents from the backend and selects the first one if none is selected yet. |
setTitle(title) |
Changes the header title. The backend may still replace it with the title it generates. |
clearMessages() |
Aborts a running request, clears the conversation, resets the title and emits chatCleared. |
addMessage(message) |
Appends a ChatMessage to the conversation. Nothing is sent to the backend. |
setMessages(messages) |
Replaces the whole conversation. Nothing is sent to the backend. |
loadSessionHistory(sessionId, agentId?) |
Reloads the history of the session bound to the sessionId input. The result is dropped if sessionId doesn't match the input, or a message is being sent. |
The component also exposes its state as read-only signals:
| Signal | Description |
|---|---|
messages() |
The conversation shown. |
status() |
'idle', 'loading', 'streaming' or 'error'. |
isBusy() |
Whether a request is running. |
agents() |
The agents loaded from the backend or set with setAgents(). |
selectedAgentId() |
The agent messages are sent to. |
activeSessionId() |
The session the conversation is on. |
displayTitle() |
The title shown in the header. |
Authentication¶
All requests go through fetch with the custom headers, so you can pass a token either way:
Interceptors and cookies
Requests don't go through Angular's HttpClient, so HTTP interceptors don't apply and cookies follow the default
fetch rules. Pass credentials as headers. If the backend is on another origin, it must allow your headers
through CORS.
Agents, sessions and history¶
Agents¶
Every message goes to one agent:
- If
agentId(input or config) is set, that agent is used. - With
[showAgentSelector]="true", the component loads the agents from the backend, selects the first one if none is set, and shows a dropdown in the header to switch between them. - If no agent is selected when the user sends a message, the component loads the agents and uses the first one. If the backend returns none, the message fails with an error.
Sessions¶
The backend creates a session on the first message and returns its id. The component sends that id back with every later message, so the whole conversation stays on one session. Session ids are only unique per agent: switching to another agent starts a new session.
Resuming a conversation¶
Bind sessionId, together with agentId, to show an existing chat again:
With loadHistory left at true, the component loads the session's history from the backend and continues the
conversation on that session. Set [loadHistory]="false" to keep sending on the session without showing its earlier
messages. Changing sessionId resets the active session, and clearMessages() returns to it.
What is sent to the backend
Agents that keep memory (hasMemory: true in the agent list) only receive the new message. For other agents, the
component sends the whole visible conversation with each request. Tool runs and thinking are never sent back.
Using AssistantService directly¶
AssistantService is provided in root and handles all communication with the backend. Use it to build your own UI,
or to manage sessions outside the chat component. Every method takes the chat URL (apiUrl) and optional headers:
import { inject } from '@angular/core';
import { AssistantService } from '@aeontronix/aeon-assistant';
const service = inject(AssistantService);
const apiUrl = '/assistant/chat';
const headers = { Authorization: 'Bearer my-token' };
const agents = await service.loadAgents(apiUrl, headers);
const history = await service.loadHistory(apiUrl, 'support', sessionId, headers);
await service.renameSession(apiUrl, 'support', sessionId, 'Invoice question', headers);
await service.deleteSession(apiUrl, 'support', sessionId, headers);
| Method | Returns | Description |
|---|---|---|
loadAgents(apiUrl, headers?) |
Promise<AgentInfo[]> |
The public agents, or [] on failure. |
sendMessage(content, options) |
Promise<ChatMessage> or null |
Sends a message and streams the answer through callbacks. Resolves to the answer, or null on error or abort. |
loadHistory(apiUrl, agentId, sessionId, headers?, from?) |
Promise<ChatMessage[]> |
The messages of a session, optionally only those from a given time on. [] on failure. |
answerQuestion(apiUrl, agentId, sessionId, questionId, answers, headers?) |
Promise<boolean> |
Answers a question the agent is waiting on. |
renameSession(apiUrl, agentId, sessionId, title, headers?) |
Promise<boolean> |
Renames a session. Only its owner can do this. |
deleteSession(apiUrl, agentId, sessionId, headers?) |
Promise<boolean> |
Deletes a session and its history. |
clearSessionHistory(apiUrl, agentId, headers?) |
Promise<boolean> |
Deletes every session of an agent. |
The methods don't throw. Failures resolve to false, [] or null, and are logged to the console.
sendMessage requires apiUrl and agentId in its options. Everything else is optional: sessionId, imageUrl,
headers, agents, the previous messages, an abort signal, and callbacks such as onMessagesUpdate,
onSessionId, onTitle, onToolExecution, onThinking, onQuestion, onLoading, onStreaming and onError.
const controller = new AbortController();
const answer = await service.sendMessage('Hello!', {
apiUrl,
agentId: 'support',
sessionId,
headers,
signal: controller.signal,
onSessionId: (id) => (sessionId = id),
onMessagesUpdate: (messages) => render(messages),
onError: (error) => error && console.error(error),
});
Backend URLs¶
apiUrl is the chat URL. The base URL is apiUrl with its trailing /chat removed, and every other endpoint is
built from it. A relative apiUrl is resolved against the page's origin. With the default /assistant/chat, the base
is /assistant:
| Method | Path | Used by |
|---|---|---|
GET |
{base}/agents |
Agent selector, loadAgents() |
POST |
{base}/agents/{agentId}/chat |
Sending a message, sendMessage() |
GET |
{base}/agents/{agentId}/sessions/{sessionId}/events?from= |
Resuming a session, loadHistory() |
POST |
{base}/agents/{agentId}/sessions/{sessionId}/questions/{questionId} |
Answering a question card, answerQuestion() |
PUT |
{base}/agents/{agentId}/sessions/{sessionId}/title |
renameSession() |
DELETE |
{base}/agents/{agentId}/sessions/{sessionId} |
deleteSession() |
DELETE |
{base}/agents/{agentId}/sessions |
clearSessionHistory() |
The component renders each event of the chat stream as follows:
| Event | What the component does |
|---|---|
title |
Stores the session id and, on the first turn, shows the generated title. |
message |
Adds the agent's answer to the conversation. |
text |
Shows text the model wrote between tool calls, in place. |
tool |
Shows a tool run, then updates it when it completes or fails (hidden with showTools). |
thinking |
Shows the model's reasoning in a collapsible block (hidden with showThinking). |
question |
Shows a question card. The user's answers are posted to the questions endpoint. |
A backend that answers with plain text or JSON instead of an event stream also works: the response body is shown as the answer.
See the REST API Specs for the full contract.