Skip to content

Commit

Permalink
feat: add generated caution text to the messages with corresponding k…
Browse files Browse the repository at this point in the history
…eywords (#5298)

* feat: add generated text to the messages with corresponding keywords

* Changelog

* Snapshots

* Update multiline message snapshot

* Update packages/component/src/Attachment/Text/TextContent.tsx

Co-authored-by: William Wong <[email protected]>

---------

Co-authored-by: William Wong <[email protected]>
  • Loading branch information
OEvgeny and compulim authored Sep 16, 2024
1 parent 54a6a40 commit 4b9342a
Show file tree
Hide file tree
Showing 65 changed files with 103 additions and 28 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/
- Moved from `[email protected]` to `@redux/[email protected]`, in PR [#5292](https://github.com/microsoft/BotFramework-WebChat/pull/5292), by [@compulim](https://github.com/compulim)
- Enhanced the visual presentation of the Fluent theme copilot variant, in PR [#5293](https://github.com/microsoft/BotFramework-WebChat/pull/5293), by [@OEvgeny](https://github.com/OEvgeny)
- Refactored spacing and layout for copilot variant in Fluent theme, improving visual consistency, in PR [#5296](https://github.com/microsoft/BotFramework-WebChat/pull/5296), by [@OEvgeny](https://github.com/OEvgeny)
- Added a content generated badge to AI-generated messages, in PR [#5298](https://github.com/microsoft/BotFramework-WebChat/pull/5298), by [@OEvgeny](https://github.com/OEvgeny)

### Fixed

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 9 additions & 8 deletions __tests__/html/fluentTheme/side-by-side.wide.dark.html
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
const timestamp = () => new Date(timestampStart += 100).toISOString();

const [leftTranscriptIndex = 0, rightTranscriptIndex = 1] = new URLSearchParams(location.search).getAll('transcript');
const [leftVariant = 'fluent', rightVariant = 'copilot'] = new URLSearchParams(location.search).getAll('variant');

const sendBoxIndexes = new URLSearchParams(location.search).getAll('focus');

Expand All @@ -97,7 +98,6 @@
{
timestamp: timestamp(),
from: { role: 'bot' },
entities: [aiMessageEntity],
id: '1.0',
text: `Great! Your copilot will maintain a polite, friendly, professional, and fun while assisting team members with onboarding tasks.
Are there any topics or tasks this copilot shouldn’t help with or talk about?`,
Expand All @@ -114,7 +114,6 @@
{
timestamp: timestamp(),
from: { role: 'bot' },
entities: [aiMessageEntity],
id: '2.0',
text: 'Is there a website or datasource that has knowledge that will be necessary for Onboarding Buddy?',
type: 'message'
Expand All @@ -130,7 +129,6 @@
{
timestamp: timestamp(),
from: { role: 'bot' },
entities: [aiMessageEntity],
id: '3.0',
text: `https://sharepoint.contoso.com/team/ has been added as a Knowledge source.
You can preview your extension at any time.`,
Expand Down Expand Up @@ -271,8 +269,9 @@
from: { role: 'bot' },
entities: [aiMessageEntity],
id: "1.1",
text: "Welcome to the team! I'd be happy to assist you with your onboarding. Let's start with the essentials. Have you received your welcome package yet?",
type: "message"
text: "Welcome to the team! \n\n I'd be happy to assist you with your onboarding. Let's start with the essentials. Have you received your welcome package yet?",
type: "message",
textFormat: "plain"
},
{
timestamp: timestamp(),
Expand Down Expand Up @@ -550,7 +549,7 @@
colorNeutralForeground5: '#424242'
}}>
<div className="webchat-left">
<FluentThemeProvider variant="fluent">
<FluentThemeProvider variant={leftVariant}>
<ReactWebChat
directLine={leftDirectLine}
store={leftStore}
Expand All @@ -564,7 +563,7 @@
</FluentThemeProvider>
</div>
<div className="webchat-right">
<FluentThemeProvider variant="copilot">
<FluentThemeProvider variant={rightVariant}>
<ReactWebChat
directLine={rightDirectLine}
store={rightStore}
Expand Down Expand Up @@ -599,7 +598,9 @@
await host.snapshot();
await host.sendShiftTab();
await host.snapshot();
await host.sendKeys('ARROW_UP');
await host.sendKeys('HOME');
await host.snapshot();
await host.sendKeys('END');
await host.snapshot();
await host.sendKeys('ARROW_UP');
await host.snapshot();
Expand Down
2 changes: 2 additions & 0 deletions __tests__/html/fluentTheme/side-by-side.wide.dark.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ describe('Fluent theme applied', () => {
runHTML('fluentTheme/side-by-side.wide.dark?transcript=0&transcript=3'));
test('side by side left - transcript, right - streaming', () =>
runHTML('fluentTheme/side-by-side.wide.dark?transcript=0&transcript=4'));
test('side by side left - fluent, right - fluent', () =>
runHTML('fluentTheme/side-by-side.wide.dark?transcript=0&transcript=2&focus=1&variant=fluent&variant=fluent'));
});
});
18 changes: 9 additions & 9 deletions __tests__/html/fluentTheme/side-by-side.wide.html
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
const timestamp = () => new Date(timestampStart += 100).toISOString();

const [leftTranscriptIndex = 0, rightTranscriptIndex = 1] = new URLSearchParams(location.search).getAll('transcript');
const [leftVariant = 'fluent', rightVariant = 'copilot'] = new URLSearchParams(location.search).getAll('variant');

const sendBoxIndexes = new URLSearchParams(location.search).getAll('focus');

Expand All @@ -106,7 +107,6 @@
{
timestamp: timestamp(),
from: { role: 'bot' },
entities: [aiMessageEntity],
id: '1.0',
text: `Great! Your copilot will maintain a polite, friendly, professional, and fun while assisting team members with onboarding tasks.
Are there any topics or tasks this copilot shouldn’t help with or talk about?`,
Expand All @@ -123,7 +123,6 @@
{
timestamp: timestamp(),
from: { role: 'bot' },
entities: [aiMessageEntity],
id: '2.0',
text: 'Is there a website or datasource that has knowledge that will be necessary for Onboarding Buddy?',
type: 'message'
Expand All @@ -139,7 +138,6 @@
{
timestamp: timestamp(),
from: { role: 'bot' },
entities: [aiMessageEntity],
id: '3.0',
text: `https://sharepoint.contoso.com/team/ has been added as a Knowledge source.
You can preview your extension at any time.`,
Expand All @@ -165,7 +163,6 @@
{
timestamp: timestamp(),
from: { role: 'bot' },
entities: [aiMessageEntity],
suggestedActions: {
actions: [
{
Expand Down Expand Up @@ -281,8 +278,9 @@
from: { role: 'bot' },
entities: [aiMessageEntity],
id: "1.1",
text: "Welcome to the team! I'd be happy to assist you with your onboarding. Let's start with the essentials. Have you received your welcome package yet?",
type: "message"
text: "Welcome to the team! \n\n I'd be happy to assist you with your onboarding. Let's start with the essentials. Have you received your welcome package yet?",
type: "message",
textFormat: "plain"
},
{
timestamp: timestamp(),
Expand Down Expand Up @@ -532,7 +530,7 @@

const App = () => <>
<div className="webchat-left">
<FluentThemeProvider variant="fluent">
<FluentThemeProvider variant={leftVariant}>
<ReactWebChat
directLine={leftDirectLine}
store={leftStore}
Expand All @@ -546,7 +544,7 @@
</FluentThemeProvider>
</div>
<div className="webchat-right">
<FluentThemeProvider variant="copilot">
<FluentThemeProvider variant={rightVariant}>
<ReactWebChat
directLine={rightDirectLine}
store={rightStore}
Expand Down Expand Up @@ -582,7 +580,9 @@
await host.snapshot();
await host.sendShiftTab();
await host.snapshot();
await host.sendKeys('ARROW_UP');
await host.sendKeys('HOME');
await host.snapshot();
await host.sendKeys('END');
await host.snapshot();
await host.sendKeys('ARROW_UP');
await host.snapshot();
Expand Down
2 changes: 2 additions & 0 deletions __tests__/html/fluentTheme/side-by-side.wide.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ describe('Fluent theme applied', () => {
runHTML('fluentTheme/side-by-side.wide?transcript=0&transcript=3'));
test('side by side left - transcript, right - streaming', () =>
runHTML('fluentTheme/side-by-side.wide?transcript=0&transcript=4'));
test('side by side left - fluent, right - fluent', () =>
runHTML('fluentTheme/side-by-side.wide?transcript=0&transcript=2&focus=1&variant=fluent&variant=fluent'));
});
38 changes: 32 additions & 6 deletions packages/component/src/Attachment/Text/TextContent.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,56 @@
import React, { type FC, memo } from 'react';
import React, { memo, useMemo } from 'react';
import classNames from 'classnames';
import { hooks } from 'botframework-webchat-api';
import { type WebChatActivity } from 'botframework-webchat-core';

import isAIGeneratedActivity from './private/isAIGeneratedActivity';
import MarkdownTextContent from './private/MarkdownTextContent';
import PlainTextContent from './private/PlainTextContent';
import CustomPropertyNames from '../../Styles/CustomPropertyNames';
import useStyleToEmotionObject from '../../hooks/internal/useStyleToEmotionObject';
import useRenderMarkdownAsHTML from '../../hooks/useRenderMarkdownAsHTML';

import { type WebChatActivity } from 'botframework-webchat-core';
const { useLocalizer } = hooks;

type Props = Readonly<{
activity: WebChatActivity;
contentType?: string;
text: string;
}>;

const TextContent: FC<Props> = memo(({ activity, contentType = 'text/plain', text }: Props) => {
const generatedBadgeStyle = {
'&.webchat__text-content__generated-badge': {
color: `var(${CustomPropertyNames.ColorSubtle})`,
fontSize: `var(${CustomPropertyNames.FontSizeSmall})`
}
};

const TextContent = memo(({ activity, contentType = 'text/plain', text }: Props) => {
const supportMarkdown = !!useRenderMarkdownAsHTML('message activity');
const localize = useLocalizer();
const generatedBadgeClassName = useStyleToEmotionObject()(generatedBadgeStyle) + '';

const generatedBadge = useMemo(
() =>
isAIGeneratedActivity(activity) && (
<div className={classNames('webchat__text-content__generated-badge', generatedBadgeClassName)}>
{localize('ACTIVITY_CONTENT_CAUTION')}
</div>
),
[activity, generatedBadgeClassName, localize]
);

return text ? (
contentType === 'text/markdown' && supportMarkdown ? (
<MarkdownTextContent activity={activity} markdown={text} />
<MarkdownTextContent activity={activity} markdown={text}>
{generatedBadge}
</MarkdownTextContent>
) : (
<PlainTextContent text={text} />
<PlainTextContent text={text}>{generatedBadge}</PlainTextContent>
)
) : null;
});

TextContent.defaultProps = { contentType: 'text/plain' };
TextContent.displayName = 'TextContent';

export default TextContent;
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import classNames from 'classnames';
import type { Definition } from 'mdast';
import { fromMarkdown } from 'mdast-util-from-markdown';
import React, { memo, useCallback, useMemo, type MouseEventHandler } from 'react';
import React, { memo, useCallback, useMemo, type MouseEventHandler, type ReactNode } from 'react';
import { useRefFrom } from 'use-ref-from';

import { LinkDefinitionItem, LinkDefinitions } from '../../../LinkDefinition/index';
Expand All @@ -35,14 +35,15 @@ type Entry = {

type Props = Readonly<{
activity: WebChatActivity;
children?: ReactNode | undefined;
markdown: string;
}>;

function isCitationURL(url: string): boolean {
return onErrorResumeNext(() => new URL(url))?.protocol === 'cite:';
}

const MarkdownTextContent = memo(({ activity, markdown }: Props) => {
const MarkdownTextContent = memo(({ activity, children, markdown }: Props) => {
const [
{
citationModalDialog: citationModalDialogStyleSet,
Expand Down Expand Up @@ -212,6 +213,7 @@ const MarkdownTextContent = memo(({ activity, markdown }: Props) => {
dangerouslySetInnerHTML={dangerouslySetInnerHTML}
onClick={handleClick}
/>
{children}
{!!entries.length && (
<LinkDefinitions<MessageSensitivityLabelProps>
accessoryComponentType={messageSensitivityLabelProps && MessageSensitivityLabel}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import classNames from 'classnames';
import React, { type FC, Fragment, memo } from 'react';
import React, { Fragment, memo, type ReactNode } from 'react';

import useStyleSet from '../../../hooks/useStyleSet';

type Props = Readonly<{ text: string }>;
type Props = Readonly<{ children?: ReactNode | undefined; text: string }>;

const PlainTextContent: FC<Props> = memo(({ text }: Props) => {
const PlainTextContent = memo(({ children, text }: Props) => {
const [{ textContent: textContentStyleSet }] = useStyleSet();

return (
Expand All @@ -18,6 +18,13 @@ const PlainTextContent: FC<Props> = memo(({ text }: Props) => {
{line.trim()}
</p>
))}
{children && (
<div
className={classNames('webchat__text-content', 'webchat__text-content--children', textContentStyleSet + '')}
>
{children}
</div>
)}
</Fragment>
);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { getOrgSchemaMessage, type WebChatActivity } from 'botframework-webchat-core';

export default function isAIGeneratedActivity(activity: undefined | WebChatActivity) {
return !!(activity && getOrgSchemaMessage(activity?.entities || [])?.keywords?.includes('AIGeneratedContent'));
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@
&:has(:global(.webchat__bubble--from-user)) :global(.webchat__bubble) {
margin-block-end: var(--webchat-spacingVerticalM);
}

/* Hide generated badge as it is in the copilot header */
:global(.webchat__bubble .webchat__text-content .webchat__text-content__generated-badge) {
display: none;
}
}

/* Decorator copilot variant which has bot message */
Expand Down Expand Up @@ -147,6 +152,30 @@
min-height: auto;
padding-block: var(--webchat__bubble--block-padding);
padding-inline: var(--webchat__bubble--inline-padding);

&:empty {
padding-block-end: 0;
}

+:global(.webchat__text-content) {
margin-top: calc(var(--webchat__bubble--block-padding) * -1);
}
}

/* Message bubble text content generated badge */
:global(.webchat-fluent) .activity-decorator :global(.webchat__stacked-layout .webchat__bubble .webchat__text-content__generated-badge) {
align-items: center;
align-self: flex-start;
background-color: var(--webchat-colorNeutralBackground5);
border-radius: var(--webchat-borderRadiusMedium);
box-sizing: border-box;
color: var(--webchat-colorNeutralForeground3);
cursor: default;
display: inline-flex;
font-size: var(--webchat-fontSizeBase100);
height: 16px;
line-height: var(--webchat-lineHeightBase100);
padding-inline: var(--webchat-spacingHorizontalXS);
}

/* Message bubble attachment content */
Expand Down

0 comments on commit 4b9342a

Please sign in to comment.