Skip to content
Open
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
45 changes: 45 additions & 0 deletions api-goldens/element-ng/chat-messages/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ 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 { OnInit } from '@angular/core';
import * as _siemens_element_translate_ng_translate from '@siemens/element-translate-ng/translate';
import { SiModalService } from '@siemens/element-ng/modal';
import { SiPopoverDirective } from '@siemens/element-ng/popover';
import { TemplateRef } from '@angular/core';
import { TranslatableString } from '@siemens/element-translate-ng/translate-types';
import { TranslatableString as TranslatableString_2 } from '@siemens/element-translate-ng/translate';
Expand Down Expand Up @@ -57,6 +59,8 @@ export class SiAiMessageComponent {
constructor();
readonly actionParam: _angular_core.InputSignal<unknown>;
readonly actions: _angular_core.InputSignal<MessageAction[]>;
readonly annotatedText: _angular_core.InputSignal<SiChatAnnotatedText | undefined>;
readonly citationClicked: _angular_core.OutputEmitterRef<SiChatCitation>;
readonly content: _angular_core.InputSignal<string>;
readonly contentFormatter: _angular_core.InputSignal<((text: string) => string | Node) | undefined>;
readonly loading: _angular_core.InputSignalWithTransform<boolean, unknown>;
Expand All @@ -81,6 +85,28 @@ export class SiAttachmentListComponent {
readonly removeLabel: _angular_core.InputSignal<_siemens_element_translate_ng_translate.TranslatableString>;
}

// @public
export interface SiChatAnnotatedText {
citations: SiChatCitation[];
segments: SiChatTextSegment[];
}

// @public
export interface SiChatCitation {
description?: string;
id: string;
title: string;
url?: string;
}

// @public
export interface SiChatCitationRun {
// (undocumented)
citationId: string;
// (undocumented)
type: 'citation';
}

// @public
export class SiChatContainerComponent implements AfterContentInit, OnDestroy {
constructor();
Expand Down Expand Up @@ -143,6 +169,25 @@ export class SiChatMessageComponent {
readonly loading: _angular_core.InputSignal<boolean>;
}

// @public
export interface SiChatTextRun {
// (undocumented)
content: string;
// (undocumented)
type: 'text';
}

// @public (undocumented)
export type SiChatTextSegment = SiChatTextRun | SiChatCitationRun;

// @public
export class SiCitationPillComponent implements OnInit {
readonly citation: _angular_core.InputSignal<SiChatCitation>;
readonly clicked: _angular_core.OutputEmitterRef<SiChatCitation>;
readonly icon: _angular_core.InputSignal<string | undefined>;
readonly label: _angular_core.InputSignal<_siemens_element_translate_ng_translate.TranslatableString>;
}

// @public
export class SiUserMessageComponent {
constructor();
Expand Down
6 changes: 3 additions & 3 deletions playwright/e2e/element-examples/static.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@ test('typography/display-styles', ({ si }) => si.static());
test('typography/typography', ({ si }) => si.static());
test('si-markdown-renderer/si-markdown-renderer', ({ si }) =>
si.static({ disabledA11yRules: ['link-in-text-block'] }));
test('si-chat-messages/si-ai-message', ({ si }) => si.static());
test('si-chat-messages/si-ai-message', ({ si }) => si.static({ withPopover: 'si-citation-pill' }));
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());
// FIXME: test is unstable
// test('si-chat-messages/si-chat-container', ({ si }) => si.static());
test('si-chat-messages/si-chat-container', ({ si }) =>
si.static({ withPopover: 'si-citation-pill' }));
test('si-chat-messages/si-ai-welcome-screen', ({ si }) => si.static());
test('ag-grid/ag-grid-empty-state', async ({ si }) => {
await si.static({ disabledA11yRules: ['aria-required-children'] });
Expand Down
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,36 @@
- paragraph:
- text: Here's a
- strong: simple response
- text: with basic formatting.
- paragraph:
- text: You can use
- code: inline code
- text: "and create lists:"
- list:
- listitem: First item
- listitem: Second item
- group:
- button "Good response"
- button "Bad response"
- button "Copy response"
- button "More actions"
- text: Neural networks are composed of layers of interconnected nodes that process information in parallel, enabling the model to learn complex patterns from large datasets.
- group:
- link "Deep Learning – Ian Goodfellow et al.":
- /url: https://examples.org/deeplearningbook
- text: ""
- text: The training process adjusts the weights of these connections to minimize prediction error using backpropagation.
- group:
- link "Backpropagation Algorithm – Stanford CS231n":
- /url: https://examples.org/cs231n
- text: ""
- group:
- button "Good response"
- button "Bad response"
- button "Copy response"
- button "More actions"
- dialog "Deep Learning – Ian Goodfellow et al.":
- text: ""
- paragraph: Neural networks are universal function approximators composed of alternating linear transformations and non-linear activations, trained end-to-end via gradient descent.
- link "View source":
- /url: https://examples.org/deeplearningbook
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.
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,51 @@
- 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.
- group:
- button "Export message"
- text: I'd be happy to help! Data processing pipelines typically follow a structured approach.
- group:
- link "Data Pipeline Design Patterns":
- /url: https://examples.org/articles/data-pipeline.html
- text: ""
- text: Let me examine your files and provide detailed guidance.
- group:
- button "Add to list"
- button "Export 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?
- group:
- button "Export message"
- text: Great question! When analyzing large datasets, it's crucial to focus on vectorized operations and avoid row-by-row iteration.
- group:
- link "Pandas Performance Guide":
- /url: https://examples.org/docs/user_guide/enhancingperf.html
- group:
- button "Add to list"
- button "Export response"
- button "Retry response"
- button "More actions"
- 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...
- group:
- button "More actions"
- button "Show Welcome Screen"
- button "Send"
- text: The content is AI generated. Always verify the information for accuracy.
- dialog "Data Pipeline Design Patterns":
- text: ""
- paragraph: Pipelines can be structured as linear chains or branching graphs. Each stage transforms data independently, enabling testability and reuse.
- link "View source":
- /url: https://examples.org/articles/data-pipeline.html
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
Expand Up @@ -6,22 +6,29 @@
- paragraph: I'm having trouble understanding the data structure and need assistance with the implementation.
- group:
- button "Export 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.
- text: I'd be happy to help! Data processing pipelines typically follow a structured approach.
- group:
- button "Good response"
- button "Bad response"
- button "Copy response"
- link "Data Pipeline Design Patterns":
- /url: https://examples.org/articles/data-pipeline.html
- text: ""
- text: Let me examine your files and provide detailed guidance.
- group:
- button "Add to list"
- button "Export 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?
- group:
- button "Export message"
- paragraph: Great question! When analyzing large datasets, it's crucial to focus on...
- text: Great question! When analyzing large datasets, it's crucial to focus on vectorized operations and avoid row-by-row iteration.
- group:
- link "Pandas Performance Guide":
- /url: https://examples.org/docs/user_guide/enhancingperf.html
- group:
- button "Good response"
- button "Bad response"
- button "Copy response"
- button "Add to list"
- button "Export response"
- button "Retry response"
- button "More actions"
- alert: Info AI responses are for demonstration purposes.
- group:
Expand Down
11 changes: 11 additions & 0 deletions playwright/support/test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export type StaticTestOptions = {
waitCallback?: (page: Page) => Promise<void>;
skipAutoScaleViewport?: boolean;
skipAriaSnapshot?: boolean;
/** CSS selector of an element to hover after the initial snapshot to open a popover for a second snapshot. */
withPopover?: string;
};

// Playwright since 1.48 has the mouse cursor at 0/0 causing any element at this coordinate to be
Expand Down Expand Up @@ -113,6 +115,15 @@ class SiTestHelpers {
maxDiffPixels: options?.maxDiffPixels,
skipAriaSnapshot: options?.skipAriaSnapshot
});
if (options?.withPopover) {
await this.page.locator(options.withPopover).first().hover();
await expect(this.page.getByRole('dialog')).toBeVisible();
await this.runVisualAndA11yTests(step ? `${step}--popover` : 'popover', {
axeRulesSet: options?.disabledA11yRules?.map(item => ({ id: item, enabled: false })),
maxDiffPixels: options?.maxDiffPixels,
skipAriaSnapshot: options?.skipAriaSnapshot
});
}
}
}
}
Expand Down
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 @@ -3,7 +3,9 @@
* SPDX-License-Identifier: MIT
*/
export * from './si-ai-message.component';
export * from './si-annotated-text.model';
export * from './si-attachment-list.component';
export * from './si-citation-pill.component';
export * from './si-chat-container.component';
export * from './si-chat-container-input.directive';
export * from './si-chat-input.component';
Expand Down
17 changes: 16 additions & 1 deletion projects/element-ng/chat-messages/si-ai-message.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
<si-chat-message alignment="start" actionsPosition="bottom" [loading]="loading()">
@if (content()) {
@if (annotatedText(); as annotated) {
<span class="annotated-content text-pre-wrap">
@for (segment of annotated.segments; track $index) {
@if (segment.type === 'text') {
<span>{{ segment.content }}</span>
} @else {
@let cit = getCitation(segment.citationId);
<si-citation-pill
[citation]="cit"
[label]="cit.title.length > 24 ? cit.title.slice(0, 24) + '…' : cit.title"
(clicked)="citationClicked.emit($event)"
/><br />
Comment thread
robertwilde marked this conversation as resolved.
}
}
</span>
} @else if (content()) {
@let content = textContent();
@if (content) {
<span class="text-pre-wrap">{{ content }}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ si-chat-message {
margin-block-start: map.get(variables.$spacers, 5) - map.get(variables.$spacers, 2);
}

.annotated-content {
line-height: 20px;
}

// Loading spinner size adjustment (inherited from generic component)
:host ::ng-deep si-loading-spinner {
--loading-spinner-size: 1.5em;
Expand Down
27 changes: 27 additions & 0 deletions projects/element-ng/chat-messages/si-ai-message.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Component,
effect,
input,
output,
viewChild,
ElementRef,
signal
Expand All @@ -18,8 +19,10 @@ import { MenuItem, SiMenuFactoryComponent } from '@siemens/element-ng/menu';
import { SiTranslatePipe, t } from '@siemens/element-translate-ng/translate';

import { MessageAction } from './message-action.model';
import { SiChatAnnotatedText, SiChatCitation } from './si-annotated-text.model';
import { SiChatMessageActionDirective } from './si-chat-message-action.directive';
import { SiChatMessageComponent } from './si-chat-message.component';
import { SiCitationPillComponent } from './si-citation-pill.component';

/**
* AI message component for displaying AI-generated responses in conversational interfaces.
Expand Down Expand Up @@ -47,6 +50,7 @@ import { SiChatMessageComponent } from './si-chat-message.component';
imports: [
CdkMenuTrigger,
SiChatMessageComponent,
SiCitationPillComponent,
SiIconComponent,
SiMenuFactoryComponent,
SiChatMessageActionDirective,
Expand All @@ -59,6 +63,29 @@ export class SiAiMessageComponent {
protected readonly formattedContent = viewChild<ElementRef<HTMLDivElement>>('formattedContent');
protected readonly icons = addIcons({ elementOptionsVertical });

/**
* Pre-segmented annotated text containing inline citation references.
* When provided, takes precedence over the `content` input.
* Use `parseCitationMarkers` or `parseCitationOffsets` to produce this value.
* @defaultValue undefined
*/
readonly annotatedText = input<SiChatAnnotatedText | undefined>(undefined);

/**
* Emitted when a citation pill inside the message is clicked.
* The emitted value is the {@link SiChatCitation} that was clicked.
*/
readonly citationClicked = output<SiChatCitation>();

protected getCitation(id: string): SiChatCitation {
return (
this.annotatedText()?.citations.find(c => c.id === id) ?? {
id,
title: id
}
);
}

/**
* The AI-generated message content
* @defaultValue ''
Expand Down
Loading
Loading