Skip to content

Commit b0f5be7

Browse files
committed
feat(ai-chat): add follow-up prompts
1 parent 2e47470 commit b0f5be7

10 files changed

Lines changed: 98 additions & 5 deletions

File tree

api-goldens/element-ng/chat-messages/index.api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ export class SiChatInputComponent implements AfterViewInit {
108108
readonly disclaimer: _angular_core.InputSignal<TranslatableString_2 | undefined>;
109109
readonly fileError: _angular_core.OutputEmitterRef<FileUploadError>;
110110
focus(): void;
111+
readonly followUpPrompts: _angular_core.InputSignal<string[]>;
112+
readonly followUpPromptSelected: _angular_core.OutputEmitterRef<string>;
111113
readonly interrupt: _angular_core.OutputEmitterRef<void>;
112114
readonly interruptButtonLabel: _angular_core.InputSignal<TranslatableString_2>;
113115
readonly interruptible: _angular_core.InputSignalWithTransform<boolean, unknown>;

docs/components/chat-messages/chat-input.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,33 @@ If multiple attachments are added, they wrap and stack within the input field to
6060

6161
![Chat input attachments](images/chat-input-attachments.png)
6262

63+
### Follow-up prompts
64+
65+
Follow-up prompts are suggested next actions displayed as pill buttons above the chat input after an AI response.
66+
They help users continue the conversation without having to formulate the next message from scratch.
67+
68+
- Prompts wrap to multiple lines when labels are long.
69+
- Selecting a prompt inserts its text into the input field so the user can review or edit before sending.
70+
- The prompt list is cleared when the user sends a message.
71+
6372
## Code ---
6473

6574
<si-docs-component example="si-chat-messages/si-chat-input"></si-docs-component>
6675

76+
### Follow-up prompts
77+
78+
Pass a `string[]` to `followUpPrompts` to display suggested prompts above the input.
79+
When the user clicks a prompt, its text is inserted into the input and `followUpPromptSelected` is emitted.
80+
Clear the list (set to `[]`) on send and repopulate it after each AI response.
81+
82+
```html
83+
<si-chat-input
84+
[followUpPrompts]="aiResponse?.followUpPrompts ?? []"
85+
(followUpPromptSelected)="onFollowUpSelected()"
86+
(send)="onSend($event)"
87+
/>
88+
```
89+
6790
<si-docs-api component="SiChatInputComponent"></si-docs-api>
6891

6992
<si-docs-types></si-docs-types>

docs/patterns/ai/ai-chat.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ AI chat is useful when users describe tasks in their own words and expect struct
2222
- Keep the conversation focused, avoid unnecessary messages.
2323
- Place the AI in a clearly defined visual space.
2424
- Output can be styled as needed using the [typography system](../../fundamentals/typography.md).
25+
- Use [follow-up prompts](../../components/chat-messages/chat-input.md#follow-up-prompts) after AI responses to help users continue the conversation without formulating the next message from scratch.
2526

2627
## Design ---
2728

Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 2 additions & 2 deletions
Loading

projects/element-ng/chat-messages/si-chat-input.component.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
@if (followUpPrompts().length) {
2+
<div class="follow-up-prompts d-flex flex-wrap gap-4 mb-4">
3+
@for (prompt of followUpPrompts(); track $index) {
4+
<button
5+
type="button"
6+
class="btn btn-secondary-ghost btn-sm follow-up-prompt-btn"
7+
[disabled]="disabled() || sending()"
8+
(click)="selectFollowUpPrompt(prompt)"
9+
>{{ prompt }}</button
10+
>
11+
}
12+
</div>
13+
}
14+
115
<div
216
class="input-wrapper border rounded-3 bg-body"
317
[class.drag-over]="dragOver"

projects/element-ng/chat-messages/si-chat-input.component.scss

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,16 @@
4848
.disclaimer-wrapper:empty {
4949
display: none;
5050
}
51+
52+
.follow-up-prompt-btn {
53+
--btn-font-weight: #{variables.$si-font-weight-normal};
54+
--btn-color: #{variables.$element-text-primary};
55+
--btn-color-hover: #{variables.$element-text-primary};
56+
--btn-color-active: #{variables.$element-text-primary};
57+
--btn-border-color: #{variables.$element-ui-3};
58+
padding-block: calc(#{map.get(variables.$spacers, 4)} - 1px);
59+
padding-inline: calc(#{map.get(variables.$spacers, 4)} - 1px);
60+
min-inline-size: 0;
61+
white-space: normal;
62+
text-align: start;
63+
}

projects/element-ng/chat-messages/si-chat-input.component.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,20 @@ export class SiChatInputComponent implements AfterViewInit {
275275
t(() => $localize`:@@SI_CHAT_INPUT.SECONDARY_ACTIONS:More actions`)
276276
);
277277

278+
/**
279+
* Suggested follow-up prompts to display as pill buttons above the chat input.
280+
* When a prompt is selected, its text is inserted into the input field.
281+
* Typically populated from AI response data and cleared on send.
282+
* @defaultValue []
283+
*/
284+
readonly followUpPrompts = input<string[]>([]);
285+
286+
/**
287+
* Emitted when the user selects a follow-up prompt.
288+
* The emitted value is the text of the selected prompt.
289+
*/
290+
readonly followUpPromptSelected = output<string>();
291+
278292
/**
279293
* Emitted when the user wants to send a message
280294
*/
@@ -326,6 +340,18 @@ export class SiChatInputComponent implements AfterViewInit {
326340
this.value.set(value);
327341
}
328342

343+
protected selectFollowUpPrompt(prompt: string): void {
344+
this.value.set(prompt);
345+
this.followUpPromptSelected.emit(prompt);
346+
this.focus();
347+
setTimeout(() => {
348+
const textarea = this.textInput();
349+
if (textarea?.nativeElement) {
350+
this.setTextareaHeight(textarea.nativeElement);
351+
}
352+
});
353+
}
354+
329355
protected onSend(): void {
330356
if (this.canSend()) {
331357
this.send.emit({

src/app/examples/si-chat-messages/si-chat-container.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858

5959
@if (!firstMessageSent() && messages().length) {
6060
<si-inline-notification
61-
class="mb-6 w-100 overflow-hidden"
61+
class="mb-4 w-100 overflow-hidden"
6262
severity="info"
6363
message="AI responses are for demonstration purposes."
6464
/>
@@ -76,8 +76,10 @@
7676
[maxFileSize]="5242880"
7777
[sending]="sending()"
7878
[interruptible]="loading() && !sending()"
79+
[followUpPrompts]="followUpPrompts()"
7980
[(value)]="inputValue"
8081
(send)="onMessageSent($event)"
82+
(followUpPromptSelected)="followUpPrompts.set([])"
8183
(interrupt)="onInterrupt()"
8284
(fileError)="onFileError($event)"
8385
>

src/app/examples/si-chat-messages/si-chat-container.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@ export class SampleComponent {
162162

163163
readonly loading = signal(false);
164164
readonly sending = signal(false);
165+
readonly followUpPrompts = signal<string[]>([
166+
'What are the risks if this insight is ignored?',
167+
'Show related insights from similar customer events.',
168+
'Summarize this insight in 2 bullet points for presentation.'
169+
]);
165170
readonly disabled = signal(false);
166171
readonly disableInterrupt = signal(false);
167172
readonly interrupting = signal(false);
@@ -265,6 +270,7 @@ export class SampleComponent {
265270
onMessageSent(event: { content: string; attachments: ChatInputAttachment[] }): void {
266271
this.logEvent(`Message sent: "${event.content}" with ${event.attachments.length} attachments`);
267272
this.firstMessageSent.set(true);
273+
this.followUpPrompts.set([]);
268274
this.messages.update(current => [
269275
...current,
270276
{
@@ -327,6 +333,12 @@ export class SampleComponent {
327333
]
328334
}
329335
]);
336+
this.followUpPrompts.set([
337+
'What are the risks if this insight is ignored?',
338+
'Show related insights from similar customer events.',
339+
'Summarize this insight in 2 bullet points for presentation.',
340+
'Generate a summary report'
341+
]);
330342
this.loading.set(false);
331343
}, 2000);
332344
}, 1000);

0 commit comments

Comments
 (0)