From a6f30cc3ebc4e5915128147d258607b529c7c174 Mon Sep 17 00:00:00 2001 From: Linus Schlumberger Date: Wed, 5 Nov 2025 19:30:13 +0100 Subject: [PATCH 1/5] fix(chat-messages): auto-collapse smaller left-aligned chat-messages --- ...e-element-examples-chromium-dark-linux.png | 4 +-- ...-element-examples-chromium-light-linux.png | 4 +-- .../chat-messages/si-ai-message.component.ts | 5 +-- .../si-chat-message.component.html | 1 + .../si-chat-message.component.scss | 33 ++++++++----------- 5 files changed, 19 insertions(+), 28 deletions(-) diff --git a/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-message-element-examples-chromium-dark-linux.png b/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-message-element-examples-chromium-dark-linux.png index a4ca0684e..f6996a1ae 100644 --- a/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-message-element-examples-chromium-dark-linux.png +++ b/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-message-element-examples-chromium-dark-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:da41b13122636959844f35394c7a51c8df53fae54e2c7e38ee04f2bdbded8aff -size 21037 +oid sha256:4a945aa0a229d60d29e12c55313a2cede322a7f8985780eef464c1a61e8a1a88 +size 21049 diff --git a/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-message-element-examples-chromium-light-linux.png b/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-message-element-examples-chromium-light-linux.png index d7ff43a3f..66e7a9a24 100644 --- a/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-message-element-examples-chromium-light-linux.png +++ b/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-message-element-examples-chromium-light-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3b9cb32d78ddfe58a571b80487e73a38a64620a60f395a81a2f245af141ede8 -size 20133 +oid sha256:0ec236c9f4e4a2e6e279dfba82a8d50241d47ad587fda50d9dff25f044e0fc97 +size 20146 diff --git a/projects/element-ng/chat-messages/si-ai-message.component.ts b/projects/element-ng/chat-messages/si-ai-message.component.ts index a25dcf5fe..82172c031 100644 --- a/projects/element-ng/chat-messages/si-ai-message.component.ts +++ b/projects/element-ng/chat-messages/si-ai-message.component.ts @@ -97,10 +97,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>('formattedContent'); diff --git a/projects/element-ng/chat-messages/si-chat-message.component.html b/projects/element-ng/chat-messages/si-chat-message.component.html index 22f2648f2..c58c18b35 100644 --- a/projects/element-ng/chat-messages/si-chat-message.component.html +++ b/projects/element-ng/chat-messages/si-chat-message.component.html @@ -23,6 +23,7 @@
Date: Mon, 10 Nov 2025 13:17:10 +0100 Subject: [PATCH 2/5] docs(chat-messages): align JSDoc in base components --- .../element-ng/chat-messages/index.api.md | 31 +++++++++- .../chat-messages/message-action.model.ts | 6 +- .../chat-messages/si-ai-message.component.ts | 55 ++---------------- .../si-attachment-list.component.ts | 52 +++-------------- .../si-chat-input-disclaimer.directive.ts | 6 +- .../chat-messages/si-chat-input.component.ts | 32 ++++++++++ .../si-chat-message-action.directive.ts | 7 ++- .../si-chat-message.component.ts | 20 +++++-- .../si-user-message.component.ts | 58 +++---------------- 9 files changed, 112 insertions(+), 155 deletions(-) diff --git a/api-goldens/element-ng/chat-messages/index.api.md b/api-goldens/element-ng/chat-messages/index.api.md index 71f834212..1a323e46c 100644 --- a/api-goldens/element-ng/chat-messages/index.api.md +++ b/api-goldens/element-ng/chat-messages/index.api.md @@ -23,7 +23,7 @@ export interface Attachment { previewTemplate?: TemplateRef | (() => TemplateRef); } -// @public (undocumented) +// @public export interface ChatInputAttachment extends Attachment { file: File; size: number; @@ -78,6 +78,35 @@ export class SiAttachmentListComponent { } // @public (undocumented) +export class SiChatContainerComponent implements AfterContentInit, OnDestroy { + constructor(); + // (undocumented) + readonly colorVariant: _angular_core.InputSignal; + // (undocumented) + focus(): void; + // (undocumented) + ngAfterContentInit(): void; + // (undocumented) + ngOnDestroy(): void; + // (undocumented) + readonly noAutoScroll: _angular_core.InputSignalWithTransform; + // (undocumented) + protected onScroll(): void; + // (undocumented) + static ɵcmp: _angular_core.ɵɵComponentDeclaration; + // (undocumented) + static ɵfac: _angular_core.ɵɵFactoryDeclaration; +} + +// @public (undocumented) +export class SiChatContainerInputDirective { + // (undocumented) + static ɵdir: _angular_core.ɵɵDirectiveDeclaration; + // (undocumented) + static ɵfac: _angular_core.ɵɵFactoryDeclaration; +} + +// @public export class SiChatInputComponent implements AfterViewInit { readonly accept: _angular_core.InputSignal; readonly actionParam: _angular_core.InputSignal; diff --git a/projects/element-ng/chat-messages/message-action.model.ts b/projects/element-ng/chat-messages/message-action.model.ts index cc50b0dd2..aa7b25239 100644 --- a/projects/element-ng/chat-messages/message-action.model.ts +++ b/projects/element-ng/chat-messages/message-action.model.ts @@ -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 { diff --git a/projects/element-ng/chat-messages/si-ai-message.component.ts b/projects/element-ng/chat-messages/si-ai-message.component.ts index 82172c031..d38c1882e 100644 --- a/projects/element-ng/chat-messages/si-ai-message.component.ts +++ b/projects/element-ng/chat-messages/si-ai-message.component.ts @@ -27,61 +27,14 @@ import { SiChatMessageComponent } from './si-chat-message.component'; * 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 - * * 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 - * - * ``` - * - * @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: ` - * - * ` - * }) - * 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 * * @experimental diff --git a/projects/element-ng/chat-messages/si-attachment-list.component.ts b/projects/element-ng/chat-messages/si-attachment-list.component.ts index ed3f2e16a..3dd12dfec 100644 --- a/projects/element-ng/chat-messages/si-attachment-list.component.ts +++ b/projects/element-ng/chat-messages/si-attachment-list.component.ts @@ -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 */ @@ -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 - * - * ``` - * - * @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: ` - * - * ` - * }) - * 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 diff --git a/projects/element-ng/chat-messages/si-chat-input-disclaimer.directive.ts b/projects/element-ng/chat-messages/si-chat-input-disclaimer.directive.ts index 63bda7a84..5ac508b10 100644 --- a/projects/element-ng/chat-messages/si-chat-input-disclaimer.directive.ts +++ b/projects/element-ng/chat-messages/si-chat-input-disclaimer.directive.ts @@ -5,7 +5,7 @@ import { Directive } from '@angular/core'; /** - * Directive to mark content as chat input disclaimer. + * Directive to mark content as chat input disclaimer into {@link SiChatInputComponent}. * Apply this directive to content that should be slotted into the disclaimer area. * * @example @@ -16,6 +16,10 @@ import { Directive } from '@angular/core'; *
* * ``` + * + * @see {@link SiChatInputComponent} for the chat input wrapper component + * + * @experimental */ @Directive({ selector: '[siChatInputDisclaimer]' diff --git a/projects/element-ng/chat-messages/si-chat-input.component.ts b/projects/element-ng/chat-messages/si-chat-input.component.ts index e17cdfc8f..eda00cba5 100644 --- a/projects/element-ng/chat-messages/si-chat-input.component.ts +++ b/projects/element-ng/chat-messages/si-chat-input.component.ts @@ -27,6 +27,16 @@ import { SiTranslatePipe, TranslatableString, t } from '@siemens/element-transla import { MessageAction } from './message-action.model'; import { SiAttachmentListComponent, Attachment } from './si-attachment-list.component'; +/** + * Attachment item interface for file attachments in chat messages, extension of {@link Attachment} for {@link SiAttachmentListComponent} to use within {@link SiChatInputComponent}. + * Adds the action file information. + * + * @see {@link Attachment} for base attachment interface + * @see {@link SiAttachmentListComponent} for the attachment list component + * @see {@link SiChatInputComponent} for the chat input component + * + * @experimental + */ export interface ChatInputAttachment extends Attachment { /** File object */ file: File; @@ -36,6 +46,28 @@ export interface ChatInputAttachment extends Attachment { type: string; } +/** + * Chat input component for composing and sending messages in conversational interfaces. + * + * The chat input component provides a text area for users to compose messages, + * supporting text, attachments, and contextual actions. It appears as a textarea + * with buttons for adding attachments and sending messages, as well as an optional disclaimer. + * + * The component automatically handles: + * - Styling for chat input and actions. + * - Dynamic resizing of the textarea based on content. + * - Uploading of and displaying of attachments above the input area. + * - Displaying primary and secondary actions. + * + * Additionally to the inputs and outputs documented here, the component supports content projection via the following slots: + * - Default content: Custom action buttons to display inline, prefer using the `actions` input for buttons, can be used in addition. + * - `siChatInputDisclaimer` selector: Custom disclaimer content to display below the input area, prefer using the `disclaimer` input for simple text disclaimers. + * + * @see {@link SiAttachmentListComponent} for the base attachment component + * @see {@link SiChatInputDisclaimerDirective} to slot in custom disclaimer content + * + * @experimental + */ @Component({ selector: 'si-chat-input', imports: [ diff --git a/projects/element-ng/chat-messages/si-chat-message-action.directive.ts b/projects/element-ng/chat-messages/si-chat-message-action.directive.ts index 0a43204cc..89a410db0 100644 --- a/projects/element-ng/chat-messages/si-chat-message-action.directive.ts +++ b/projects/element-ng/chat-messages/si-chat-message-action.directive.ts @@ -5,10 +5,9 @@ import { Directive } from '@angular/core'; /** - * Directive to mark content as chat message actions. + * Directive to mark content as chat message actions into {@link SiChatMessageComponent}. * Apply this directive to e.g. buttons that should be slotted into the message actions area. * - * @experimental * @example * ```html * @@ -17,6 +16,10 @@ import { Directive } from '@angular/core'; * * * ``` + * + * @see {@link SiChatMessageComponent} for the chat message wrapper component + * + * @experimental */ @Directive({ selector: '[siChatMessageAction]' diff --git a/projects/element-ng/chat-messages/si-chat-message.component.ts b/projects/element-ng/chat-messages/si-chat-message.component.ts index 57dd10f09..fe2e9747e 100644 --- a/projects/element-ng/chat-messages/si-chat-message.component.ts +++ b/projects/element-ng/chat-messages/si-chat-message.component.ts @@ -6,23 +6,31 @@ import { Component, input } from '@angular/core'; import { SiResponsiveContainerDirective } from '@siemens/element-ng/resize-observer'; /** - * Base chat message component that provides the layout structure for conversational interfaces. + * Base declarative chat message component that provides the layout structure for chat messages. * * This component handles the core message layout including avatar positioning, loading states, - * and action button placement. It serves as the foundation for more specialized message components + * and action button as well as attachment list placement. It serves as the foundation for more specialized message components * like {@link SiUserMessageComponent} and {@link SiAiMessageComponent}. * - * @remarks * The component provides: * - Flexible alignment (start/end) for different message types * - Avatar/icon slot for message attribution * - Loading state with skeleton UI * - Action buttons positioned on the side or bottom + * - Attachment list display slot * - Responsive behavior that adapts to container size - * - Attachment display slot * - * This is a low-level component typically not used directly. Instead, use the higher-level - * message components that wrap this component with specific styling and behavior. + * This is a low-level component designed for slotting in custom content, it provides slots via content projection: + * - Default content: Main message content area (consider using {@link SiMarkdownRendererComponent} for markdown support) + * - `si-avatar/si-icon/img` selector: Avatar or icon representing the message sender + * - `si-chat-message-action` selector: Action buttons related to the message + * - `si-attachment-list` selector: Attachment list component for displaying file attachments + * + * @see {@link SiUserMessageComponent} for user message display + * @see {@link SiAiMessageComponent} for AI message display + * @see {@link SiAttachmentListComponent} for attachment list to slot in + * @see {@link SiChatMessageActionDirective} for action buttons to slot in + * @see {@link SiMarkdownRendererComponent} for markdown content rendering * * @experimental */ diff --git a/projects/element-ng/chat-messages/si-user-message.component.ts b/projects/element-ng/chat-messages/si-user-message.component.ts index 54c400eff..1386ba2b8 100644 --- a/projects/element-ng/chat-messages/si-user-message.component.ts +++ b/projects/element-ng/chat-messages/si-user-message.component.ts @@ -14,64 +14,22 @@ import { SiChatMessageActionDirective } from './si-chat-message-action.directive import { SiChatMessageComponent } from './si-chat-message.component'; /** - * User message component for displaying user input in conversational interfaces. + * User message component for displaying the user's messages in conversational interfaces. * - * The user message component renders user-submitted content in chat interfaces, + * The user message component renders user-submitted content in (AI) chat interfaces, * supporting text, attachments, and contextual actions. It appears as a text bubble * aligned to the right side and supports markdown formatting for rich content. * - * @remarks - * This component is designed for use in: - * - AI chat interfaces where user input needs to be displayed - * - Peer-to-peer conversation interfaces - * - Conversation histories or chat transcripts - * * The component automatically handles: - * - Rendering markdown content with syntax highlighting + * - Styling for user messages distinct from AI or generic chat messages + * - Option to render markdown content, provide via `contentFormatter` input with a markdown renderer function (e.g., from {@link getMarkdownRenderer}) * - Displaying attachments above the message bubble - * - Showing primary and secondary actions on hover (desktop) or tap (mobile) - * - Proper alignment and styling for user messages - * - * @example - * Basic usage with content only: - * ```html - * - * ``` - * - * @example - * With actions and attachments: - * ```typescript - * import { Component } from '@angular/core'; - * import { SiUserMessageComponent } from '@siemens/element-ng/chat-messages'; - * - * @Component({ - * selector: 'app-chat', - * imports: [SiUserMessageComponent], - * template: ` - * - * ` - * }) - * export class ChatComponent { - * messageActions = [ - * { icon: 'copy', label: 'Copy', action: (id) => this.copyMessage(id) }, - * { icon: 'edit', label: 'Edit', action: (id) => this.editMessage(id) } - * ]; - * - * menuActions = [ - * { label: 'Delete', action: (id) => this.deleteMessage(id) } - * ]; - * } - * ``` + * - Displaying primary and secondary actions * * @see {@link SiChatMessageComponent} for the base message wrapper component - * @see {@link SiAttachmentListComponent} for attachment handling - * @see {@link SiMarkdownRendererComponent} for markdown rendering + * @see {@link SiAiMessageComponent} for the AI message component + * @see {@link SiAttachmentListComponent} for the base attachment component + * @see {@link getMarkdownRenderer} for markdown formatting support * * @experimental */ From 2cb808316bfb7b9c825fb952b5baea70f935540b Mon Sep 17 00:00:00 2001 From: Linus Schlumberger Date: Thu, 30 Oct 2025 15:06:56 +0100 Subject: [PATCH 3/5] feat(chat-messages): add chat container --- .../element-ng/chat-messages/index.api.md | 9 +- projects/element-ng/chat-messages/index.ts | 2 + .../si-ai-message.component.html | 4 +- .../chat-messages/si-ai-message.component.ts | 2 + .../si-chat-container-input.directive.ts | 31 +++ .../si-chat-container.component.html | 15 ++ .../si-chat-container.component.scss | 76 ++++++++ .../si-chat-container.component.spec.ts | 154 +++++++++++++++ .../si-chat-container.component.ts | 180 ++++++++++++++++++ .../chat-messages/si-chat-input.component.ts | 2 + .../si-chat-message.component.ts | 2 + .../si-user-message.component.html | 4 +- .../si-user-message.component.ts | 4 +- 13 files changed, 475 insertions(+), 10 deletions(-) create mode 100644 projects/element-ng/chat-messages/si-chat-container-input.directive.ts create mode 100644 projects/element-ng/chat-messages/si-chat-container.component.html create mode 100644 projects/element-ng/chat-messages/si-chat-container.component.scss create mode 100644 projects/element-ng/chat-messages/si-chat-container.component.spec.ts create mode 100644 projects/element-ng/chat-messages/si-chat-container.component.ts diff --git a/api-goldens/element-ng/chat-messages/index.api.md b/api-goldens/element-ng/chat-messages/index.api.md index 1a323e46c..712dd344f 100644 --- a/api-goldens/element-ng/chat-messages/index.api.md +++ b/api-goldens/element-ng/chat-messages/index.api.md @@ -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'; @@ -77,18 +79,15 @@ export class SiAttachmentListComponent { static ɵfac: _angular_core.ɵɵFactoryDeclaration; } -// @public (undocumented) +// @public export class SiChatContainerComponent implements AfterContentInit, OnDestroy { constructor(); - // (undocumented) readonly colorVariant: _angular_core.InputSignal; - // (undocumented) focus(): void; // (undocumented) ngAfterContentInit(): void; // (undocumented) ngOnDestroy(): void; - // (undocumented) readonly noAutoScroll: _angular_core.InputSignalWithTransform; // (undocumented) protected onScroll(): void; @@ -98,7 +97,7 @@ export class SiChatContainerComponent implements AfterContentInit, OnDestroy { static ɵfac: _angular_core.ɵɵFactoryDeclaration; } -// @public (undocumented) +// @public export class SiChatContainerInputDirective { // (undocumented) static ɵdir: _angular_core.ɵɵDirectiveDeclaration; diff --git a/projects/element-ng/chat-messages/index.ts b/projects/element-ng/chat-messages/index.ts index ed9aaad10..d0442eb14 100644 --- a/projects/element-ng/chat-messages/index.ts +++ b/projects/element-ng/chat-messages/index.ts @@ -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'; diff --git a/projects/element-ng/chat-messages/si-ai-message.component.html b/projects/element-ng/chat-messages/si-ai-message.component.html index 34e04f4bf..6e5a47989 100644 --- a/projects/element-ng/chat-messages/si-ai-message.component.html +++ b/projects/element-ng/chat-messages/si-ai-message.component.html @@ -8,7 +8,7 @@ } } - @if (actions().length > 0 || secondaryActions().length > 0) { + @if ((actions()?.length ?? 0 > 0) || (secondaryActions()?.length ?? 0 > 0)) {
@for (action of actions(); track $index) { + } + + } + } + + @if ( + loading() && (messages().length === 0 || messages()[messages().length - 1].type !== 'ai') + ) { + + } + + + + + +
+ + + + + diff --git a/src/app/examples/si-chat-messages/si-chat-container.ts b/src/app/examples/si-chat-messages/si-chat-container.ts new file mode 100644 index 000000000..335ce8f05 --- /dev/null +++ b/src/app/examples/si-chat-messages/si-chat-container.ts @@ -0,0 +1,289 @@ +/** + * Copyright (c) Siemens 2016 - 2025 + * SPDX-License-Identifier: MIT + */ +import { Component, inject, signal, TemplateRef, viewChild } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; +import { + SiChatContainerComponent, + SiAiMessageComponent, + SiUserMessageComponent, + SiChatInputComponent, + SiChatMessageComponent, + ChatInputAttachment, + MessageAction, + SiChatMessageActionDirective, + SiAttachmentListComponent, + Attachment +} from '@siemens/element-ng/chat-messages'; +import { SiIconComponent } from '@siemens/element-ng/icon'; +import { SiInlineNotificationComponent } from '@siemens/element-ng/inline-notification'; +import { + getMarkdownRenderer, + SiMarkdownRendererComponent +} from '@siemens/element-ng/markdown-renderer'; +import { MenuItem } from '@siemens/element-ng/menu'; +import { LOG_EVENT } from '@siemens/live-preview'; + +interface ChatMessage { + type: 'user' | 'ai' | 'custom'; + content: string; + attachments?: Attachment[]; + actions?: MessageAction[]; +} + +@Component({ + selector: 'app-sample', + imports: [ + SiChatContainerComponent, + SiAiMessageComponent, + SiUserMessageComponent, + SiInlineNotificationComponent, + SiChatInputComponent, + SiChatMessageComponent, + SiIconComponent, + SiMarkdownRendererComponent, + SiChatMessageActionDirective, + SiAttachmentListComponent + ], + templateUrl: './si-chat-container.html' +}) +export class SampleComponent { + private logEvent = inject(LOG_EVENT); + private readonly modalTemplate = viewChild>('modalTemplate'); + private sanitizer = inject(DomSanitizer); + + protected markdownRenderer = getMarkdownRenderer(this.sanitizer); + + readonly preAttachedFiles: ChatInputAttachment[] = [ + { + name: 'requirements.pdf', + size: 1234567, + type: 'application/pdf', + file: new File([''], 'requirements.pdf', { type: 'application/pdf' }) + }, + { + name: 'mockup.png', + size: 654321, + type: 'image/png', + file: new File([''], 'mockup.png', { type: 'image/png' }) + } + ]; + + readonly messages = signal([ + { + type: 'user', + content: `Can you help me analyze these files? + + I'm having trouble understanding the data structure + and need assistance with the implementation.`, + attachments: [ + { + name: 'data-analysis.py', + previewTemplate: () => this.modalTemplate()! + }, + { + name: 'dataset.csv', + previewTemplate: () => this.modalTemplate()! + } + ], + actions: [ + { + label: 'Copy message', + icon: 'element-export', + action: (message: ChatMessage) => + this.logEvent(`Copy user message ${message.content.slice(0, 20)}...`) + } + ] + }, + { + type: 'ai', + content: `I'd be happy to help you analyze your files! I can see you've shared a Python script and a CSV dataset. + + Let me examine the structure and provide guidance.`, + actions: [ + { + label: 'Good response', + icon: 'element-plus', + action: (_message: ChatMessage) => this.logEvent('Thumbs up for AI message') + }, + { + label: 'Copy response', + icon: 'element-export', + action: (_message: ChatMessage) => this.logEvent('Copy AI message') + }, + { + label: 'Retry response', + icon: 'element-refresh', + action: (_message: ChatMessage) => this.logEvent('Retry AI message') + }, + { + label: 'Bookmark', + icon: 'element-bookmark', + action: (_message: ChatMessage) => this.logEvent('Bookmark AI message') + } + ] + }, + { + type: 'user', + content: + 'Perfect! What should I focus on first\n\nI also want to make sure the performance is optimized for large datasets since this will be used in production with potentially millions of rows?', + actions: [ + { + label: 'Copy message', + icon: 'element-export', + action: (_message: ChatMessage) => + this.logEvent(`Copy user message ${_message.content.slice(0, 20)}...`) + } + ] + }, + { + type: 'ai', + content: "Great question! When analyzing large datasets, it's crucial to focus on..." + } + ]); + + readonly loading = signal(false); + readonly sending = signal(false); + readonly disabled = signal(false); + readonly disableInterrupt = signal(false); + readonly interrupting = signal(false); + readonly inputValue = signal(''); + + inputActions: MessageAction[] = [ + { + label: 'Text formatting', + icon: 'element-brush', + action: () => this.logEvent('Text formatting clicked') + }, + { + label: 'Message templates', + icon: 'element-template', + action: () => this.logEvent('Templates clicked') + } + ]; + + userActions: MessageAction[] = [ + { + label: 'Copy message', + icon: 'element-export', + action: (_message: ChatMessage) => + this.logEvent(`Copy user message ${_message.content.slice(0, 20)}...`) + }, + { + label: 'Delete message', + icon: 'element-delete', + action: (_message: ChatMessage) => + this.logEvent(`Delete user message ${_message.content.slice(0, 20)}...`) + } + ]; + + aiActions: MessageAction[] = [ + { + label: 'Good response', + icon: 'element-plus', + action: (_message: ChatMessage) => this.logEvent('Thumbs up for AI message') + }, + { + label: 'Copy response', + icon: 'element-export', + action: (_message: ChatMessage) => this.logEvent('Copy AI message') + } + ]; + + onMessageSent(event: { content: string; attachments: ChatInputAttachment[] }): void { + this.logEvent(`Message sent: "${event.content}" with ${event.attachments.length} attachments`); + this.messages.update(current => [ + ...current, + { + type: 'user', + content: event.content, + actions: [ + { + label: 'Copy message', + icon: 'element-export', + action: () => this.logEvent('Copy user message') + } + ], + attachments: event.attachments.map(att => ({ + name: att.name, + previewTemplate: () => this.modalTemplate()! + })) + } + ]); + this.simulateAiResponse(event.content); + } + + onInterrupt(): void { + this.logEvent('Interrupt clicked'); + this.loading.set(false); + this.interrupting.set(false); + } + + private simulateAiResponse(userInput: string): void { + this.sending.set(true); + + setTimeout(() => { + this.sending.set(false); + this.loading.set(true); + + setTimeout(() => { + const response = `Thanks for your message: "${userInput}". I can help with that!`; + + this.messages.update(current => [ + ...current, + { + type: 'ai', + content: response, + actions: [ + { + label: 'Good response', + icon: 'element-plus', + action: () => this.logEvent('Thumbs up for AI message') + }, + { + label: 'Copy response', + icon: 'element-export', + action: () => this.logEvent('Copy AI message') + } + ] + } + ]); + this.loading.set(false); + }, 2000); + }, 1000); + } + + private getMessageActions(message: ChatMessage): { + primary: MessageAction[]; + secondary: MenuItem[]; + } { + const actions = message.actions ?? []; + let primary: MessageAction[] = []; + let secondary: MenuItem[] = []; + + const primaryActions = actions.slice(0, 3); + const secondaryActions = actions.slice(3); + + primary = primaryActions; + secondary = secondaryActions.map( + action => + ({ + ...action, + action: action.action as unknown as (actionParam: any, source: MenuItem) => void, + type: 'action' + }) as MenuItem + ); + + const result = { primary, secondary }; + return result; + } + + protected getMessagePrimaryActions(message: ChatMessage): MessageAction[] { + return this.getMessageActions(message).primary; + } + + protected getMessageSecondaryActions(message: ChatMessage): MenuItem[] { + return this.getMessageActions(message).secondary; + } +} From 771a752395b942b95d26472d5e60546b9bc5f389 Mon Sep 17 00:00:00 2001 From: Linus Schlumberger Date: Mon, 10 Nov 2025 10:57:32 +0100 Subject: [PATCH 5/5] test(chat-messages): add static VRT for chat-container --- .../e2e/element-examples/static.spec.ts | 1 + ...r-element-examples-chromium-dark-linux.png | 3 ++ ...-element-examples-chromium-light-linux.png | 3 ++ .../si-chat-messages--si-chat-container.yaml | 30 +++++++++++++++++++ 4 files changed, 37 insertions(+) create mode 100644 playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-container-element-examples-chromium-dark-linux.png create mode 100644 playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-container-element-examples-chromium-light-linux.png create mode 100644 playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-container.yaml diff --git a/playwright/e2e/element-examples/static.spec.ts b/playwright/e2e/element-examples/static.spec.ts index 2fa83acd7..091329a60 100644 --- a/playwright/e2e/element-examples/static.spec.ts +++ b/playwright/e2e/element-examples/static.spec.ts @@ -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()); diff --git a/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-container-element-examples-chromium-dark-linux.png b/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-container-element-examples-chromium-dark-linux.png new file mode 100644 index 000000000..306539191 --- /dev/null +++ b/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-container-element-examples-chromium-dark-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af8a39d7edb8ed34dcedb3f86fcf45a32c9c01c2539194f46ff0f9abe5e3a9db +size 45596 diff --git a/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-container-element-examples-chromium-light-linux.png b/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-container-element-examples-chromium-light-linux.png new file mode 100644 index 000000000..46c86dc07 --- /dev/null +++ b/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-container-element-examples-chromium-light-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b386637c7ecd71dfdf358fcefb6de8c96b1bec0c846df9feea9aa1579dda41c +size 44278 diff --git a/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-container.yaml b/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-container.yaml new file mode 100644 index 000000000..8c06f612f --- /dev/null +++ b/playwright/snapshots/static.spec.ts-snapshots/si-chat-messages--si-chat-container.yaml @@ -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"