You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
`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:
245
+
246
+
-`ReceiveConversationAssistantToken` + `ReceiveConversationAssistantComplete` to append a new assistant message directly to the current UI.
247
+
-`LoadSession` / `LoadInteraction` to reload the full transcript after you persist a deferred assistant message.
248
+
-`ReceiveNotification`, `UpdateNotification`, and `RemoveNotification` for transient system messages sent through `IChatNotificationSender`.
249
+
250
+
If you only want to notify the user about transfer state, typing, agent connection, or similar status, use `IChatNotificationSender`. If you want the external agent's reply to appear as a real assistant message in the transcript, save it to the prompt store and then either append it with `ReceiveConversationAssistantToken` / `ReceiveConversationAssistantComplete` or refresh the transcript with `LoadSession` / `LoadInteraction`.
251
+
:::
252
+
253
+
:::note
254
+
The active SignalR connection must join the session or interaction group before deferred webhook messages can be delivered in real time. Built-in CrestApps clients now do this automatically on startup by calling `LoadSession(existingSessionId)` or `LoadInteraction(existingItemId)` when the page already has an existing identifier. If you build a custom client, make sure it explicitly calls `StartSession`, `LoadSession`, or `LoadInteraction` after connecting so the current connection joins the correct group.
255
+
:::
256
+
243
257
### Step 4: Real-Time Communication via Persistent Relay (Alternative to Webhook)
244
258
245
259
While webhooks work well for many integration scenarios, some third-party platforms support persistent connections for real-time bidirectional communication. The external chat relay infrastructure keeps a connection open, enabling instant delivery of events like typing indicators, agent-connected notifications, wait-time updates, and messages — without polling or callback endpoints.
@@ -263,7 +277,7 @@ Understanding which interface handles each direction of communication is key:
263
277
|**User → External App**|`IExternalChatRelay.SendPromptAsync()`| Sends the user's chat message text to the external system via the relay connection. Called by the response handler when it receives a prompt. |
264
278
|**User → External App** (signals) |`IExternalChatRelay.SendSignalAsync()`| Sends user signals (e.g., thumbs up/down, user typing, feedback) to the external system. Called by `IChatNotificationActionHandler` implementations. |
265
279
|**External App → User** (notifications) |`IExternalChatRelayEventHandler.HandleEventAsync()`| Receives events from the relay's background listener and routes them to `IChatNotificationSender` for UI notifications (typing indicators, agent connected, wait times, connection status, session ended). |
266
-
|**External App → User** (messages) | Your `IExternalChatRelay` implementation | Receives message events from the external system and writes them to the prompt store via `IAIChatSessionPromptStore`, then notifies the SignalR group via `IHubContext<AIChatHub>`. This is handled directly in your relay implementation's `DispatchEventAsync` method. |
280
+
|**External App → User** (messages) | Your `IExternalChatRelay` implementation | Receives message events from the external system, writes them to the appropriate prompt store, then either appends the message with `ReceiveConversationAssistantToken` / `ReceiveConversationAssistantComplete` or reloads the transcript via `LoadSession` / `LoadInteraction`. |
267
281
268
282
:::note
269
283
`ExternalChatRelayEvent.EventType` is a **string**, not an enum. Well-known event types are defined as constants in `ExternalChatRelayEventTypes` (e.g., `ExternalChatRelayEventTypes.AgentTyping`). You can use **any custom string** for platform-specific events — just register a keyed `IExternalChatRelayNotificationBuilder` for your event type.
@@ -546,7 +560,7 @@ public sealed class GenesysWebSocketRelay : IExternalChatRelay
Copy file name to clipboardExpand all lines: src/CrestApps.OrchardCore.Documentations/docs/changelog/v2.0.0.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -157,6 +157,8 @@ A new suite of modules for multi-channel communication:
157
157
-**Chat Mode (Voice Input & Output)** — AI Chat and Chat Interactions now support a unified **Chat Mode** dropdown with three options: **Text Only** (default), **Audio Input** (microphone button for speech-to-text dictation), and **Conversation** (two-way voice interaction with auto-send and text-to-speech). Users can click a microphone button to record speech, which is streamed to the server via SignalR and transcribed using the configured `ISpeechToTextClient` (e.g., OpenAI Whisper, Azure OpenAI Whisper). The transcribed text appears in the input field for review before sending. For AI Chat, configure via the **Chat Mode** dropdown on AI Profiles or AI Profile Templates (visible only for Chat profile types). For Chat Interactions, configure via the site-level **Chat Mode** dropdown under **Settings → Artificial Intelligence → Chat Interactions**. Audio Input requires a **Default Speech-to-Text Deployment**; Conversation requires both STT and TTS default deployments.
158
158
-**Workflow Integration** — AI Completion tasks for Orchard Core Workflows.
159
159
- **Extensible Chat Response Handlers** — Chat prompts are now routed through a pluggable `IChatResponseHandler` abstraction. The built-in AI handler remains the default, but custom handlers can be registered to route prompts to external systems (e.g., live agent platforms like Genesys). Handlers support two modes: **streaming** (immediate response like AI) and **deferred** (response arrives later via webhook). Sessions and interactions have a `ResponseHandlerName` property that can be changed mid-conversation by AI functions for live agent handoff scenarios. AI Profiles and Templates support an **Initial Response Handler** setting to bypass AI entirely. The chat UI shows a handler selector when multiple handlers are registered. Custom handlers are **not supported in Conversation mode** — the resolver always returns the AI handler when `ChatMode.Conversation` is active because conversation mode requires the AI pipeline for speech-to-text and text-to-speech. See the [Response Handlers documentation](../ai/response-handlers.md) for details.
160
+
-**Response handler documentation corrected** — The deferred webhook examples no longer tell integrators to call `SendAsync("ReceiveMessage", ...)`, because there is no built-in `ReceiveMessage` SignalR client method in the current AI Chat or Chat Interaction UI. The docs now show the supported message-delivery APIs: persist the assistant reply, then either append it with `ReceiveConversationAssistantToken` / `ReceiveConversationAssistantComplete` or reload the transcript with `LoadSession` / `LoadInteraction`. Transient system messages such as typing, transfer, and agent-connected updates still use `IChatNotificationSender`. This is a documentation correction only; no runtime behavior changed.
161
+
-**Existing chat connections now rejoin deferred-response groups on startup** — When an AI Chat page or Chat Interaction page opened with an existing session or interaction already rendered, the browser could display the existing transcript without calling `LoadSession` or `LoadInteraction`. That meant the active SignalR connection never joined the corresponding group, so deferred webhook replies were persisted but not shown in real time until the user sent another message. The built-in JavaScript clients now automatically call `LoadSession(existingSessionId)` or `LoadInteraction(existingItemId)` on startup so the current connection joins the proper group immediately and deferred external replies can arrive live.
160
162
- **External Chat Relay** — New protocol-agnostic infrastructure for real-time bidirectional communication with third-party live-agent platforms. Unlike the webhook pattern (where the external system calls back into your application), an external chat relay maintains a persistent connection so events like typing indicators, agent-connected notifications, wait-time updates, connection-status signals, and messages flow instantly without polling. The relay interface (`IExternalChatRelay`) is transport-agnostic — implementations can use WebSocket, SSE, gRPC streaming, WebRTC data channels, message queues, event buses, or any other protocol. Key abstractions: `IExternalChatRelay` (persistent connection interface with `SendPromptAsync` for user→external, `SendSignalAsync` for feedback signals, and `IsConnectedAsync()` for status checks), `IExternalChatRelayManager` (singleton lifecycle manager), `IExternalChatRelayEventHandler` (event-to-notification router using keyed builder/handler pattern). Event types are strings (`ExternalChatRelayEventTypes` constants) for extensibility — custom event types are supported out of the box. Built-in event types include: `agent-typing`, `agent-stopped-typing`, `agent-connected`, `agent-disconnected`, `agent-reconnecting`, `connection-lost`, `connection-restored`, `message`, `wait-time-updated`, `session-ended`. The default handler resolves keyed `IExternalChatRelayNotificationBuilder` services per event type — each builder declares a `NotificationType` (used to create the notification) and populates properties via `Build`. The `IExternalChatRelayNotificationHandler` supports send, update, and remove operations. To add custom event types, register a keyed builder: `services.AddKeyedScoped<IExternalChatRelayNotificationBuilder, MyBuilder>("my-event")`. See the [Response Handlers documentation](../ai/response-handlers.md#step-4-real-time-communication-via-persistent-relay-alternative-to-webhook) for a complete implementation example.
161
163
- **Chat UI Notifications** — New extensible notification system that allows C# code to send transient system messages to the chat interface via SignalR — no JavaScript required. Built-in notifications include typing indicators ("Mike is typing…"), transfer status with estimated wait times and cancel buttons, agent-connected indicators, and conversation/session ended indicators. The `IChatNotificationSender` interface provides `SendAsync`, `UpdateAsync`, and `RemoveAsync` methods. Well-known notification types are available via `ChatNotificationTypes` and action names via `ChatNotificationActionNames`. Notifications are created using `new ChatNotification("type")` with the `Type` serving as the sole identifier — e.g., `new ChatNotification(ChatNotificationTypes.Typing)`. All user-facing strings accept `IStringLocalizer` for full localization support. `ChatNotification` requires a `type` parameter in its constructor and the `Type` setter is private. Notification system messages support action buttons that trigger server-side `IChatNotificationActionHandler` callbacks (registered as keyed services). Built-in action handlers: `cancel-transfer` (resets handler to AI) and `end-session` (closes the session). The system uses an extensible transport architecture — `IChatNotificationTransport` implementations are registered as keyed services by `ChatContextType`, allowing third-party modules to add notification support for custom hubs. Custom notification types with custom actions and styling are fully supported. See the [Chat UI Notifications documentation](../ai/chat-notifications.md) for details.
162
164
-**`SpeechTextSanitizer` Utility** — Extracted `SanitizeForSpeech` from both hub classes into a shared `SpeechTextSanitizer.Sanitize()` static method in `CrestApps.OrchardCore.AI.Core.Services`. This utility strips markdown formatting, code blocks, emoji, and other non-speech elements from text before passing it to a text-to-speech engine. Available for reuse by any module.
0 commit comments