Skip to content

Commit 70083c4

Browse files
authored
Add Label to the Assisant (#397)
1 parent dfa7e3a commit 70083c4

File tree

10 files changed

+48
-13
lines changed

10 files changed

+48
-13
lines changed

src/Abstractions/CrestApps.OrchardCore.AI.Abstractions/Models/AssistantMessageAppearance.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ namespace CrestApps.OrchardCore.AI.Models;
55
/// </summary>
66
public sealed class AssistantMessageAppearance
77
{
8+
/// <summary>
9+
/// Gets or sets the assistant role label shown in the chat UI.
10+
/// </summary>
11+
public string Label { get; set; }
12+
813
/// <summary>
914
/// Gets or sets the Font Awesome icon classes for the assistant message.
1015
/// </summary>

src/CrestApps.OrchardCore.Documentations/docs/ai/response-handlers.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ public sealed class GenesysResponseHandler : IChatResponseHandler
8383
// Optional: set this only when you want to override the default bot appearance.
8484
context.AssistantAppearance = new AssistantMessageAppearance
8585
{
86+
Label = "Mike",
8687
Icon = "fa-solid fa-headset",
8788
CssClass = "text-secondary",
8889
DisableStreamingAnimation = true,
@@ -108,7 +109,7 @@ public sealed class GenesysResponseHandler : IChatResponseHandler
108109

109110
Setting `context.AssistantAppearance` is optional. If you leave it unset, the chat UI keeps the default assistant/bot appearance.
110111

111-
Use `context.AssistantAppearance` only when your handler needs the streamed assistant message to render as something other than the default AI bot. This is especially useful for live-agent or transferred conversations where you want a headset icon, a different Bootstrap text color, or no streaming spinner/fade animation.
112+
Use `context.AssistantAppearance` only when your handler needs the streamed assistant message to render as something other than the default AI bot. This is especially useful for live-agent or transferred conversations where you want a custom label such as `Mike` or `Agent`, a headset icon, a different Bootstrap text color, or no streaming spinner/fade animation.
112113

113114
For streaming handlers, setting `context.AssistantAppearance` is enough because the hub sends that same value to the client and persists it on the assistant prompt with `assistantMessage.Put(context.AssistantAppearance)`.
114115

@@ -198,6 +199,7 @@ internal static class GenesysWebhookEndpoint
198199
};
199200
prompt.Put(new AssistantMessageAppearance
200201
{
202+
Label = "Mike",
201203
Icon = "fa-solid fa-headset",
202204
CssClass = "text-secondary",
203205
DisableStreamingAnimation = true,
@@ -286,7 +288,7 @@ public override void Configure(IApplicationBuilder app, IEndpointRouteBuilder ro
286288
:::important
287289
`ReceiveMessage` is **not** a built-in SignalR client method in the current chat UI, so calling `SendAsync("ReceiveMessage", ...)` will not update the browser. The built-in client methods are:
288290

289-
- `ReceiveConversationAssistantToken` + `ReceiveConversationAssistantComplete` to append a new assistant message directly to the current UI. `ReceiveConversationAssistantToken` accepts an optional `AssistantMessageAppearance` so services can override the icon, text color class, and streaming animation behavior.
291+
- `ReceiveConversationAssistantToken` + `ReceiveConversationAssistantComplete` to append a new assistant message directly to the current UI. `ReceiveConversationAssistantToken` accepts an optional `AssistantMessageAppearance` so services can override the visible assistant label, icon, text color class, and streaming animation behavior.
290292
- `LoadSession` / `LoadInteraction` to reload the full transcript after you persist a deferred assistant message.
291293
- `ReceiveNotification`, `UpdateNotification`, and `RemoveNotification` for transient system messages sent through `IChatNotificationSender`.
292294

src/CrestApps.OrchardCore.Documentations/docs/changelog/v2.0.0.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ This is one of the most important practical advancements in the release because
6363

6464
Response-handler-driven assistant messages can now also override the default bot presentation by supplying a custom icon, a CSS color class such as `text-secondary`, and an option to disable the streaming spinner animation. This makes live-agent and transferred conversations look like real people in the transcript instead of always appearing as the green AI bot.
6565

66-
The response-handler API now exposes this customization through the typed `ChatResponseHandlerContext.AssistantAppearance` property, and the hubs persist or return `AssistantMessageAppearance` directly through `Put(...)` and `As<AssistantMessageAppearance>()` instead of a separate helper. The response-handler documentation also now makes it explicit that deferred webhook implementations must persist `AssistantMessageAppearance` on the saved assistant prompt and include it in `ReceiveConversationAssistantToken(...)` if they want the appearance to survive reloads and be broadcast correctly to connected clients.
66+
The response-handler API now exposes this customization through the typed `ChatResponseHandlerContext.AssistantAppearance` property, and the hubs persist or return `AssistantMessageAppearance` directly through `Put(...)` and `As<AssistantMessageAppearance>()` instead of a separate helper. The response-handler documentation also now makes it explicit that deferred webhook implementations must persist `AssistantMessageAppearance` on the saved assistant prompt and include it in `ReceiveConversationAssistantToken(...)` if they want the appearance to survive reloads and be broadcast correctly to connected clients. `AssistantMessageAppearance` can also override the visible assistant label, so live-agent and external-handler replies can display names such as `Mike` or `Agent` instead of always showing `Assistant`.
6767

6868
See [Response Handlers](../ai/response-handlers.md) and [Chat UI Notifications](../ai/chat-notifications.md).
6969

src/Modules/CrestApps.OrchardCore.AI.Chat.Interactions/Assets/js/chat-interaction.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ window.chatInteractionManager = function () {
1010
downloadChartTitle: 'Download chart as image',
1111
downloadChartButtonText: 'Download',
1212
codeCopiedText: 'Copied!',
13+
assistantLabel: 'Assistant',
1314

1415
messageTemplate: `
1516
<div class="ai-chat-messages">
@@ -18,7 +19,7 @@ window.chatInteractionManager = function () {
1819
<div v-if="message.role === 'user'" class="ai-chat-msg-role ai-chat-msg-role-user">You</div>
1920
<div v-else-if="message.role !== 'indicator'" :class="getAssistantRoleClasses(message)">
2021
<span :class="getAssistantIconClasses(message, index)"><i :class="getAssistantIcon(message)"></i></span>
21-
Assistant
22+
{{ getAssistantLabel(message) }}
2223
</div>
2324
<div class="lh-base">
2425
<h4 v-if="message.title">{{ message.title }}</h4>
@@ -691,20 +692,26 @@ window.chatInteractionManager = function () {
691692
return null;
692693
}
693694

695+
var label = typeof appearance.label === 'string' ? appearance.label.trim() : '';
694696
var icon = typeof appearance.icon === 'string' ? appearance.icon.trim() : '';
695697
var cssClass = typeof appearance.cssClass === 'string' ? appearance.cssClass.trim() : '';
696698
var disableStreamingAnimation = !!appearance.disableStreamingAnimation;
697699

698-
if (!icon && !cssClass && !disableStreamingAnimation) {
700+
if (!label && !icon && !cssClass && !disableStreamingAnimation) {
699701
return null;
700702
}
701703

702704
return {
705+
label: label,
703706
icon: icon,
704707
cssClass: cssClass,
705708
disableStreamingAnimation: disableStreamingAnimation,
706709
};
707710
},
711+
getAssistantLabel(message) {
712+
var appearance = message ? this.normalizeAssistantAppearance(message.appearance) : null;
713+
return appearance && appearance.label ? appearance.label : defaultConfig.assistantLabel;
714+
},
708715
getAssistantRoleClasses(message) {
709716
var appearance = message ? this.normalizeAssistantAppearance(message.appearance) : null;
710717
var classes = ['ai-chat-msg-role'];

src/Modules/CrestApps.OrchardCore.AI.Chat.Interactions/wwwroot/scripts/chat-interaction.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ window.chatInteractionManager = function () {
3030
downloadChartTitle: 'Download chart as image',
3131
downloadChartButtonText: 'Download',
3232
codeCopiedText: 'Copied!',
33-
messageTemplate: "\n <div class=\"ai-chat-messages\">\n <div v-for=\"(message, index) in messages\" :key=\"index\" class=\"ai-chat-message-item\">\n <div>\n <div v-if=\"message.role === 'user'\" class=\"ai-chat-msg-role ai-chat-msg-role-user\">You</div>\n <div v-else-if=\"message.role !== 'indicator'\" :class=\"getAssistantRoleClasses(message)\">\n <span :class=\"getAssistantIconClasses(message, index)\"><i :class=\"getAssistantIcon(message)\"></i></span>\n Assistant\n </div>\n <div class=\"lh-base\">\n <h4 v-if=\"message.title\">{{ message.title }}</h4>\n <div v-html=\"message.htmlContent\"></div>\n <span class=\"message-buttons-container\" v-if=\"!isIndicator(message)\">\n <button class=\"btn btn-sm btn-link text-secondary p-0 button-message-toolbox\" @click=\"copyResponse(message.content)\" title=\"Click here to copy response to clipboard.\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n </span>\n </div>\n </div>\n </div>\n <div v-for=\"notification in notifications\" :key=\"'notif-' + notification.type\" class=\"ai-chat-notification\" :class=\"'ai-chat-notification-' + (notification.type || 'info') + ' ' + (notification.cssClass || '')\">\n <div class=\"ai-chat-notification-content\">\n <i v-if=\"notification.icon\" :class=\"notification.icon\" class=\"ai-chat-notification-icon\"></i>\n <span class=\"ai-chat-notification-text\">{{ notification.content }}</span>\n <button v-if=\"notification.dismissible\" class=\"btn btn-sm btn-link p-0 ms-2 ai-chat-notification-dismiss\" @click=\"dismissNotification(notification.type)\" title=\"Dismiss\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n <div v-if=\"notification.actions && notification.actions.length\" class=\"ai-chat-notification-actions\">\n <button v-for=\"action in notification.actions\" :key=\"action.name\" class=\"btn btn-sm\" :class=\"action.cssClass || 'btn-outline-secondary'\" @click=\"handleNotificationAction(notification.type, action.name)\">\n <i v-if=\"action.icon\" :class=\"action.icon\" class=\"me-1\"></i>\n {{ action.label }}\n </button>\n </div>\n </div>\n </div>\n ",
33+
assistantLabel: 'Assistant',
34+
messageTemplate: "\n <div class=\"ai-chat-messages\">\n <div v-for=\"(message, index) in messages\" :key=\"index\" class=\"ai-chat-message-item\">\n <div>\n <div v-if=\"message.role === 'user'\" class=\"ai-chat-msg-role ai-chat-msg-role-user\">You</div>\n <div v-else-if=\"message.role !== 'indicator'\" :class=\"getAssistantRoleClasses(message)\">\n <span :class=\"getAssistantIconClasses(message, index)\"><i :class=\"getAssistantIcon(message)\"></i></span>\n {{ getAssistantLabel(message) }}\n </div>\n <div class=\"lh-base\">\n <h4 v-if=\"message.title\">{{ message.title }}</h4>\n <div v-html=\"message.htmlContent\"></div>\n <span class=\"message-buttons-container\" v-if=\"!isIndicator(message)\">\n <button class=\"btn btn-sm btn-link text-secondary p-0 button-message-toolbox\" @click=\"copyResponse(message.content)\" title=\"Click here to copy response to clipboard.\">\n <i class=\"fa-solid fa-copy\"></i>\n </button>\n </span>\n </div>\n </div>\n </div>\n <div v-for=\"notification in notifications\" :key=\"'notif-' + notification.type\" class=\"ai-chat-notification\" :class=\"'ai-chat-notification-' + (notification.type || 'info') + ' ' + (notification.cssClass || '')\">\n <div class=\"ai-chat-notification-content\">\n <i v-if=\"notification.icon\" :class=\"notification.icon\" class=\"ai-chat-notification-icon\"></i>\n <span class=\"ai-chat-notification-text\">{{ notification.content }}</span>\n <button v-if=\"notification.dismissible\" class=\"btn btn-sm btn-link p-0 ms-2 ai-chat-notification-dismiss\" @click=\"dismissNotification(notification.type)\" title=\"Dismiss\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n <div v-if=\"notification.actions && notification.actions.length\" class=\"ai-chat-notification-actions\">\n <button v-for=\"action in notification.actions\" :key=\"action.name\" class=\"btn btn-sm\" :class=\"action.cssClass || 'btn-outline-secondary'\" @click=\"handleNotificationAction(notification.type, action.name)\">\n <i v-if=\"action.icon\" :class=\"action.icon\" class=\"me-1\"></i>\n {{ action.label }}\n </button>\n </div>\n </div>\n </div>\n ",
3435
indicatorTemplate: "\n <div class=\"ai-chat-msg-role ai-chat-msg-role-assistant\">\n <span class=\"ai-streaming-icon\"><i class=\"fa fa-robot\" style=\"display: inline-block;\"></i></span>\n Assistant\n </div>\n ",
3536
// Localizable strings
3637
untitledText: 'Untitled',
@@ -676,18 +677,24 @@ window.chatInteractionManager = function () {
676677
if (!appearance) {
677678
return null;
678679
}
680+
var label = typeof appearance.label === 'string' ? appearance.label.trim() : '';
679681
var icon = typeof appearance.icon === 'string' ? appearance.icon.trim() : '';
680682
var cssClass = typeof appearance.cssClass === 'string' ? appearance.cssClass.trim() : '';
681683
var disableStreamingAnimation = !!appearance.disableStreamingAnimation;
682-
if (!icon && !cssClass && !disableStreamingAnimation) {
684+
if (!label && !icon && !cssClass && !disableStreamingAnimation) {
683685
return null;
684686
}
685687
return {
688+
label: label,
686689
icon: icon,
687690
cssClass: cssClass,
688691
disableStreamingAnimation: disableStreamingAnimation
689692
};
690693
},
694+
getAssistantLabel: function getAssistantLabel(message) {
695+
var appearance = message ? this.normalizeAssistantAppearance(message.appearance) : null;
696+
return appearance && appearance.label ? appearance.label : defaultConfig.assistantLabel;
697+
},
691698
getAssistantRoleClasses: function getAssistantRoleClasses(message) {
692699
var appearance = message ? this.normalizeAssistantAppearance(message.appearance) : null;
693700
var classes = ['ai-chat-msg-role'];

src/Modules/CrestApps.OrchardCore.AI.Chat.Interactions/wwwroot/scripts/chat-interaction.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Modules/CrestApps.OrchardCore.AI.Chat/Assets/js/ai-chat.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ window.openAIChatManager = function () {
2222
<div v-if="message.role === 'user'" class="ai-chat-msg-role ai-chat-msg-role-user">{{ userLabel }}</div>
2323
<div v-else-if="message.role !== 'indicator'" :class="getAssistantRoleClasses(message)">
2424
<span :class="getAssistantIconClasses(message, index)"><i :class="getAssistantIcon(message)"></i></span>
25-
{{ assistantLabel }}
25+
{{ getAssistantLabel(message) }}
2626
</div>
2727
<div class="lh-base">
2828
<h4 v-if="message.title">{{ message.title }}</h4>
@@ -623,20 +623,26 @@ window.openAIChatManager = function () {
623623
return null;
624624
}
625625

626+
var label = typeof appearance.label === 'string' ? appearance.label.trim() : '';
626627
var icon = typeof appearance.icon === 'string' ? appearance.icon.trim() : '';
627628
var cssClass = typeof appearance.cssClass === 'string' ? appearance.cssClass.trim() : '';
628629
var disableStreamingAnimation = !!appearance.disableStreamingAnimation;
629630

630-
if (!icon && !cssClass && !disableStreamingAnimation) {
631+
if (!label && !icon && !cssClass && !disableStreamingAnimation) {
631632
return null;
632633
}
633634

634635
return {
636+
label: label,
635637
icon: icon,
636638
cssClass: cssClass,
637639
disableStreamingAnimation: disableStreamingAnimation,
638640
};
639641
},
642+
getAssistantLabel(message) {
643+
var appearance = message ? this.normalizeAssistantAppearance(message.appearance) : null;
644+
return appearance && appearance.label ? appearance.label : this.assistantLabel;
645+
},
640646
getAssistantRoleClasses(message) {
641647
var appearance = message ? this.normalizeAssistantAppearance(message.appearance) : null;
642648
var classes = ['ai-chat-msg-role'];

0 commit comments

Comments
 (0)