Skip to content

Commit b5769b4

Browse files
authored
Merge pull request #785 from thatblindgeye/iss679_sequencing
feat(Messages): allowed more composable structures
2 parents 23d73f4 + ccb4163 commit b5769b4

30 files changed

+687
-89
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { FunctionComponent } from 'react';
2+
import Message, {
3+
ErrorMessage,
4+
MessageAndActions,
5+
MessageAttachmentItem,
6+
MessageAttachmentsContainer,
7+
MessageLoading
8+
} from '@patternfly/chatbot/dist/dynamic/Message';
9+
import MarkdownContent from '@patternfly/chatbot/dist/dynamic/MarkdownContent';
10+
import ToolCall from '@patternfly/chatbot/dist/dynamic/ToolCall';
11+
import ToolResponse from '@patternfly/chatbot/dist/dynamic/ToolResponse';
12+
import FileDetailsLabel from '@patternfly/chatbot/dist/dynamic/FileDetailsLabel';
13+
import ResponseActions, { ResponseActionsGroups } from '@patternfly/chatbot/dist/dynamic/ResponseActions';
14+
import patternflyAvatar from './patternfly_avatar.jpg';
15+
import userAvatar from './user_avatar.svg';
16+
17+
const handlePositiveResponse = () => {
18+
// Handle positive response
19+
};
20+
21+
const handleNegativeResponse = () => {
22+
// Handle negative response
23+
};
24+
25+
const handleCopy = () => {
26+
// Handle copy action
27+
};
28+
29+
const handleDownload = () => {
30+
// Handle download action
31+
};
32+
33+
const handleListen = () => {
34+
// Handle listen action
35+
};
36+
37+
export const MessageWithCustomStructure: FunctionComponent = () => (
38+
<>
39+
<Message name="Bot" role="bot" avatar={patternflyAvatar}>
40+
<MessageAndActions>
41+
<MarkdownContent
42+
content={`This is a basic message with a more custom, fine-tuned structure. You can pass markdown to the MarkdownContent component, such as **bold content with double asterisks** or _italic content with single underscores_.`}
43+
/>
44+
<ToolCall titleText="Calling 'awesome_tool'" loadingText="Loading 'awesome_tool'" isLoading={true} />
45+
<ToolResponse
46+
toggleContent="Tool response: fetch_user_data"
47+
subheading="Executed in 0.3 seconds"
48+
body="Successfully retrieved user data from the database."
49+
cardTitle="User Data Response"
50+
cardBody="The tool returned 150 user records matching the specified criteria."
51+
/>
52+
<ErrorMessage title="An issue placed within this custom structure." />
53+
<MarkdownContent
54+
isMarkdownDisabled
55+
textComponent={`You can also pass plain text without markdown to the MarkdownContent component by passing the isMarkdownDisabled prop. Optionally, you can also use the textComponent prop instead of content.`}
56+
/>
57+
<ToolCall titleText="Calling 'more_awesome_tool'" loadingText="Loading 'more_awesome_tool'" isLoading={true} />
58+
<ToolCall titleText="Calling 'even_more_awesome_tool'" loadingText="Loading 'even_more_awesome_tool'" />
59+
<MarkdownContent content={`You can even place a message loading state in the middle of a message:`} />
60+
<MessageLoading loadingWord="Loading something in the middle of a custom structured message" />
61+
<ResponseActionsGroups>
62+
<ResponseActions
63+
actions={{
64+
positive: { onClick: handlePositiveResponse, ariaLabel: 'Good response' },
65+
negative: { onClick: handleNegativeResponse, ariaLabel: 'Bad response' }
66+
}}
67+
persistActionSelection={true}
68+
/>
69+
<ResponseActions
70+
actions={{
71+
copy: { onClick: handleCopy, ariaLabel: 'Copy' },
72+
download: { onClick: handleDownload, ariaLabel: 'Download' }
73+
}}
74+
persistActionSelection={false}
75+
/>
76+
<ResponseActions
77+
actions={{
78+
listen: { onClick: handleListen, ariaLabel: 'Listen' }
79+
}}
80+
persistActionSelection={true}
81+
/>
82+
</ResponseActionsGroups>
83+
</MessageAndActions>
84+
</Message>
85+
<Message name="User" role="user" avatar={userAvatar}>
86+
<MessageAndActions>
87+
<MarkdownContent content="This message is in the MessageAndActions container, and the file attachments below are in their own separate MessageAttachmentsContainer!" />
88+
</MessageAndActions>
89+
<MessageAttachmentsContainer>
90+
<MessageAttachmentItem>
91+
<FileDetailsLabel fileName="project-report.pdf" />
92+
</MessageAttachmentItem>
93+
<MessageAttachmentItem>
94+
<FileDetailsLabel fileName="data-analysis.csv" />
95+
</MessageAttachmentItem>
96+
<MessageAttachmentItem>
97+
<FileDetailsLabel fileName="presentation-slides.pptx" />
98+
</MessageAttachmentItem>
99+
</MessageAttachmentsContainer>
100+
</Message>
101+
</>
102+
);

packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,40 @@ propComponents:
1414
[
1515
'AttachMenu',
1616
'AttachmentEdit',
17-
'FileDetailsProps',
18-
'FileDetailsLabelProps',
1917
'FileDropZone',
20-
'PreviewAttachment',
2118
'Message',
22-
'MessageExtraContent',
23-
'PreviewAttachment',
19+
'ErrorMessage',
20+
'MessageLoadingProps',
21+
'MessageInputProps',
22+
'MessageAndActionsProps',
23+
'MarkdownContent',
24+
'QuickResponseProps',
25+
'QuickStartTileProps',
26+
'UserFeedback',
27+
'UserFeedbackComplete',
28+
'DeepThinking',
29+
'ToolCall',
30+
'ToolResponse',
31+
'SourcesCard',
32+
'ResponseActionsGroupsProps',
33+
'ResponseActionProps',
2434
'ActionProps',
25-
'SourcesCardProps',
26-
'UserFeedbackProps',
27-
'UserFeedbackCompleteProps',
28-
'QuickResponseProps'
35+
'MessageAttachmentsContainerProps',
36+
'MessageAttachmentItemProps',
37+
'FileDetailsProps',
38+
'FileDetailsLabelProps',
39+
'MessageExtraContent',
40+
'PreviewAttachment'
2941
]
3042
sortValue: 3
3143
---
3244

33-
import Message from '@patternfly/chatbot/dist/dynamic/Message';
45+
import Message, { ErrorMessage, MessageAndActions, MessageLoading, MessageAttachmentItem, MessageAttachmentsContainer } from '@patternfly/chatbot/dist/dynamic/Message';
46+
import MarkdownContent from '@patternfly/chatbot/dist/dynamic/MarkdownContent';
3447
import MessageDivider from '@patternfly/chatbot/dist/dynamic/MessageDivider';
48+
import ToolCall from '@patternfly/chatbot/dist/dynamic/ToolCall';
49+
import ResponseActions, { ResponseActionsGroups } from '@patternfly/chatbot/dist/dynamic/ResponseActions';
50+
import ToolResponse from '@patternfly/chatbot/dist/dynamic/ToolResponse';
3551
import { rehypeCodeBlockToggle } from '@patternfly/chatbot/dist/esm/Message/Plugins/rehypeCodeBlockToggle';
3652
import SourcesCard from '@patternfly/chatbot/dist/dynamic/SourcesCard';
3753
import { ArrowCircleDownIcon, ArrowRightIcon, CheckCircleIcon, CopyIcon, CubeIcon, CubesIcon, DownloadIcon, InfoCircleIcon, OutlinedQuestionCircleIcon, RedoIcon, RobotIcon, WrenchIcon } from '@patternfly/react-icons';
@@ -271,6 +287,35 @@ You can add custom content to specific parts of a `<Message>` via the `extraCont
271287

272288
```
273289

290+
### Custom message structure
291+
292+
For more advanced use cases, you can build completely custom message structures by passing children directly to `<Message>`. This approach is useful when you need to customize the order or structure of message elements beyond what the standard props allow.
293+
294+
When creating custom message structures, you must follow an intended composable structure.
295+
296+
1. **Message content and actions:** Wrap in `<MessageAndActions>`. This includes, but is not limited to:
297+
298+
- `<MarkdownContent>`: For rendering markdown or plain text content
299+
- `<ErrorMessage>`
300+
- `<MessageLoading>`
301+
- `<MessageInput>`
302+
- `<ToolCall>`
303+
- `<ToolResponse>`
304+
- `<DeepThinking>`
305+
- `<QuickResponse>`
306+
- `<QuickStartTile>`
307+
- `<UserFeedback>` and `<UserFeedbackComplete>`
308+
- `<SourcesCard>`
309+
- `<ResponseActionsGroups>` and `<ResponseActions>`
310+
311+
2. **File attachments:** Placed outside `<MessageAndActions>` and wrapped in attachment containers:
312+
- `<MessageAttachmentsContainer>`: Container for all attachments
313+
- `<MessageAttachmentItem>`: Individual attachment wrapper (contains `<FileDetailsLabel>` or other attachment components)
314+
315+
```ts file="./MessageWithCustomStructure.tsx"
316+
317+
```
318+
274319
## File attachments
275320

