From a845b91a538549a664b726315e93932c2a3956db Mon Sep 17 00:00:00 2001 From: adilei Date: Thu, 14 May 2026 22:11:50 +0300 Subject: [PATCH] Add WebChat custom CSAT rating sample Intercepts Copilot Studio's built-in CSAT Adaptive Card and replaces it with a custom emoji-based React rating component, while sending the identical { rate: "N" } postBack payload back to the bot. Includes a mock DirectLine so the sample works standalone in a browser without connecting to a real agent. Co-Authored-By: Claude Opus 4.6 (1M context) --- ui/custom-ui/README.md | 1 + ui/custom-ui/webchat-csat/README.md | 69 +++++ ui/custom-ui/webchat-csat/index.html | 435 +++++++++++++++++++++++++++ 3 files changed, 505 insertions(+) create mode 100644 ui/custom-ui/webchat-csat/README.md create mode 100644 ui/custom-ui/webchat-csat/index.html diff --git a/ui/custom-ui/README.md b/ui/custom-ui/README.md index d613f198..f64b0c23 100644 --- a/ui/custom-ui/README.md +++ b/ui/custom-ui/README.md @@ -16,3 +16,4 @@ Build your own standalone chat frontend for Copilot Studio agents. | [assistant-ui/](./assistant-ui/) | React chat UI using the Assistant UI library | | [directline-js/](./directline-js/) | Custom chat UI using DirectLine API, no WebChat dependency | | [reasoning-display/](./reasoning-display/) | Display agent reasoning and citations | +| [webchat-csat/](./webchat-csat/) | Intercept CSAT card with custom emoji rating component (WebChat) | diff --git a/ui/custom-ui/webchat-csat/README.md b/ui/custom-ui/webchat-csat/README.md new file mode 100644 index 00000000..cbb9bd62 --- /dev/null +++ b/ui/custom-ui/webchat-csat/README.md @@ -0,0 +1,69 @@ +--- +title: Custom CSAT Rating +parent: Custom UI +grand_parent: UI +nav_order: 6 +--- + +# Custom CSAT Rating + +This sample shows how to intercept Copilot Studio's built-in CSAT (Customer Satisfaction) survey card and replace it with a custom React component, while sending the exact same payload back to the bot so the conversation flow continues normally. + +## What it does + +1. **Detects** the CSAT Adaptive Card using a heuristic (walks the card tree looking for `Action.Submit` nodes with a `rate` data key) +2. **Replaces** the default card with a modern emoji-based rating component +3. **Sends back** the same `{ "rate": "N" }` postBack payload the original card would have sent + +The sample includes a **mock Direct Line** so you can open `index.html` in a browser and see the custom CSAT component in action without connecting to a real agent. + +## How it works + +### CSAT detection heuristic + +Copilot Studio's `CSATQuestion` node sends an Adaptive Card with five clickable images, each wired to an `Action.Submit` with data like `{ "rate": "1" }` through `{ "rate": "5" }`. The detection function walks the entire card tree and counts how many `Action.Submit` actions carry a `rate` key. If it finds at least 3, it treats the card as a CSAT survey. + +{: .note } +> This is a heuristic. If your bot sends other Adaptive Cards whose submit actions also use a `rate` field, this function will match those too. Adjust the detection logic to suit your bot. + +### Activity middleware + +WebChat's `activityMiddleware` intercepts each activity before it is rendered. When the middleware detects a CSAT card, it returns the custom `CSATRatingActivity` React component instead of letting WebChat render the Adaptive Card: + +```javascript +const activityMiddleware = + () => next => ({ activity, ...otherArgs }) => { + if (isCSATCard(activity)) { + return () => ; + } + return next({ activity, ...otherArgs }); + }; +``` + +### Sending the rating back + +The custom component uses WebChat's `useSendPostBack()` hook to send the rating. The payload is identical to what the original Adaptive Card's `Action.Submit` would send: + +```javascript +sendPostBack({ rate: value }); // value is "1" through "5" +``` + +## Running the sample + +Open `index.html` in a browser. The mock Direct Line will simulate a bot greeting, and after you send any message, the bot will reply with a CSAT card that gets intercepted and replaced with the emoji rating component. + +### Connecting to a real agent + +Replace the mock Direct Line with a real one by fetching a token from your Copilot Studio token endpoint: + +```javascript +const res = await fetch('YOUR_TOKEN_ENDPOINT', { method: 'GET' }); +const { token } = await res.json(); + +// Replace directLine={createMockDirectLine()} with: +directLine={window.WebChat.createDirectLine({ token })} +``` + +## Based on + +- [BotFramework-WebChat password-input sample](https://github.com/microsoft/BotFramework-WebChat/tree/main/samples/05.custom-components/f.password-input) -- same `activityMiddleware` + `useSendPostBack` pattern diff --git a/ui/custom-ui/webchat-csat/index.html b/ui/custom-ui/webchat-csat/index.html new file mode 100644 index 00000000..25fbf799 --- /dev/null +++ b/ui/custom-ui/webchat-csat/index.html @@ -0,0 +1,435 @@ + + + + Web Chat: Custom CSAT Rating + + + + + + + + + + + + + + +
+ + +