Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions api-goldens/element-ng/chat-messages/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@

```ts

import { AfterContentInit } from '@angular/core';
import { AfterViewInit } from '@angular/core';
import * as _angular_core from '@angular/core';
import { ElementRef } from '@angular/core';
import { FileUploadError } from '@siemens/element-ng/file-uploader';
import * as i1 from '@siemens/element-ng/resize-observer';
import { MenuItem } from '@siemens/element-ng/menu';
import { OnDestroy } from '@angular/core';
import * as _siemens_element_translate_ng_translate_types from '@siemens/element-translate-ng/translate-types';
import { SiModalService } from '@siemens/element-ng/modal';
import { TemplateRef } from '@angular/core';
Expand All @@ -23,7 +25,7 @@ export interface Attachment {
previewTemplate?: TemplateRef<any> | (() => TemplateRef<any>);
}

// @public (undocumented)
// @public
export interface ChatInputAttachment extends Attachment {
file: File;
size: number;
Expand Down Expand Up @@ -77,7 +79,33 @@ export class SiAttachmentListComponent {
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SiAttachmentListComponent, never>;
}

// @public (undocumented)
// @public
export class SiChatContainerComponent implements AfterContentInit, OnDestroy {
constructor();
readonly colorVariant: _angular_core.InputSignal<string>;
focus(): void;
// (undocumented)
ngAfterContentInit(): void;
// (undocumented)
ngOnDestroy(): void;
readonly noAutoScroll: _angular_core.InputSignalWithTransform<boolean, string | boolean>;
// (undocumented)
protected onScroll(): void;
// (undocumented)
static ɵcmp: _angular_core.ɵɵComponentDeclaration<SiChatContainerComponent, "si-chat-container", never, { "colorVariant": { "alias": "colorVariant"; "required": false; "isSignal": true; }; "noAutoScroll": { "alias": "noAutoScroll"; "required": false; "isSignal": true; }; }, {}, never, ["*", "si-inline-notification", "si-chat-input,[siChatContainerInput]"], true, never>;
// (undocumented)
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SiChatContainerComponent, never>;
}

// @public
export class SiChatContainerInputDirective {
// (undocumented)
static ɵdir: _angular_core.ɵɵDirectiveDeclaration<SiChatContainerInputDirective, "[siChatContainerInput]", never, {}, {}, never, never, true, never>;
// (undocumented)
static ɵfac: _angular_core.ɵɵFactoryDeclaration<SiChatContainerInputDirective, never>;
}

// @public
export class SiChatInputComponent implements AfterViewInit {
readonly accept: _angular_core.InputSignal<string | undefined>;
readonly actionParam: _angular_core.InputSignal<any>;
Expand Down
18 changes: 18 additions & 0 deletions docs/patterns/ai/ai-chat.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,21 @@ When the AI cannot complete a task due to a known limitation, it should respond
These are not treated as errors and do not require a separate notification.

![AI errors and limitations](images/ai-errors.png)

## Code ---

Use the chat container with the chat messages to build chat message interfaces.

The **si-chat-container** component is a wrapper component, it has slots for chat messages and an input

The slots are:
- default (chat messages)
- `si-avatar/si-icon/img` - For the avatar or icon representing the message sender.
- `si-chat-input/siChatContainerInput (helper directive)` - For the input (whether default or custom).
- `si-inline-notification` - Slotted above the input for displaying the status.

<si-docs-component example="si-chat-messages/si-chat-container"></si-docs-component>

<si-docs-api component="SiChatContainerComponent"></si-docs-api>

<si-docs-types></si-docs-types>
1 change: 1 addition & 0 deletions playwright/e2e/element-examples/static.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,4 @@ test('si-chat-messages/si-user-message', ({ si }) => si.static());
test('si-chat-messages/si-chat-message', ({ si }) => si.static());
test('si-chat-messages/si-attachment-list', ({ si }) => si.static());
test('si-chat-messages/si-chat-input', ({ si }) => si.static());
test('si-chat-messages/si-chat-container', ({ si }) => si.static());
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
- group:
- button "data-analysis.py"
- group:
- button "dataset.csv"
- paragraph: Can you help me analyze these files?
- paragraph: I'm having trouble understanding the data structure and need assistance with the implementation.
- button "Copy message"
- paragraph: I'd be happy to help you analyze your files! I can see you've shared a Python script and a CSV dataset.
- paragraph: Let me examine the structure and provide guidance.
- button "Good response"
- button "Copy response"
- button "Retry response"
- button "More actions"
- paragraph: Perfect! What should I focus on first
- paragraph: I also want to make sure the performance is optimized for large datasets since this will be used in production with potentially millions of rows?
- button "Copy message"
- paragraph: Great question! When analyzing large datasets, it's crucial to focus on...
- alert: Info AI responses are for demonstration purposes.
- group:
- text: requirements.pdf
- button "Remove attachment requirements.pdf"
- group:
- text: mockup.png
- button "Remove attachment mockup.png"
- textbox "Chat message input":
- /placeholder: Enter a command, question or topic…
- button "Attach file"
- button "Text formatting"
- button "Message templates"
- button "Send"
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions projects/element-ng/chat-messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
*/
export * from './si-ai-message.component';
export * from './si-attachment-list.component';
export * from './si-chat-container.component';
export * from './si-chat-container-input.directive';
export * from './si-chat-input.component';
export * from './si-chat-input-disclaimer.directive';
export * from './si-chat-message-action.directive';
Expand Down
6 changes: 5 additions & 1 deletion projects/element-ng/chat-messages/message-action.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
import type { TranslatableString } from '@siemens/element-translate-ng/translate-types';

/**
* Actions for messages representing an action with icon, label (for accessibility), and handler.
* Actions for messages representing an action with icon, label (for accessibility), and handler, for use within {@link SiAiMessageComponent} and {@link SiUserMessageComponent}.
* Only the icon will be displayed.
*
* @see {@link SiAiMessageComponent} for the AI message
* @see {@link SiUserMessageComponent} for thee user message
*
* @experimental
*/
export interface MessageAction {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
}
}

@if (actions().length > 0 || secondaryActions().length > 0) {
@if ((actions()?.length ?? 0 > 0) || (secondaryActions()?.length ?? 0 > 0)) {
<div class="d-flex gap-4 ai-message-actions" siChatMessageAction>
@for (action of actions(); track $index) {
<button
Expand All @@ -22,7 +22,7 @@
</button>
}

@if (secondaryActions().length > 0) {
@if (secondaryActions()?.length ?? 0 > 0) {
<button
type="button"
class="btn btn-ghost btn-circle btn-sm"
Expand Down
62 changes: 7 additions & 55 deletions projects/element-ng/chat-messages/si-ai-message.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,63 +26,18 @@ import { SiChatMessageComponent } from './si-chat-message.component';
* The AI message component renders AI-generated content in chat interfaces,
* supporting text formatting, markdown, loading states, and contextual actions.
* It appears as text (no bubble) aligned to the left side without any avatar/icon slot.
*
* @remarks
* This component is designed for use in:
* - AI chat interfaces where model responses need to be displayed
* - Chatbot implementations
* - Conversational AI applications
* - AI assistant interfaces
* Can be used within {@link SiChatContainerComponent}.
*
* The component automatically handles:
* - Rendering markdown content with syntax highlighting
* - Styling for AI messages distinct from user or generic chat messages
* - Option to render markdown content, provide via `contentFormatter` input with a markdown renderer function (e.g., from {@link getMarkdownRenderer})
* - Showing loading states with skeleton UI during generation
* - Displaying primary and secondary actions on hover (desktop) or tap (mobile)
* - Proper alignment and styling for AI messages
* - Content sanitization for security
*
* @example
* Basic usage with content only:
* ```html
* <si-ai-message [content]="'I can help you with that.'" />
* ```
*
* @example
* With loading state and actions:
* ```typescript
* import { Component } from '@angular/core';
* import { SiAiMessageComponent } from '@siemens/element-ng/chat-messages';
*
* @Component({
* selector: 'app-chat',
* imports: [SiAiMessageComponent],
* template: `
* <si-ai-message
* [content]="message.text"
* [loading]="message.isGenerating"
* [actions]="messageActions"
* [secondaryActions]="menuActions"
* [actionParam]="message"
* />
* `
* })
* export class ChatComponent {
* messageActions = [
* { icon: 'thumbs-up', label: 'Good response', action: (id) => this.ratePositive(id) },
* { icon: 'thumbs-down', label: 'Bad response', action: (id) => this.rateNegative(id) },
* { icon: 'copy', label: 'Copy', action: (id) => this.copyMessage(id) }
* ];
*
* menuActions = [
* { label: 'Regenerate', action: (id) => this.regenerate(id) },
* { label: 'Report', action: (id) => this.reportMessage(id) }
* ];
* }
* ```
* - Displaying primary and secondary actions
*
* @see {@link SiChatMessageComponent} for the base message wrapper component
* @see {@link SiUserMessageComponent} for the companion user message component
* @see {@link SiUserMessageComponent} for the user message component
* @see {@link getMarkdownRenderer} for markdown formatting support
* @see {@link SiChatContainerComponent} for the chat container to use this within
*
* @experimental
*/
Expand All @@ -97,10 +52,7 @@ import { SiChatMessageComponent } from './si-chat-message.component';
SiTranslatePipe
],
templateUrl: './si-ai-message.component.html',
styleUrl: './si-ai-message.component.scss',
host: {
class: 'si-ai-message'
}
styleUrl: './si-ai-message.component.scss'
})
export class SiAiMessageComponent {
protected readonly formattedContent = viewChild<ElementRef<HTMLDivElement>>('formattedContent');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import { SiModalService } from '@siemens/element-ng/modal';
import { SiTranslatePipe, t } from '@siemens/element-translate-ng/translate';

/**
* Attachment item interface for file attachments in chat messages.
* Attachment item interface for file attachments in chat messages, used by {@link SiAttachmentListComponent} and inside {@link SiUserMessageComponent} as well as {@link SiChatInputComponent}.
*
* @see {@link SiAttachmentListComponent} for the attachment list component
* @see {@link SiUserMessageComponent} for the user message
* @see {@link SiChatInputComponent} for the chat input component
*
* @experimental
*/
Expand All @@ -26,55 +30,17 @@ export interface Attachment {
* preview and remove functionality. It's designed to work with chat message components
* to show files that have been uploaded or shared in conversations.
*
* @remarks
* This component provides:
* - Automatic file type detection with appropriate icons
* - A list of pills showing each attachment's name and an icon
* - Optional preview modal for attachments
* - Optional remove functionality for editable messages
* - Flexible alignment (start/end) to match message alignment
* - Support for various file types (images, videos, audio, documents, archives)
*
* The component is typically used within {@link SiUserMessageComponent} or {@link SiAiMessageComponent}
* to display uploaded files, but can also be used standalone.
*
* @example
* Basic usage with attachments:
* ```html
* <si-attachment-list [attachments]="files" />
* ```
*
* @example
* With remove functionality and custom alignment:
* ```typescript
* import { Component } from '@angular/core';
* import { SiAttachmentListComponent, Attachment } from '@siemens/element-ng/chat-messages';
*
* @Component({
* selector: 'app-chat',
* imports: [SiAttachmentListComponent],
* template: `
* <si-attachment-list
* [attachments]="attachments"
* [alignment]="'end'"
* [removable]="true"
* (remove)="handleRemove($event)"
* />
* `
* })
* export class ChatComponent {
* attachments: Attachment[] = [
* { id: '1', name: 'report.pdf' },
* { id: '2', name: 'image.png', previewTemplate: this.imagePreview }
* ];
*
* handleRemove(attachment: Attachment) {
* this.attachments = this.attachments.filter(a => a !== attachment);
* }
* }
* ```
* The component is included within {@link SiUserMessageComponent}, {@link SiAiMessageComponent} and {@link SiChatInputComponent} but can also be used inside custom chat messages with {@link SiChatMessageComponent}
*
* @see {@link SiUserMessageComponent} for user message display
* @see {@link SiAiMessageComponent} for AI message display
* @see {@link SiChatMessageComponent} for custom chat message display
* @see {@link SiChatInputComponent} for chat input with attachment support
* @see {@link Attachment} for attachment data structure
*
* @experimental
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Copyright (c) Siemens 2016 - 2025
* SPDX-License-Identifier: MIT
*/
import { Directive } from '@angular/core';

/**
* A directive to mark elements as input controls within a {@link SiChatContainerComponent}.
*
* This directive is used to identify and style input elements that belong to
* the chat container component, typically applied to form inputs or textareas
* used for composing chat messages.
*
* @example
* ```html
* <si-chat-container>
* <si-chat-message>Hello!</si-chat-message>
* <si-chat-message>How are you?</si-chat-message>
*
* <input siChatContainerInput type="text" placeholder="Type a message..." />
* </si-chat-container>
* ```
*
* @see {@link SiChatContainerComponent} for the chat container wrapper component
*
* @experimental
*/
@Directive({
selector: '[siChatContainerInput]'
})
export class SiChatContainerInputDirective {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<div class="chat-container d-flex flex-column flex-grow-1 px-6 px-md-9 py-6 w-100">
<div
#messagesContainer
class="messages-container d-flex flex-column flex-grow-1 w-100 align-self-center"
tabindex="0"
(scroll)="onScroll()"
>
<ng-content />
</div>

<div class="chat-input-area py-3 pt-0 mt-6 d-flex flex-column align-items-center">
<ng-content select="si-inline-notification" />
<ng-content select="si-chat-input,[siChatContainerInput]" />
</div>
</div>
Loading
Loading