Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9e38eee
feat(chat-messages): add code copy button to markdown renderer
Killusions Dec 6, 2025
a21fc3d
feat(chat-messages): add table download button to markdown renderer
Killusions Dec 6, 2025
aec9205
fix(chat-messages): complete unfinished code blocks for streaming in …
Killusions Dec 6, 2025
3e38d1b
fix(chat-messages): sanitize code-block separately
Killusions Dec 6, 2025
eaaa2d8
feat(chat-messages): add support for code syntax highlighting in mark…
Killusions Dec 6, 2025
717b3ca
fix(chat-messages): support horizontal rule/divider in markdown renderer
Killusions Dec 22, 2025
e0d15a5
docs(chat-messages): extend markdown renderer example
Killusions Dec 6, 2025
5853e94
fix(chat-messages): keep width of si-chat-message constrained
Killusions Dec 9, 2025
d2bc17b
fix(chat-messages): auto scroll si-chat-container correctly with fast…
Killusions Dec 9, 2025
4324397
fix(chat-messages): expose method to scroll to bottom
Killusions Dec 16, 2025
9623ac0
docs(chat-messages): scroll to bottom when user sends message in chat…
Killusions Dec 16, 2025
ba73669
fix(chat-messages): cache rendering in markdown renderer to allow for…
Killusions Dec 22, 2025
3e62153
fix(chat-messages): support table headers in markdown renderer
Killusions Dec 22, 2025
3df5241
feat(chat-messages): add support for latex formula rendering in markd…
Killusions Dec 22, 2025
9fd1561
fix(chat-messages): allow closing attachment modal on backdrop click
Killusions Jan 7, 2026
dfaa2e0
fix(chat-messages): expose method to scroll to top
Killusions Jan 8, 2026
01f9e4c
fixup(chat-messages): fix displaying of single line code blocks and m…
Killusions Jan 8, 2026
e1bb8b9
refactor(chat-messages): restructure markdown renderer
Killusions Jan 8, 2026
06c938e
fix(chat-messages): support nested code blocks
Killusions Jan 8, 2026
792f96b
fix(chat-messages): support angle bracket links
Killusions Jan 8, 2026
1a33314
fix(chat-messages): make table parsing more forgiving
Killusions Jan 9, 2026
60627ae
fixup(chat-messages): use new design for code blocks
Killusions Jan 14, 2026
9d64c9b
fix(chat-messages): underline links in markdown
Killusions Jan 14, 2026
9e68baa
fixup(chat-messages): use nicer table download button design
Killusions Jan 14, 2026
2147017
test(e2e): update vrt snapshots
Killusions Jan 14, 2026
36aaea3
fixup(chat-components): allow pre-rendering in SSR enviroments
Killusions Jan 16, 2026
17f2d29
feat(chat-messages): add ai-welcome-screen component
Killusions Dec 19, 2025
534316e
feat(chat-messages): add ai chat container
Killusions Jan 15, 2026
5dc1569
docs(chat-messages): add example for ai-chat-container
Killusions Jan 15, 2026
9b9f9d7
test(chat-messages): add static VRT for ai-chat-container
Killusions Jan 16, 2026
6730f5d
feat(chat-messages): add experimental tool message
Killusions Oct 30, 2025
ad7ef67
docs(chat-messages): add example and document experimental tool messages
Killusions Oct 30, 2025
232e8ef
test(chat-messages): add static VRT for experimental tool message
Killusions Oct 30, 2025
9759172
feat(chat-messages): add experimental tool message to ai-chat-container
Killusions Nov 6, 2025
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
2 changes: 1 addition & 1 deletion angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"stylePreprocessorOptions": {
"includePaths": ["node_modules/"]
},
"styles": ["src/styles.scss"],
"styles": ["src/styles.scss", "node_modules/katex/dist/katex.min.css"],
"scripts": [],
"allowedCommonJsDependencies": [
"@babel/standalone",
Expand Down
128 changes: 125 additions & 3 deletions api-goldens/element-ng/chat-messages/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,51 @@
import { AfterContentInit } from '@angular/core';
import { AfterViewInit } from '@angular/core';
import * as _angular_core from '@angular/core';
import { BackgroundColorVariant } from '@siemens/element-ng/common';
import { ElementRef } from '@angular/core';
import { FileUploadError } from '@siemens/element-ng/file-uploader';
import * as i1 from '@siemens/element-ng/resize-observer';
import { isSignal } from '@angular/core';
import { MenuItem } from '@siemens/element-ng/menu';
import { OnDestroy } from '@angular/core';
import * as _siemens_element_translate_ng_translate from '@siemens/element-translate-ng/translate';
import { Signal } from '@angular/core';
import { SiModalService } from '@siemens/element-ng/modal';
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';
import { UploadFile } from '@siemens/element-ng/file-uploader';

// @public
export interface AiChatMessage extends BaseChatMessage {
actions?: MessageAction[];
content: string | Signal<string>;
type: 'ai';
}

// @public
export interface Attachment {
name: string;
previewTemplate?: TemplateRef<any> | (() => TemplateRef<any>);
}

// @public
export interface BaseChatMessage {
content?: string | Signal<string>;
loading?: boolean | Signal<boolean>;
type: 'user' | 'ai' | 'tool';
}

// @public
export interface ChatInputAttachment extends Attachment {
file: File;
size: number;
type: string;
}

// @public
export type ChatMessage = UserChatMessage | AiChatMessage | ToolChatMessage | TemplateChatMessage;

// @public
export interface MessageAction {
action: (actionParam: any, source: this) => void;
Expand All @@ -40,6 +60,57 @@ export interface MessageAction {
label: TranslatableString;
}

// @public (undocumented)
export interface PromptCategory {
// (undocumented)
label: string;
}

// @public (undocumented)
export interface PromptSuggestion {
// (undocumented)
text: string;
}

// @public
export class SiAiChatContainerComponent {
constructor();
readonly aiIcon: _angular_core.InputSignal<string>;
readonly colorVariant: _angular_core.InputSignal<BackgroundColorVariant>;
readonly copyCodeButtonLabel: _angular_core.InputSignal<TranslatableString_2>;
readonly disableCopyCodeButton: _angular_core.InputSignal<boolean>;
readonly disableDownloadTableButton: _angular_core.InputSignal<boolean>;
readonly disableInterrupt: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly downloadTableButtonLabel: _angular_core.InputSignal<TranslatableString_2>;
focus(): void;
readonly greeting: _angular_core.InputSignal<TranslatableString_2>;
readonly interrupting: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly latexRenderer: _angular_core.InputSignal<((latex: string, displayMode: boolean) => string | undefined) | undefined>;
readonly loading: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly messages: _angular_core.InputSignal<ChatMessage[] | undefined>;
readonly messageSent: _angular_core.OutputEmitterRef<{
content: string;
attachments: ChatInputAttachment[];
}>;
readonly noAutoScroll: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly promptSuggestions: _angular_core.InputSignal<PromptSuggestion[] | Record<string, PromptSuggestion[]>>;
scrollToBottom(): void;
readonly secondaryActionsLabel: _angular_core.InputSignal<TranslatableString_2>;
readonly sending: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly statusAction: _angular_core.InputSignal<{
title: string;
href: string;
target?: string;
} | undefined>;
readonly statusHeading: _angular_core.InputSignal<string | undefined>;
readonly statusMessage: _angular_core.InputSignal<string | undefined>;
readonly statusSeverity: _angular_core.InputSignal<"info" | "success" | "warning" | "danger" | "caution" | "critical" | undefined>;
readonly syntaxHighlighter: _angular_core.InputSignal<((code: string, language?: string) => string | undefined) | undefined>;
readonly toolInputArgumentsLabel: _angular_core.InputSignal<TranslatableString_2>;
readonly toolOutputLabel: _angular_core.InputSignal<TranslatableString_2>;
readonly welcomeMessage: _angular_core.InputSignal<TranslatableString_2>;
}

// @public
export class SiAiMessageComponent {
constructor();
Expand All @@ -52,6 +123,14 @@ export class SiAiMessageComponent {
readonly secondaryActionsLabel: _angular_core.InputSignal<_siemens_element_translate_ng_translate.TranslatableString>;
}

// @public
export class SiAiWelcomeScreenComponent {
readonly categories: _angular_core.InputSignal<PromptCategory[]>;
readonly promptSelected: _angular_core.OutputEmitterRef<PromptSuggestion>;
readonly promptSuggestions: _angular_core.InputSignal<PromptSuggestion[] | Record<string, PromptSuggestion[]>>;
readonly selectedCategory: _angular_core.ModelSignal<string | undefined>;
}

// @public
export class SiAttachmentListComponent {
readonly alignment: _angular_core.InputSignal<"start" | "end">;
Expand All @@ -67,14 +146,17 @@ export class SiChatContainerComponent implements AfterContentInit, OnDestroy {
readonly colorVariant: _angular_core.InputSignal<string>;
focus(): void;
readonly noAutoScroll: _angular_core.InputSignalWithTransform<boolean, string | boolean>;
scrollToBottom(): void;
scrollToTop(): void;
}

// @public
export class SiChatContainerInputDirective {
}

// @public
export class SiChatInputComponent implements AfterViewInit {
export class SiChatInputComponent implements AfterViewInit, OnDestroy {
constructor();
readonly accept: _angular_core.InputSignal<string | undefined>;
readonly actionParam: _angular_core.InputSignal<any>;
readonly actions: _angular_core.InputSignal<MessageAction[]>;
Expand All @@ -88,11 +170,13 @@ export class SiChatInputComponent implements AfterViewInit {
focus(): void;
readonly interrupt: _angular_core.OutputEmitterRef<void>;
readonly interruptButtonLabel: _angular_core.InputSignal<TranslatableString_2>;
readonly interruptible: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly interruptible: _angular_core.ModelSignal<boolean>;
readonly label: _angular_core.InputSignal<string>;
readonly maxFileSize: _angular_core.InputSignal<number>;
readonly maxLength: _angular_core.InputSignal<number | undefined>;
readonly placeholder: _angular_core.InputSignal<TranslatableString_2>;
// (undocumented)
registerParent(sending: Signal<boolean>, interruptible: Signal<boolean>, sendListener?: () => void): void;
readonly removeAttachmentLabel: _angular_core.InputSignal<TranslatableString_2>;
readonly secondaryActions: _angular_core.InputSignal<MenuItem[]>;
readonly secondaryActionsLabel: _angular_core.InputSignal<TranslatableString_2>;
Expand All @@ -102,7 +186,7 @@ export class SiChatInputComponent implements AfterViewInit {
}>;
readonly sendButtonIcon: _angular_core.InputSignal<string>;
readonly sendButtonLabel: _angular_core.InputSignal<TranslatableString_2>;
readonly sending: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly sending: _angular_core.ModelSignal<boolean>;
readonly value: _angular_core.ModelSignal<string>;
}

Expand All @@ -121,6 +205,19 @@ export class SiChatMessageComponent {
readonly loading: _angular_core.InputSignal<boolean>;
}

// @public
export class SiToolMessageComponent {
readonly expandInputArguments: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly expandOutput: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly inputArguments: _angular_core.InputSignal<string | object | undefined>;
readonly inputArgumentsLabel: _angular_core.InputSignal<TranslatableString_2>;
readonly loading: _angular_core.InputSignalWithTransform<boolean, unknown>;
readonly name: _angular_core.InputSignal<string>;
readonly output: _angular_core.InputSignal<string | object | undefined>;
readonly outputLabel: _angular_core.InputSignal<TranslatableString_2>;
readonly toolIcon: _angular_core.InputSignal<string>;
}

// @public
export class SiUserMessageComponent {
constructor();
Expand All @@ -133,6 +230,31 @@ export class SiUserMessageComponent {
readonly secondaryActionsLabel: _angular_core.InputSignal<_siemens_element_translate_ng_translate.TranslatableString>;
}

// @public
export interface TemplateChatMessage {
template: TemplateRef<any>;
templateContext?: any;
}

// @public
export interface ToolChatMessage extends BaseChatMessage {
autoExpandInputArguments?: boolean;
autoExpandOutput?: boolean;
icon?: string;
inputArguments?: string | object;
name: string;
output?: string | object | Signal<string | object>;
type: 'tool';
}

// @public
export interface UserChatMessage extends BaseChatMessage {
actions?: MessageAction[];
attachments?: Attachment[];
content: string;
type: 'user';
}

// (No @packageDocumentation comment for this package)

```
24 changes: 21 additions & 3 deletions api-goldens/element-ng/markdown-renderer/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,34 @@

```ts

import * as _angular_core from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import * as i0 from '@angular/core';
import * as _siemens_element_translate_ng_translate from '@siemens/element-translate-ng/translate';
import { SiTranslateService } from '@siemens/element-translate-ng/translate';
import { TranslatableString } from '@siemens/element-translate-ng/translate-types';

// @public
export const getMarkdownRenderer: (sanitizer: DomSanitizer) => ((text: string) => Node);
export const getMarkdownRenderer: (sanitizer: DomSanitizer, options?: MarkdownRendererOptions, doc?: Document, isBrowser?: boolean) => ((text: string) => Node);

// @public (undocumented)
export interface MarkdownRendererOptions {
copyCodeButton?: TranslatableString;
downloadTableButton?: TranslatableString;
latexRenderer?: (latex: string, displayMode: boolean) => string | undefined;
syntaxHighlighter?: (code: string, language?: string) => string | undefined;
translateSync?: SiTranslateService['translateSync'];
}

// @public
export class SiMarkdownRendererComponent {
constructor();
readonly text: i0.InputSignal<string>;
readonly copyButtonLabel: _angular_core.InputSignal<_siemens_element_translate_ng_translate.TranslatableString>;
readonly disableCopyButton: _angular_core.InputSignal<boolean>;
readonly disableDownloadButton: _angular_core.InputSignal<boolean>;
readonly downloadButtonLabel: _angular_core.InputSignal<_siemens_element_translate_ng_translate.TranslatableString>;
readonly latexRenderer: _angular_core.InputSignal<((latex: string, displayMode: boolean) => string | undefined) | undefined>;
readonly syntaxHighlighter: _angular_core.InputSignal<((code: string, language?: string) => string | undefined) | undefined>;
readonly text: _angular_core.InputSignal<string>;
}

// (No @packageDocumentation comment for this package)
Expand Down
14 changes: 14 additions & 0 deletions api-goldens/element-ng/translate/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ export const provideSiTranslatableOverrides: (values: SiTranslatableKeys) => Pro

// @public (undocumented)
export interface SiTranslatableKeys {
// (undocumented)
'SI_AI_CHAT_CONTAINER.SECONDARY_ACTIONS'?: string;
// (undocumented)
'SI_AI_CHAT_CONTAINER.WELCOME_GREETING'?: string;
// (undocumented)
'SI_AI_CHAT_CONTAINER.WELCOME_MESSAGE'?: string;
// (undocumented)
'SI_AI_MESSAGE.SECONDARY_ACTIONS'?: string;
// (undocumented)
Expand Down Expand Up @@ -334,6 +340,10 @@ export interface SiTranslatableKeys {
// (undocumented)
'SI_MAIN_DETAIL_CONTAINER.BACK'?: string;
// (undocumented)
'SI_MARKDOWN_RENDERER.COPY_CODE'?: string;
// (undocumented)
'SI_MARKDOWN_RENDERER.DOWNLOAD'?: string;
// (undocumented)
'SI_NAVBAR.OPEN_LAUNCHPAD'?: string;
// (undocumented)
'SI_NAVBAR.TOGGLE_NAVIGATION'?: string;
Expand Down Expand Up @@ -432,6 +442,10 @@ export interface SiTranslatableKeys {
// (undocumented)
'SI_TOAST.CLOSE'?: string;
// (undocumented)
'SI_TOOL_MESSAGE.INPUT_ARGUMENTS'?: string;
// (undocumented)
'SI_TOOL_MESSAGE.OUTPUT'?: string;
// (undocumented)
'SI_TOUR.BACK'?: string;
// (undocumented)
'SI_TOUR.CLOSE'?: string;
Expand Down
10 changes: 10 additions & 0 deletions docs/components/chat-messages/chat-message.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,14 @@ The slots are:

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

## Tool Message Component

> **Note:** The tool message is currently experimental and may undergo changes in future releases.

The **si-tool-message** component is similar to the AI message component, but it includes a tool icon instead of an AI icon and is used to display tool calls and their results.

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

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

<si-docs-types></si-docs-types>
51 changes: 49 additions & 2 deletions docs/patterns/ai/ai-chat.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,20 +86,67 @@ These are not treated as errors and do not require a separate notification.

## Code ---

Use the chat container with the chat messages to build chat message interfaces.
The **si-ai-chat-container** component provides a complete AI chat interface, it should be inserted into a layout with fixed height, if no fixed height is provided, please set a height in pixels.

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

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

### Initial Screen

The AI chat container includes a built-in initial welcome screen component that displays when there are no messages. It can be slotted into the **si-chat-container** component. It accepts prompt suggestions as an input.

<si-docs-component example="si-chat-messages/si-ai-welcome-screen" height="600"></si-docs-component>

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

#### Prompt Suggestions

Prompt suggestions can be provided as either:

- A simple array of suggestions (no categories)
- A record mapping category labels to arrays of suggestions

When using a record, categories are automatically displayed as filter pills.

```typescript
// Simple array (no categories)
promptSuggestions = [
{ text: 'How do I optimize performance?' },
{ text: 'What are best practices?' }
];

// Record with categories
promptSuggestions = {
'All prompts': [{ text: 'How do I optimize performance?' }, { text: 'What are best practices?' }],
'Maintenance': [{ text: 'Schedule preventive maintenance' }, { text: 'Track equipment downtime' }]
};
```

### Base Chat Container

Use this base container with the chat messages to build custom chat message interfaces.

The **si-chat-container** component is a wrapper component, it has slots for
[chat messages](../../components/chat-messages/chat-message.md) and a
[chat input](../../components/chat-messages/chat-input.md).

The slots are:

- default -> chat messages or empty state
- default -> chat messages or initial screen (`si-welcome-screen`)
- `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" height="600"></si-docs-component>

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

### Initial Screen

When initially displaying a chat interface use the initial **si-welcome-screen** component that displays when there are no messages. It can be slotted into the **si-chat-container** component. It accepts prompt suggestions as an input.

<si-docs-component example="si-chat-messages/si-ai-welcome-screen" height="600"></si-docs-component>

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

<si-docs-types></si-docs-types>
Loading
Loading