diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithDeepThinking.tsx b/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithDeepThinking.tsx
new file mode 100644
index 000000000..2131b7f55
--- /dev/null
+++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithDeepThinking.tsx
@@ -0,0 +1,17 @@
+import { FunctionComponent } from 'react';
+import Message from '@patternfly/chatbot/dist/dynamic/Message';
+import patternflyAvatar from './patternfly_avatar.jpg';
+
+export const MessageWithDeepThinkingExample: FunctionComponent = () => (
+
+);
diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md b/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md
index caef87e76..b5a741073 100644
--- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md
+++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md
@@ -186,6 +186,16 @@ If you are using [model context protocol (MCP)](https://www.redhat.com/en/blog/m
```
+### Messages with deep thinking
+
+You can share details about the "thought process" behind an LLM's response, also known as deep thinking. To display a customizable, expandable card with these details, pass `deepThinking` to `` and provide a subheading (optional) and content body.
+
+Because this is an evolving area, this card content is currently fully customizable.
+
+```js file="./MessageWithDeepThinking.tsx"
+
+```
+
### Messages with quick start tiles
[Quick start](/extensions/quick-starts/) tiles can be added to messages via the `quickStarts` prop. Users can initiate the quick start from a link within the message tile.
diff --git a/packages/module/src/DeepThinking/DeepThinking.scss b/packages/module/src/DeepThinking/DeepThinking.scss
new file mode 100644
index 000000000..7a5f49f71
--- /dev/null
+++ b/packages/module/src/DeepThinking/DeepThinking.scss
@@ -0,0 +1,24 @@
+.pf-chatbot__deep-thinking {
+ --pf-v6-c-card--BorderColor: var(--pf-t--global--border--color--control--read-only);
+ overflow: unset;
+}
+
+.pf-chatbot__deep-thinking-expandable-section {
+ --pf-v6-c-expandable-section--Gap: var(--pf-t--global--spacer--xs);
+}
+
+.pf-chatbot__deep-thinking-section {
+ display: flex;
+ flex-direction: column;
+ gap: var(--pf-t--global--spacer--xs);
+}
+
+.pf-chatbot__deep-thinking-subheading {
+ font-size: var(--pf-t--global--font--size--body--sm);
+ font-weight: var(--pf-t--global--font--weight--body--default);
+ color: var(--pf-t--global--text--color--subtle);
+}
+
+.pf-chatbot__deep-thinking-body {
+ color: var(--pf-t--global--text--color--subtle);
+}
diff --git a/packages/module/src/DeepThinking/DeepThinking.test.tsx b/packages/module/src/DeepThinking/DeepThinking.test.tsx
new file mode 100644
index 000000000..03f333385
--- /dev/null
+++ b/packages/module/src/DeepThinking/DeepThinking.test.tsx
@@ -0,0 +1,61 @@
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import DeepThinking from './DeepThinking';
+
+describe('DeepThinking', () => {
+ const defaultProps = {
+ toggleContent: 'Show thinking'
+ };
+
+ it('should render with required props only', () => {
+ render();
+ expect(screen.getByText('Show thinking')).toBeTruthy();
+ });
+
+ it('should render subheading when provided', () => {
+ const subheading = 'Thought for 3 seconds';
+ render();
+ expect(screen.getByText(subheading)).toBeTruthy();
+ });
+
+ it('should render body content when provided', () => {
+ const body = "Here's why I think that";
+ render();
+ expect(screen.getByText(body)).toBeTruthy();
+ });
+
+ it('should render with complex content including React elements', () => {
+ const body = (
+
+
Complex body content
+
+
+ );
+
+ render();
+ expect(screen.getByText('Complex body content')).toBeTruthy();
+ expect(screen.getByText('Item 1')).toBeTruthy();
+ expect(screen.getByText('Item 2')).toBeTruthy();
+ });
+
+ it('should apply custom className from cardProps', () => {
+ const { container } = render(
+
+ );
+ expect(container.querySelector('.custom-tool-response-class')).toBeTruthy();
+ });
+
+ it('should pass through expandableSectionProps', () => {
+ render();
+ expect(document.querySelector('.custom-expandable-class')).toBeTruthy();
+ });
+
+ it('should not render subheading span when subheading is not provided', () => {
+ const { container } = render();
+ const subheadingContainer = container.querySelector('.pf-chatbot__tool-response-subheading');
+ expect(subheadingContainer).toBeFalsy();
+ });
+});
diff --git a/packages/module/src/DeepThinking/DeepThinking.tsx b/packages/module/src/DeepThinking/DeepThinking.tsx
new file mode 100644
index 000000000..9b485cb6b
--- /dev/null
+++ b/packages/module/src/DeepThinking/DeepThinking.tsx
@@ -0,0 +1,68 @@
+// ============================================================================
+// Deep Thinking
+// ============================================================================
+import {
+ Card,
+ CardBody,
+ CardBodyProps,
+ CardProps,
+ ExpandableSection,
+ ExpandableSectionProps
+} from '@patternfly/react-core';
+import { useState, type FunctionComponent } from 'react';
+
+export interface DeepThinkingProps {
+ /** Toggle content shown for expandable section */
+ toggleContent: React.ReactNode;
+ /** Additional props passed to expandable section */
+ expandableSectionProps?: Omit;
+ /** Subheading rendered inside expandable section */
+ subheading?: string;
+ /** Body text rendered inside expandable section */
+ body?: React.ReactNode | string;
+ /** Additional props passed to main card */
+ cardProps?: CardProps;
+ /** Additional props passed to main card body */
+ cardBodyProps?: CardBodyProps;
+}
+
+export const DeepThinking: FunctionComponent = ({
+ body,
+ cardProps,
+ expandableSectionProps,
+ subheading,
+ toggleContent,
+ cardBodyProps
+}: DeepThinkingProps) => {
+ const [isExpanded, setIsExpanded] = useState(true);
+
+ const onToggle = (_event: React.MouseEvent, isExpanded: boolean) => {
+ setIsExpanded(isExpanded);
+ };
+
+ return (
+
+
+
+
+ {subheading && (
+
+ {subheading}
+
+ )}
+ {body &&
{body}
}
+
+
+
+
+ );
+};
+
+export default DeepThinking;
diff --git a/packages/module/src/DeepThinking/index.ts b/packages/module/src/DeepThinking/index.ts
new file mode 100644
index 000000000..5b072f993
--- /dev/null
+++ b/packages/module/src/DeepThinking/index.ts
@@ -0,0 +1,3 @@
+export { default } from './DeepThinking';
+
+export * from './DeepThinking';
diff --git a/packages/module/src/Message/Message.test.tsx b/packages/module/src/Message/Message.test.tsx
index c64c191f3..c05753475 100644
--- a/packages/module/src/Message/Message.test.tsx
+++ b/packages/module/src/Message/Message.test.tsx
@@ -7,6 +7,7 @@ import { monitorSampleAppQuickStart } from './QuickStarts/monitor-sampleapp-quic
import { monitorSampleAppQuickStartWithImage } from './QuickStarts/monitor-sampleapp-quickstart-with-image';
import rehypeExternalLinks from '../__mocks__/rehype-external-links';
import { AlertActionLink } from '@patternfly/react-core';
+import { DeepThinkingProps } from '../DeepThinking';
const ALL_ACTIONS = [
{ label: /Good response/i },
@@ -145,6 +146,12 @@ const IMAGE = ``;
+const DEEP_THINKING: DeepThinkingProps = {
+ toggleContent: 'Show thinking',
+ subheading: 'Thought for 3 seconds',
+ body: "Here's why I said this."
+};
+
const ERROR = {
title: 'Could not load chat',
children: 'Wait a few minutes and check your network settings. If the issue persists: ',
@@ -1003,4 +1010,10 @@ describe('Message', () => {
// code block isn't rendering
expect(screen.queryByRole('button', { name: 'Copy code' })).toBeFalsy();
});
+ it('should render deep thinking section correctly', () => {
+ render();
+ expect(screen.getByRole('button', { name: /Show thinking/i })).toBeTruthy();
+ expect(screen.getByText('Thought for 3 seconds')).toBeTruthy();
+ expect(screen.getByText("Here's why I said this.")).toBeTruthy();
+ });
});
diff --git a/packages/module/src/Message/Message.tsx b/packages/module/src/Message/Message.tsx
index 9e28d4f1e..067c04475 100644
--- a/packages/module/src/Message/Message.tsx
+++ b/packages/module/src/Message/Message.tsx
@@ -50,6 +50,7 @@ import ErrorMessage from './ErrorMessage/ErrorMessage';
import MessageInput from './MessageInput';
import { rehypeMoveImagesOutOfParagraphs } from './Plugins/rehypeMoveImagesOutOfParagraphs';
import ToolResponse, { ToolResponseProps } from '../ToolResponse';
+import DeepThinking, { DeepThinkingProps } from '../DeepThinking';
export interface MessageAttachment {
/** Name of file attached to the message */
@@ -192,6 +193,8 @@ export interface MessageProps extends Omit, 'role'> {
reactMarkdownProps?: Options;
/** Props for tool response card */
toolResponse?: ToolResponseProps;
+ /** Props for deep thinking card */
+ deepThinking?: DeepThinkingProps;
}
export const MessageBase: FunctionComponent = ({
@@ -234,6 +237,7 @@ export const MessageBase: FunctionComponent = ({
isMarkdownDisabled,
reactMarkdownProps,
toolResponse,
+ deepThinking,
...props
}: MessageProps) => {
const [messageText, setMessageText] = useState(content);
@@ -381,6 +385,7 @@ export const MessageBase: FunctionComponent = ({
{renderMessage()}
{afterMainContent && <>{afterMainContent}>}
{toolResponse && }
+ {deepThinking && }
{!isLoading && sources && }
{quickStarts && quickStarts.quickStart && (