Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 22 additions & 51 deletions package-lock.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { FunctionComponent } from 'react';
import Message from '@patternfly/chatbot/dist/dynamic/Message';
import patternflyAvatar from './patternfly_avatar.jpg';

export const MessageWithMarkdownDeepThinkingExample: FunctionComponent = () => (
<Message
name="Bot"
role="bot"
avatar={patternflyAvatar}
content="This example shows how to use Markdown formatting in deep thinking content. Note the use of shouldRetainStyles to maintain proper formatting:"
deepThinking={{
shouldRetainStyles: true,
toggleContent: 'Show thinking',
subheading: '> Thought for 3 seconds',
isSubheadingMarkdown: true,
body: `I considered **multiple approaches** to answer your question:

1. *Direct response* - Quick but less comprehensive
2. *Research-based* - Thorough but time-consuming
3. **Balanced approach** - Combines speed and accuracy

I chose option 3 because it provides the best user experience.`,
isBodyMarkdown: true
}}
/>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { FunctionComponent } from 'react';
import Message from '@patternfly/chatbot/dist/dynamic/Message';
import patternflyAvatar from './patternfly_avatar.jpg';

export const MessageWithMarkdownToolCallExample: FunctionComponent = () => (
<Message
name="Bot"
role="bot"
avatar={patternflyAvatar}
content="This example shows how to use Markdown formatting in tool call content. Note the use of shouldRetainStyles to maintain proper formatting:"
toolCall={{
shouldRetainStyles: true,
titleText: "Calling 'data_processor'",
expandableContent: `**Processing data** from the following sources:

- Database query results
- API responses
- *Local cache*

\`\`\`json
{
"status": "processing",
"items": 42
}
\`\`\``,
isExpandableContentMarkdown: true
}}
/>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import { useState, FunctionComponent, MouseEvent as ReactMouseEvent } from 'react';
import Message from '@patternfly/chatbot/dist/dynamic/Message';
import patternflyAvatar from './patternfly_avatar.jpg';
import { CopyIcon, WrenchIcon } from '@patternfly/react-icons';
import {
Button,
DescriptionList,
DescriptionListDescription,
DescriptionListGroup,
DescriptionListTerm,
ExpandableSection,
ExpandableSectionVariant,
Flex,
FlexItem,
Label
} from '@patternfly/react-core';
export const MessageWithToolResponseExample: FunctionComponent = () => {
const [isExpanded, setIsExpanded] = useState(false);
const onToggle = (_event: ReactMouseEvent, isExpanded: boolean) => {
setIsExpanded(isExpanded);
};
const toolResponseBody = `The tool processed **3 database queries** and returned the following results:

1. User data - *42 records*
2. Transaction history - *128 records*
3. Analytics metrics - *15 data points*

\`\`\`json
{
"status": "success",
"execution_time": "0.12s"
}
\`\`\``;
return (
<Message
name="Bot"
role="bot"
avatar={patternflyAvatar}
content="This example shows how to use Markdown formatting in tool response content. Note the use of shouldRetainStyles to maintain proper formatting:"
toolResponse={{
shouldRetainStyles: true,
isToggleContentMarkdown: true,
toggleContent: '**Tool response:** data_query_tool',
isSubheadingMarkdown: true,
subheading: '> Completed in 0.12 seconds',
body: toolResponseBody,
isBodyMarkdown: true,
cardTitle: (
<Flex
alignItems={{
default: 'alignItemsCenter'
}}
justifyContent={{
default: 'justifyContentSpaceBetween'
}}
>
<FlexItem>
<Flex
direction={{
default: 'column'
}}
gap={{
default: 'gapXs'
}}
>
<FlexItem
grow={{
default: 'grow'
}}
>
<Flex
gap={{
default: 'gapXs'
}}
>
<FlexItem>
<WrenchIcon
style={{
color: 'var(--pf-t--global--icon--color--brand--default'
}}
/>
</FlexItem>
<FlexItem>toolName</FlexItem>
</Flex>
</FlexItem>
<FlexItem>
<Flex
gap={{
default: 'gapSm'
}}
style={{
fontSize: '12px',
fontWeight: '400'
}}
>
<FlexItem>Execution time:</FlexItem>
<FlexItem>0.12 seconds</FlexItem>
</Flex>
</FlexItem>
</Flex>
</FlexItem>
<FlexItem>
<Button
variant="plain"
aria-label="Copy tool response to clipboard"
icon={
<CopyIcon
style={{
color: 'var(--pf-t--global--icon--color--subtle)'
}}
/>
}
></Button>
</FlexItem>
</Flex>
),
cardBody: (
<>
<DescriptionList
style={{
'--pf-v6-c-description-list--RowGap': 'var(--pf-t--global--spacer--md)'
}}
aria-label="Tool response"
>
<DescriptionListGroup
style={{
'--pf-v6-c-description-list__group--RowGap': 'var(--pf-t--global--spacer--xs)'
}}
>
<DescriptionListTerm>Parameters</DescriptionListTerm>
<DescriptionListDescription>
<Flex
direction={{
default: 'column'
}}
>
<FlexItem>Optional description text for parameters.</FlexItem>
<FlexItem>
<Flex
gap={{
default: 'gapSm'
}}
>
<FlexItem>
<Label variant="outline" color="blue">
type
</Label>
</FlexItem>
<FlexItem>
<Label variant="outline" color="blue">
properties
</Label>
</FlexItem>
<FlexItem>
<Label variant="outline" color="blue">
label
</Label>
</FlexItem>
<FlexItem>
<Label variant="outline" color="blue">
label
</Label>
</FlexItem>
</Flex>
</FlexItem>
</Flex>
</DescriptionListDescription>
</DescriptionListGroup>
<DescriptionListGroup
style={{
'--pf-v6-c-description-list__group--RowGap': 'var(--pf-t--global--spacer--xs)'
}}
>
<DescriptionListTerm>Response</DescriptionListTerm>
<DescriptionListDescription>
<ExpandableSection
variant={ExpandableSectionVariant.truncate}
toggleTextExpanded="show less of response"
toggleTextCollapsed="show more of response"
onToggle={onToggle}
isExpanded={isExpanded}
style={{
'--pf-v6-c-expandable-section__content--Opacity': '1',
'--pf-v6-c-expandable-section__content--PaddingInlineStart': 0,
'--pf-v6-c-expandable-section__content--TranslateY': 0,
'--pf-v6-c-expandable-section--m-expand-top__content--TranslateY': 0
}}
>
Descriptive text about the tool response, including completion status, details on the data that was
processed, or anything else relevant to the use case.
</ExpandableSection>
</DescriptionListDescription>
</DescriptionListGroup>
</DescriptionList>
</>
)
}}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -349,3 +349,35 @@ An attachment dropzone allows users to upload files via drag and drop.
```js file="./FileDropZone.tsx"

```

## Examples with Markdown

The ChatBot supports Markdown formatting in several message components, allowing you to display rich, formatted content. This is particularly useful when you need to include code snippets, lists, emphasis, or other formatted text. The following examples demonstrate different ways you can use Markdown in a few of the ChatBot components, but this is not an exhaustive list of all Markdown customizations you can make.

To enable Markdown rendering, use the appropriate Markdown flag prop (such as `isBodyMarkdown`, `isSubheadingMarkdown`, or `isExpandableContentMarkdown`) depending on the component and content you're formatting.

**Important:** When using Markdown in these components, set `shouldRetainStyles: true` to retain the styling of the context the Markdown is used in. This ensures that Markdown content maintains the proper font sizes, colors, and other styling properties of its parent component. For example, Markdown passed into a toggle will retain the ChatBot toggle styling, while Markdown in a card body will maintain the appropriate card body styling. Without this prop, the Markdown may override the contextual styles and create inconsistencies with the rest of the ChatBot interface.

### Tool calls with Markdown

When displaying tool call information, you can use Markdown in the expandable content to provide formatted details about what the tool is processing. This is useful for showing structured data, code snippets, or formatted lists.

```ts file="./MessageWithMarkdownToolCall.tsx"

```

### Deep thinking with Markdown

Deep thinking content can include Markdown formatting in both the subheading and body to better communicate the LLM's reasoning process. This allows you to emphasize key points, structure thought processes with lists, or include other formatting.

```ts file="./MessageWithMarkdownDeepThinking.tsx"

```

### Tool responses with Markdown

Tool response cards support Markdown in multiple areas, including the toggle content, subheading, and body. Use `shouldRetainStyles: true` along with the appropriate Markdown flag props to ensure proper formatting and spacing.

```ts file="./MessageWithMarkdownToolResponse.tsx"

```
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,28 @@ import ExpandIcon from '@patternfly/react-icons/dist/esm/icons/expand-icon';
import OpenDrawerRightIcon from '@patternfly/react-icons/dist/esm/icons/open-drawer-right-icon';
import OutlinedWindowRestoreIcon from '@patternfly/react-icons/dist/esm/icons/outlined-window-restore-icon';
import { BarsIcon } from '@patternfly/react-icons/dist/esm/icons/bars-icon';
import { CopyIcon } from '@patternfly/react-icons/dist/esm/icons/copy-icon';
import { WrenchIcon } from '@patternfly/react-icons/dist/esm/icons/wrench-icon';
import {
Button,
DescriptionList,
DescriptionListDescription,
DescriptionListGroup,
DescriptionListTerm,
ExpandableSection,
ExpandableSectionVariant,
Flex,
FlexItem,
Label
} from '@patternfly/react-core';
import PFHorizontalLogoColor from '../UI/PF-HorizontalLogo-Color.svg';
import PFHorizontalLogoReverse from '../UI/PF-HorizontalLogo-Reverse.svg';
import PFIconLogoColor from '../UI/PF-IconLogo-Color.svg';
import PFIconLogoReverse from '../UI/PF-IconLogo-Reverse.svg';
import userAvatar from '../Messages/user_avatar.svg';
import patternflyAvatar from '../Messages/patternfly_avatar.jpg';
import { getTrackingProviders } from "@patternfly/chatbot/dist/dynamic/tracking";
import { useEffect,useCallback, useRef, useState, FunctionComponent, MouseEvent } from 'react';
import { useEffect,useCallback, useRef, useState, FunctionComponent, MouseEvent, MouseEvent as ReactMouseEvent } from 'react';
import saveAs from 'file-saver';

### Basic ChatBot
Expand Down
48 changes: 48 additions & 0 deletions packages/module/src/DeepThinking/DeepThinking.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,52 @@ describe('DeepThinking', () => {
expect(toggleButton).toHaveAttribute('aria-expanded', 'false');
expect(screen.getByText('Thinking content')).not.toBeVisible();
});

it('should render toggleContent as markdown when isToggleContentMarkdown is true', () => {
const toggleContent = '**Bold thinking**';
const { container } = render(<DeepThinking toggleContent={toggleContent} isToggleContentMarkdown />);
expect(container.querySelector('strong')).toBeTruthy();
expect(screen.getByText('Bold thinking')).toBeTruthy();
});

it('should not render toggleContent as markdown when isToggleContentMarkdown is false', () => {
const toggleContent = '**Bold thinking**';
const { container } = render(<DeepThinking toggleContent={toggleContent} />);
expect(container.querySelector('strong')).toBeFalsy();
expect(screen.getByText('**Bold thinking**')).toBeTruthy();
});

it('should render subheading as markdown when isSubheadingMarkdown is true', () => {
const subheading = '**Bold subheading**';
const { container } = render(<DeepThinking {...defaultProps} subheading={subheading} isSubheadingMarkdown />);
expect(container.querySelector('strong')).toBeTruthy();
expect(screen.getByText('Bold subheading')).toBeTruthy();
});

it('should not render subheading as markdown when isSubheadingMarkdown is false', () => {
const subheading = '**Bold subheading**';
render(<DeepThinking {...defaultProps} subheading={subheading} />);
expect(screen.getByText('**Bold subheading**')).toBeTruthy();
});

it('should render body as markdown when isBodyMarkdown is true', () => {
const body = '**Bold body**';
const { container } = render(<DeepThinking {...defaultProps} body={body} isBodyMarkdown />);
expect(container.querySelector('strong')).toBeTruthy();
expect(screen.getByText('Bold body')).toBeTruthy();
});

it('should not render body as markdown when isBodyMarkdown is false', () => {
const body = '**Bold body**';
render(<DeepThinking {...defaultProps} body={body} />);
expect(screen.getByText('**Bold body**')).toBeTruthy();
});

it('should pass markdownContentProps to MarkdownContent component', () => {
const body = '**Bold body**';
const { container } = render(
<DeepThinking {...defaultProps} body={body} isBodyMarkdown markdownContentProps={{ isPrimary: true }} />
);
expect(container.querySelector('.pf-m-primary')).toBeTruthy();
});
});
Loading
Loading