276321
### Messages with attachments

packages/module/src/MarkdownContent/MarkdownContent.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,15 @@ import SuperscriptMessage from '../Message/SuperscriptMessage/SuperscriptMessage
3030
import { ButtonProps } from '@patternfly/react-core';
3131
import { css } from '@patternfly/react-styles';
3232

33+
/**
34+
* MarkdownContent renders content either as plain text or with content with markdown support.
35+
*
36+
* Use this component when passing children to Message to customize its structure.
37+
*/
3338
export interface MarkdownContentProps {
34-
/** The markdown content to render */
39+
/** The content to render. Supports markdown formatting by default, or plain text when isMarkdownDisabled is true. */
3540
content?: string;
36-
/** Disables markdown parsing, allowing only text input */
41+
/** Disables markdown parsing, allowing only plain text input */
3742
isMarkdownDisabled?: boolean;
3843
/** Props for code blocks */
3944
codeBlockProps?: CodeBlockMessageProps;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { render, screen } from '@testing-library/react';
2+
import '@testing-library/jest-dom';
3+
import ErrorMessage from './ErrorMessage';
4+
5+
test('Renders with title', () => {
6+
render(<ErrorMessage title="Error occurred" />);
7+
8+
expect(screen.getByText('Error occurred')).toBeVisible();
9+
});
10+
11+
test('Renders with children', () => {
12+
render(<ErrorMessage title="Error occurred">This is the error message body</ErrorMessage>);
13+
14+
expect(screen.getByText('This is the error message body')).toBeVisible();
15+
});
16+
17+
test('Renders with action links', () => {
18+
const actionLinks = (
19+
<a href="#retry" data-testid="retry-link">
20+
Retry action link
21+
</a>
22+
);
23+
render(<ErrorMessage title="Error occurred" actionLinks={actionLinks} />);
24+
25+
expect(screen.getByText('Retry action link')).toBeVisible();
26+
});
27+
28+
test('Renders with custom className', () => {
29+
render(<ErrorMessage title="Error occurred" className="custom-error-class" />);
30+
31+
expect(screen.getByText('Error occurred').parentElement).toHaveClass('custom-error-class');
32+
});
33+
34+
test('Renders with spread props', () => {
35+
render(<ErrorMessage title="Error occurred" id="test-error-id" />);
36+
37+
expect(screen.getByText('Error occurred').parentElement).toHaveAttribute('id', 'test-error-id');
38+
});

packages/module/src/Message/ErrorMessage/ErrorMessage.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,23 @@
44

55
import { Alert, AlertProps } from '@patternfly/react-core';
66

7-
const ErrorMessage = ({ title, actionLinks, children, ...props }: AlertProps) => (
8-
<Alert isInline variant="danger" title={title} actionLinks={actionLinks} {...props}>
7+
/**
8+
* ErrorMessage displays an inline danger alert for error states in messages.
9+
* Use this component when passing children to Message to display error information.
10+
*/
11+
export interface ErrorMessageProps extends Partial<AlertProps> {
12+
/** Content to display in the error alert body */
13+
children?: React.ReactNode;
14+
/** Additional classes for the error alert */
15+
className?: string;
16+
/** Title of the error alert */
17+
title?: React.ReactNode;
18+
/** Action links to display in the alert footer */
19+
actionLinks?: React.ReactNode;
20+
}
21+
22+
export const ErrorMessage = ({ title, actionLinks, children, className, ...props }: ErrorMessageProps) => (
23+
<Alert isInline variant="danger" title={title} actionLinks={actionLinks} className={className} {...props}>
924
{children}
1025
</Alert>
1126
);

0 commit comments

Comments
 (0)