Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 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
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
102 changes: 99 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';
}

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

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

// @public
export interface MessageAction {
action: (actionParam: any, source: this) => void;
Expand All @@ -40,6 +60,55 @@ 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 welcomeMessage: _angular_core.InputSignal<TranslatableString_2>;
}

// @public
export class SiAiMessageComponent {
constructor();
Expand All @@ -52,6 +121,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 +144,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 +168,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 +184,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 Down Expand Up @@ -133,6 +215,20 @@ 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 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
10 changes: 10 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
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.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix comma splice and clarify the height instruction.

Line 89 contains a run-on sentence with comma-spliced clauses. Additionally, the instruction to "set a height in pixels" is vague—it should specify the mechanism (inline style, CSS class, etc.).

📝 Proposed fix
-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.
+The **si-ai-chat-container** component provides a complete AI chat interface and should be inserted into a layout with fixed height. If no fixed height is provided, set an explicit pixel height via inline styles or CSS.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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.
The **si-ai-chat-container** component provides a complete AI chat interface and should be inserted into a layout with fixed height. If no fixed height is provided, set an explicit pixel height via inline styles or CSS.
🤖 Prompt for AI Agents
In @docs/patterns/ai/ai-chat.md at line 89, Rewrite the sentence to remove the
comma splice and clarify how to set height: split into two sentences so the
first states that the si-ai-chat-container component provides a complete AI chat
interface, and the second instructs that it must be placed in a layout with a
fixed height; if no fixed height exists, require specifying a height in pixels
using an inline style or a CSS class (e.g., set style="height: 600px;" or apply
a CSS rule that sets height: 600px). Include the component name
"si-ai-chat-container" in the text for clarity.


<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