@patternfly/chatbot 6.4.0-prerelease.2 → 6.4.0-prerelease.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/dist/cjs/Chatbot/Chatbot.js +1 -7
  2. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.d.ts +2 -0
  3. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +2 -2
  4. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +22 -2
  5. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +15 -9
  6. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +40 -2
  7. package/dist/cjs/ChatbotHeader/ChatbotHeaderMenu.js +1 -1
  8. package/dist/cjs/ChatbotHeader/ChatbotHeaderMenu.test.js +1 -1
  9. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.d.ts +18 -0
  10. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.js +25 -0
  11. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.test.d.ts +1 -0
  12. package/dist/cjs/ChatbotHeader/ChatbotHeaderNewChatButton.test.js +22 -0
  13. package/dist/cjs/ChatbotHeader/index.d.ts +1 -0
  14. package/dist/cjs/ChatbotHeader/index.js +1 -0
  15. package/dist/cjs/DeepThinking/DeepThinking.d.ts +18 -0
  16. package/dist/cjs/DeepThinking/DeepThinking.js +18 -0
  17. package/dist/cjs/DeepThinking/DeepThinking.test.d.ts +1 -0
  18. package/dist/cjs/DeepThinking/DeepThinking.test.js +48 -0
  19. package/dist/cjs/DeepThinking/index.d.ts +2 -0
  20. package/dist/cjs/DeepThinking/index.js +23 -0
  21. package/dist/cjs/FilePreview/FilePreview.d.ts +26 -0
  22. package/dist/cjs/FilePreview/FilePreview.js +26 -0
  23. package/dist/cjs/FilePreview/FilePreview.test.d.ts +1 -0
  24. package/dist/cjs/FilePreview/FilePreview.test.js +97 -0
  25. package/dist/cjs/FilePreview/index.d.ts +2 -0
  26. package/dist/cjs/FilePreview/index.js +23 -0
  27. package/dist/cjs/Message/CodeBlockMessage/CodeBlockMessage.js +3 -3
  28. package/dist/cjs/Message/LinkMessage/LinkMessage.d.ts +2 -1
  29. package/dist/cjs/Message/LinkMessage/LinkMessage.js +7 -3
  30. package/dist/cjs/Message/ListMessage/ListItemMessage.d.ts +1 -1
  31. package/dist/cjs/Message/ListMessage/ListItemMessage.js +16 -1
  32. package/dist/cjs/Message/Message.d.ts +15 -0
  33. package/dist/cjs/Message/Message.js +129 -32
  34. package/dist/cjs/Message/Message.test.js +71 -0
  35. package/dist/cjs/Message/SuperscriptMessage/SuperscriptMessage.d.ts +3 -0
  36. package/dist/cjs/Message/SuperscriptMessage/SuperscriptMessage.js +5 -0
  37. package/dist/cjs/Message/UserFeedback/UserFeedback.d.ts +15 -1
  38. package/dist/cjs/Message/UserFeedback/UserFeedback.js +4 -4
  39. package/dist/cjs/Message/UserFeedback/UserFeedback.test.js +44 -0
  40. package/dist/cjs/MessageBar/MessageBar.js +19 -4
  41. package/dist/cjs/MessageBox/JumpButton.d.ts +5 -0
  42. package/dist/cjs/MessageBox/JumpButton.js +1 -1
  43. package/dist/cjs/MessageBox/JumpButton.test.js +4 -4
  44. package/dist/cjs/MessageBox/MessageBox.d.ts +9 -0
  45. package/dist/cjs/MessageBox/MessageBox.js +2 -2
  46. package/dist/cjs/MessageBox/MessageBox.test.js +2 -2
  47. package/dist/cjs/SourcesCard/SourcesCard.d.ts +13 -1
  48. package/dist/cjs/SourcesCard/SourcesCard.js +6 -6
  49. package/dist/cjs/SourcesCard/SourcesCard.test.js +49 -0
  50. package/dist/cjs/ToolResponse/ToolResponse.d.ts +30 -0
  51. package/dist/cjs/ToolResponse/ToolResponse.js +18 -0
  52. package/dist/cjs/ToolResponse/ToolResponse.test.d.ts +1 -0
  53. package/dist/cjs/ToolResponse/ToolResponse.test.js +60 -0
  54. package/dist/cjs/ToolResponse/index.d.ts +2 -0
  55. package/dist/cjs/ToolResponse/index.js +23 -0
  56. package/dist/cjs/index.d.ts +6 -0
  57. package/dist/cjs/index.js +10 -1
  58. package/dist/css/main.css +273 -17
  59. package/dist/css/main.css.map +1 -1
  60. package/dist/dynamic/DeepThinking/package.json +1 -0
  61. package/dist/dynamic/FilePreview/package.json +1 -0
  62. package/dist/dynamic/ToolResponse/package.json +1 -0
  63. package/dist/esm/Chatbot/Chatbot.js +1 -7
  64. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.d.ts +2 -0
  65. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +2 -2
  66. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +22 -2
  67. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.js +17 -11
  68. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +41 -3
  69. package/dist/esm/ChatbotHeader/ChatbotHeaderMenu.js +1 -1
  70. package/dist/esm/ChatbotHeader/ChatbotHeaderMenu.test.js +1 -1
  71. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.d.ts +18 -0
  72. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.js +22 -0
  73. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.test.d.ts +1 -0
  74. package/dist/esm/ChatbotHeader/ChatbotHeaderNewChatButton.test.js +20 -0
  75. package/dist/esm/ChatbotHeader/index.d.ts +1 -0
  76. package/dist/esm/ChatbotHeader/index.js +1 -0
  77. package/dist/esm/DeepThinking/DeepThinking.d.ts +18 -0
  78. package/dist/esm/DeepThinking/DeepThinking.js +14 -0
  79. package/dist/esm/DeepThinking/DeepThinking.test.d.ts +1 -0
  80. package/dist/esm/DeepThinking/DeepThinking.test.js +43 -0
  81. package/dist/esm/DeepThinking/index.d.ts +2 -0
  82. package/dist/esm/DeepThinking/index.js +2 -0
  83. package/dist/esm/FilePreview/FilePreview.d.ts +26 -0
  84. package/dist/esm/FilePreview/FilePreview.js +21 -0
  85. package/dist/esm/FilePreview/FilePreview.test.d.ts +1 -0
  86. package/dist/esm/FilePreview/FilePreview.test.js +92 -0
  87. package/dist/esm/FilePreview/index.d.ts +2 -0
  88. package/dist/esm/FilePreview/index.js +2 -0
  89. package/dist/esm/Message/CodeBlockMessage/CodeBlockMessage.js +5 -5
  90. package/dist/esm/Message/LinkMessage/LinkMessage.d.ts +2 -1
  91. package/dist/esm/Message/LinkMessage/LinkMessage.js +7 -3
  92. package/dist/esm/Message/ListMessage/ListItemMessage.d.ts +1 -1
  93. package/dist/esm/Message/ListMessage/ListItemMessage.js +16 -1
  94. package/dist/esm/Message/Message.d.ts +15 -0
  95. package/dist/esm/Message/Message.js +129 -32
  96. package/dist/esm/Message/Message.test.js +71 -0
  97. package/dist/esm/Message/SuperscriptMessage/SuperscriptMessage.d.ts +3 -0
  98. package/dist/esm/Message/SuperscriptMessage/SuperscriptMessage.js +3 -0
  99. package/dist/esm/Message/UserFeedback/UserFeedback.d.ts +15 -1
  100. package/dist/esm/Message/UserFeedback/UserFeedback.js +4 -4
  101. package/dist/esm/Message/UserFeedback/UserFeedback.test.js +45 -1
  102. package/dist/esm/MessageBar/MessageBar.js +19 -4
  103. package/dist/esm/MessageBox/JumpButton.d.ts +5 -0
  104. package/dist/esm/MessageBox/JumpButton.js +1 -1
  105. package/dist/esm/MessageBox/JumpButton.test.js +4 -4
  106. package/dist/esm/MessageBox/MessageBox.d.ts +9 -0
  107. package/dist/esm/MessageBox/MessageBox.js +2 -2
  108. package/dist/esm/MessageBox/MessageBox.test.js +2 -2
  109. package/dist/esm/SourcesCard/SourcesCard.d.ts +13 -1
  110. package/dist/esm/SourcesCard/SourcesCard.js +6 -6
  111. package/dist/esm/SourcesCard/SourcesCard.test.js +50 -1
  112. package/dist/esm/ToolResponse/ToolResponse.d.ts +30 -0
  113. package/dist/esm/ToolResponse/ToolResponse.js +14 -0
  114. package/dist/esm/ToolResponse/ToolResponse.test.d.ts +1 -0
  115. package/dist/esm/ToolResponse/ToolResponse.test.js +55 -0
  116. package/dist/esm/ToolResponse/index.d.ts +2 -0
  117. package/dist/esm/ToolResponse/index.js +2 -0
  118. package/dist/esm/index.d.ts +6 -0
  119. package/dist/esm/index.js +6 -0
  120. package/dist/tsconfig.tsbuildinfo +1 -1
  121. package/package.json +7 -6
  122. package/patternfly-docs/content/extensions/chatbot/examples/Messages/BotMessage.tsx +101 -3
  123. package/patternfly-docs/content/extensions/chatbot/examples/Messages/FilePreview.tsx +33 -0
  124. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithDeepThinking.tsx +17 -0
  125. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithFeedback.tsx +111 -85
  126. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithSources.tsx +70 -0
  127. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithToolResponse.tsx +135 -0
  128. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +28 -4
  129. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessage.tsx +107 -2
  130. package/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessageWithExtraContent.tsx +616 -3
  131. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotConversationEditing.tsx +202 -0
  132. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderBasic.tsx +17 -3
  133. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawer.tsx +36 -5
  134. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithPin.tsx +12 -2
  135. package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +22 -3
  136. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +1 -1
  137. package/patternfly-docs/patternfly-docs.config.js +1 -1
  138. package/src/Chatbot/Chatbot.scss +9 -2
  139. package/src/Chatbot/Chatbot.tsx +18 -31
  140. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.tsx +5 -1
  141. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +28 -10
  142. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +132 -3
  143. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +80 -33
  144. package/src/ChatbotHeader/ChatbotHeaderMenu.test.tsx +1 -1
  145. package/src/ChatbotHeader/ChatbotHeaderMenu.tsx +2 -2
  146. package/src/ChatbotHeader/ChatbotHeaderNewChatButton.test.tsx +25 -0
  147. package/src/ChatbotHeader/ChatbotHeaderNewChatButton.tsx +64 -0
  148. package/src/ChatbotHeader/index.ts +1 -0
  149. package/src/ChatbotModal/ChatbotModal.scss +1 -1
  150. package/src/DeepThinking/DeepThinking.scss +24 -0
  151. package/src/DeepThinking/DeepThinking.test.tsx +61 -0
  152. package/src/DeepThinking/DeepThinking.tsx +68 -0
  153. package/src/DeepThinking/index.ts +3 -0
  154. package/src/FileDetails/__snapshots__/FileDetails.test.tsx.snap +6 -9
  155. package/src/FileDetailsLabel/__snapshots__/FileDetailsLabel.test.tsx.snap +6 -9
  156. package/src/FilePreview/FilePreview.scss +22 -0
  157. package/src/FilePreview/FilePreview.test.tsx +112 -0
  158. package/src/FilePreview/FilePreview.tsx +58 -0
  159. package/src/FilePreview/index.ts +3 -0
  160. package/src/Message/CodeBlockMessage/CodeBlockMessage.scss +2 -1
  161. package/src/Message/CodeBlockMessage/CodeBlockMessage.tsx +6 -5
  162. package/src/Message/LinkMessage/LinkMessage.tsx +6 -2
  163. package/src/Message/ListMessage/ListItemMessage.tsx +5 -1
  164. package/src/Message/ListMessage/ListMessage.scss +17 -0
  165. package/src/Message/Message.scss +44 -0
  166. package/src/Message/Message.test.tsx +90 -0
  167. package/src/Message/Message.tsx +171 -46
  168. package/src/Message/SuperscriptMessage/SuperscriptMessage.scss +8 -0
  169. package/src/Message/SuperscriptMessage/SuperscriptMessage.tsx +13 -0
  170. package/src/Message/TextMessage/TextMessage.scss +46 -5
  171. package/src/Message/UserFeedback/UserFeedback.test.tsx +107 -0
  172. package/src/Message/UserFeedback/UserFeedback.tsx +41 -6
  173. package/src/MessageBar/MessageBar.tsx +23 -3
  174. package/src/MessageBox/JumpButton.test.tsx +4 -4
  175. package/src/MessageBox/JumpButton.tsx +20 -4
  176. package/src/MessageBox/MessageBox.test.tsx +2 -2
  177. package/src/MessageBox/MessageBox.tsx +22 -1
  178. package/src/SourcesCard/SourcesCard.scss +17 -0
  179. package/src/SourcesCard/SourcesCard.test.tsx +93 -0
  180. package/src/SourcesCard/SourcesCard.tsx +116 -80
  181. package/src/ToolResponse/ToolResponse.scss +36 -0
  182. package/src/ToolResponse/ToolResponse.test.tsx +78 -0
  183. package/src/ToolResponse/ToolResponse.tsx +95 -0
  184. package/src/ToolResponse/index.ts +3 -0
  185. package/src/index.ts +9 -0
  186. package/src/main.scss +3 -0
@@ -0,0 +1,68 @@
1
+ // ============================================================================
2
+ // Deep Thinking
3
+ // ============================================================================
4
+ import {
5
+ Card,
6
+ CardBody,
7
+ CardBodyProps,
8
+ CardProps,
9
+ ExpandableSection,
10
+ ExpandableSectionProps
11
+ } from '@patternfly/react-core';
12
+ import { useState, type FunctionComponent } from 'react';
13
+
14
+ export interface DeepThinkingProps {
15
+ /** Toggle content shown for expandable section */
16
+ toggleContent: React.ReactNode;
17
+ /** Additional props passed to expandable section */
18
+ expandableSectionProps?: Omit<ExpandableSectionProps, 'ref'>;
19
+ /** Subheading rendered inside expandable section */
20
+ subheading?: string;
21
+ /** Body text rendered inside expandable section */
22
+ body?: React.ReactNode | string;
23
+ /** Additional props passed to main card */
24
+ cardProps?: CardProps;
25
+ /** Additional props passed to main card body */
26
+ cardBodyProps?: CardBodyProps;
27
+ }
28
+
29
+ export const DeepThinking: FunctionComponent<DeepThinkingProps> = ({
30
+ body,
31
+ cardProps,
32
+ expandableSectionProps,
33
+ subheading,
34
+ toggleContent,
35
+ cardBodyProps
36
+ }: DeepThinkingProps) => {
37
+ const [isExpanded, setIsExpanded] = useState(true);
38
+
39
+ const onToggle = (_event: React.MouseEvent, isExpanded: boolean) => {
40
+ setIsExpanded(isExpanded);
41
+ };
42
+
43
+ return (
44
+ <Card isCompact className="pf-chatbot__deep-thinking" {...cardProps}>
45
+ <CardBody {...cardBodyProps}>
46
+ <ExpandableSection
47
+ toggleContent={toggleContent}
48
+ onToggle={onToggle}
49
+ isExpanded={isExpanded}
50
+ isIndented
51
+ className="pf-chatbot__deep-thinking-expandable-section"
52
+ {...expandableSectionProps}
53
+ >
54
+ <div className="pf-chatbot__deep-thinking-section">
55
+ {subheading && (
56
+ <div className="pf-chatbot__deep-thinking-subheading">
57
+ <span>{subheading}</span>
58
+ </div>
59
+ )}
60
+ {body && <div className="pf-chatbot__deep-thinking-body">{body}</div>}
61
+ </div>
62
+ </ExpandableSection>
63
+ </CardBody>
64
+ </Card>
65
+ );
66
+ };
67
+
68
+ export default DeepThinking;
@@ -0,0 +1,3 @@
1
+ export { default } from './DeepThinking';
2
+
3
+ export * from './DeepThinking';
@@ -48,19 +48,16 @@ exports[`FileDetails should render file details 1`] = `
48
48
  <span
49
49
  class="pf-chatbot__code-fileName"
50
50
  >
51
- <div
52
- style="display: contents;"
51
+ <span
52
+ class="pf-v6-c-truncate"
53
+ tabindex="0"
53
54
  >
54
55
  <span
55
- class="pf-v6-c-truncate"
56
+ class="pf-v6-c-truncate__start"
56
57
  >
57
- <span
58
- class="pf-v6-c-truncate__start"
59
- >
60
- test
61
- </span>
58
+ test
62
59
  </span>
63
- </div>
60
+ </span>
64
61
  </span>
65
62
  </div>
66
63
  <div
@@ -60,19 +60,16 @@ exports[`FileDetailsLabel should render file details label 1`] = `
60
60
  <span
61
61
  class="pf-chatbot__code-fileName"
62
62
  >
63
- <div
64
- style="display: contents;"
63
+ <span
64
+ class="pf-v6-c-truncate"
65
+ tabindex="0"
65
66
  >
66
67
  <span
67
- class="pf-v6-c-truncate"
68
+ class="pf-v6-c-truncate__start"
68
69
  >
69
- <span
70
- class="pf-v6-c-truncate__start"
71
- >
72
- test
73
- </span>
70
+ test
74
71
  </span>
75
- </div>
72
+ </span>
76
73
  </span>
77
74
  </div>
78
75
  <div
@@ -0,0 +1,22 @@
1
+ .pf-chatbot__file-preview-body {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--pf-t--global--spacer--md);
5
+ align-items: center;
6
+ justify-content: center;
7
+ }
8
+
9
+ .pf-chatbot__file-preview-icon {
10
+ color: var(--pf-t--global--icon--color--subtle);
11
+ width: var(--pf-t--global--icon--size--2xl);
12
+ height: var(--pf-t--global--icon--size--2xl);
13
+ }
14
+
15
+ .pf-chatbot__file-preview-name {
16
+ font-size: var(--pf-t--global--font--size--xl);
17
+ font-weight: var(--pf-t--global--font--weight--heading--default);
18
+ }
19
+ .pf-chatbot__file-preview-body {
20
+ color: var(--pf-t--global--text--color--subtle);
21
+ font-size: var(--pf-t--global--font--size--body--lg);
22
+ }
@@ -0,0 +1,112 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import '@testing-library/jest-dom';
3
+ import FilePreview from './FilePreview';
4
+ import { ChatbotDisplayMode } from '../Chatbot';
5
+ import { Button, ModalBodyProps, ModalHeaderProps } from '@patternfly/react-core';
6
+
7
+ describe('FilePreview', () => {
8
+ const defaultProps = {
9
+ isModalOpen: true,
10
+ handleModalToggle: jest.fn(),
11
+ fileName: 'test-file.txt',
12
+ children: 'File content preview'
13
+ };
14
+
15
+ beforeEach(() => {
16
+ jest.clearAllMocks();
17
+ });
18
+
19
+ it('should render with basic props', () => {
20
+ render(<FilePreview {...defaultProps} />);
21
+ expect(screen.getByText('File preview')).toBeInTheDocument();
22
+ expect(screen.getByText('test-file.txt')).toBeInTheDocument();
23
+ });
24
+
25
+ it('should render with custom title', () => {
26
+ const customTitle = 'Custom file preview title';
27
+ render(<FilePreview {...defaultProps} title={customTitle} />);
28
+ expect(screen.getByRole('heading', { name: customTitle })).toBeTruthy();
29
+ });
30
+
31
+ it('should handle modal toggle when closed', () => {
32
+ const mockToggle = jest.fn();
33
+ render(<FilePreview {...defaultProps} isModalOpen={false} handleModalToggle={mockToggle} />);
34
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
35
+ });
36
+
37
+ it('should apply default display mode class', () => {
38
+ render(<FilePreview {...defaultProps} />);
39
+ const modal = screen.getByRole('dialog');
40
+ expect(modal).toHaveClass('pf-chatbot__file-preview-modal--default');
41
+ });
42
+
43
+ it('should apply custom display mode class', () => {
44
+ render(<FilePreview {...defaultProps} displayMode={ChatbotDisplayMode.fullscreen} />);
45
+ const modal = screen.getByRole('dialog');
46
+ expect(modal).toHaveClass('pf-chatbot__file-preview-modal--fullscreen');
47
+ });
48
+
49
+ it('should apply compact styling when isCompact is true', () => {
50
+ render(<FilePreview {...defaultProps} isCompact />);
51
+ const modal = screen.getByRole('dialog');
52
+ expect(modal).toHaveClass('pf-m-compact');
53
+ });
54
+
55
+ it('should not apply compact styling when isCompact is false', () => {
56
+ render(<FilePreview {...defaultProps} isCompact={false} />);
57
+ const modal = screen.getByRole('dialog');
58
+ expect(modal).not.toHaveClass('pf-m-compact');
59
+ });
60
+
61
+ it('should apply custom className', () => {
62
+ const customClass = 'custom-file-preview';
63
+ render(<FilePreview {...defaultProps} className={customClass} />);
64
+ const modal = screen.getByRole('dialog');
65
+ expect(modal).toHaveClass(customClass);
66
+ });
67
+
68
+ it('should pass through additional props to ChatbotModal', () => {
69
+ render(<FilePreview {...defaultProps} data-testid="file-preview-modal" />);
70
+ const modal = screen.getByTestId('file-preview-modal');
71
+ expect(modal).toBeInTheDocument();
72
+ });
73
+
74
+ it('should pass modalHeaderProps to ModalHeader', () => {
75
+ const modalHeaderProps = {
76
+ 'data-testid': 'custom-header'
77
+ } as ModalHeaderProps;
78
+ render(<FilePreview {...defaultProps} modalHeaderProps={modalHeaderProps} />);
79
+ const header = screen.getByTestId('custom-header');
80
+ expect(header).toBeInTheDocument();
81
+ });
82
+
83
+ it('should pass modalBodyProps to ModalBody', () => {
84
+ const modalBodyProps = {
85
+ 'data-testid': 'custom-body'
86
+ } as ModalBodyProps;
87
+ render(<FilePreview {...defaultProps} modalBodyProps={modalBodyProps} />);
88
+ const body = screen.getByTestId('custom-body');
89
+ expect(body).toBeInTheDocument();
90
+ });
91
+
92
+ it('should pass ouiaId to ChatbotModal', () => {
93
+ const ouiaId = 'file-preview-ouia-id';
94
+ render(<FilePreview {...defaultProps} ouiaId={ouiaId} />);
95
+ const modal = screen.getByRole('dialog');
96
+ expect(modal).toHaveAttribute('data-ouia-component-id', ouiaId);
97
+ });
98
+
99
+ it('should handle complex children', () => {
100
+ const complexChildren = (
101
+ <div>
102
+ <h3>File details</h3>
103
+ <p>Size: 1.2 MB</p>
104
+ <Button>Download</Button>
105
+ </div>
106
+ );
107
+ render(<FilePreview {...defaultProps}>{complexChildren}</FilePreview>);
108
+ expect(screen.getByRole('heading', { name: /File details/i })).toBeTruthy();
109
+ expect(screen.getByText('Size: 1.2 MB')).toBeTruthy();
110
+ expect(screen.getByRole('button', { name: /Download/i })).toBeTruthy();
111
+ });
112
+ });
@@ -0,0 +1,58 @@
1
+ import { ModalBody, ModalBodyProps, ModalHeader, ModalHeaderProps } from '@patternfly/react-core';
2
+ import type { FunctionComponent } from 'react';
3
+ import { ChatbotDisplayMode } from '../Chatbot';
4
+ import ChatbotModal, { ChatbotModalProps } from '../ChatbotModal';
5
+ import { FileIcon } from '@patternfly/react-icons';
6
+
7
+ export interface FilePreviewProps extends ChatbotModalProps {
8
+ /** Class applied to modal */
9
+ className?: string;
10
+ /** Function that handles modal toggle */
11
+ handleModalToggle: (event: React.MouseEvent | MouseEvent | KeyboardEvent) => void;
12
+ /** Whether modal is open */
13
+ isModalOpen: boolean;
14
+ /** Title of modal */
15
+ title?: string;
16
+ /** Display mode for the Chatbot parent; this influences the styles applied */
17
+ displayMode?: ChatbotDisplayMode;
18
+ /** File name */
19
+ fileName: string;
20
+ /** Sets modal to compact styling. */
21
+ isCompact?: boolean;
22
+ /** Additional props passed to modal header */
23
+ modalHeaderProps?: ModalHeaderProps;
24
+ /** Additional props passed to modal body */
25
+ modalBodyProps?: ModalBodyProps;
26
+ }
27
+
28
+ const FilePreview: FunctionComponent<FilePreviewProps> = ({
29
+ isModalOpen,
30
+ displayMode = ChatbotDisplayMode.default,
31
+ children,
32
+ fileName,
33
+ isCompact,
34
+ className,
35
+ handleModalToggle,
36
+ title = 'File preview',
37
+ modalHeaderProps,
38
+ modalBodyProps,
39
+ ...props
40
+ }: FilePreviewProps) => (
41
+ <ChatbotModal
42
+ isOpen={isModalOpen}
43
+ className={`pf-chatbot__file-preview-modal pf-chatbot__file-preview-modal--${displayMode} ${isCompact ? 'pf-m-compact' : ''} ${className ? className : ''}`}
44
+ displayMode={displayMode}
45
+ onClose={handleModalToggle}
46
+ isCompact={isCompact}
47
+ {...props}
48
+ >
49
+ <ModalHeader title={title} {...modalHeaderProps} />
50
+ <ModalBody className="pf-chatbot__file-preview-body" {...modalBodyProps}>
51
+ <FileIcon className="pf-chatbot__file-preview-icon" />
52
+ <h2 className="pf-chatbot__file-preview-name">{fileName}</h2>
53
+ {children && <div className="pf-chatbot__file-preview-body">{children}</div>}
54
+ </ModalBody>
55
+ </ChatbotModal>
56
+ );
57
+
58
+ export default FilePreview;
@@ -0,0 +1,3 @@
1
+ export { default } from './FilePreview';
2
+
3
+ export * from './FilePreview';
@@ -77,8 +77,9 @@
77
77
  }
78
78
 
79
79
  .pf-chatbot__message-inline-code {
80
+ --pf-chatbot-message-text-inline-code-font-size: var(--pf-t--global--font--size--body--default);
80
81
  background-color: var(--pf-t--global--background--color--tertiary--default);
81
- font-size: var(--pf-t--global--font--size--body--default);
82
+ font-size: var(--pf-chatbot-message-text-inline-code-font-size);
82
83
  }
83
84
 
84
85
  .pf-chatbot__message-code-toggle {
@@ -1,7 +1,7 @@
1
1
  // ============================================================================
2
2
  // Chatbot Main - Message - Content - Code Block
3
3
  // ============================================================================
4
- import { useState, useRef, useId, useCallback, useEffect } from 'react';
4
+ import { useState, useRef, useCallback, useEffect } from 'react';
5
5
  // Import PatternFly components
6
6
  import {
7
7
  CodeBlock,
@@ -13,7 +13,8 @@ import {
13
13
  ExpandableSectionToggle,
14
14
  ExpandableSectionProps,
15
15
  ExpandableSectionToggleProps,
16
- ExpandableSectionVariant
16
+ ExpandableSectionVariant,
17
+ getUniqueId
17
18
  } from '@patternfly/react-core';
18
19
 
19
20
  import { CheckIcon } from '@patternfly/react-icons/dist/esm/icons/check-icon';
@@ -56,9 +57,9 @@ const CodeBlockMessage = ({
56
57
  const [isExpanded, setIsExpanded] = useState(false);
57
58
 
58
59
  const buttonRef = useRef();
59
- const tooltipID = useId();
60
- const toggleId = useId();
61
- const contentId = useId();
60
+ const tooltipID = getUniqueId();
61
+ const toggleId = getUniqueId();
62
+ const contentId = getUniqueId();
62
63
  const codeBlockRef = useRef<HTMLDivElement>(null);
63
64
 
64
65
  const language = /language-(\w+)/.exec(className || '')?.[1];
@@ -4,8 +4,9 @@
4
4
 
5
5
  import { Button, ButtonProps } from '@patternfly/react-core';
6
6
  import { ExternalLinkSquareAltIcon } from '@patternfly/react-icons';
7
+ import { ExtraProps } from 'react-markdown';
7
8
 
8
- const LinkMessage = ({ children, target, href, ...props }: ButtonProps) => {
9
+ const LinkMessage = ({ children, target, href, id, ...props }: ButtonProps & ExtraProps) => {
9
10
  if (target === '_blank') {
10
11
  return (
11
12
  <Button
@@ -16,6 +17,8 @@ const LinkMessage = ({ children, target, href, ...props }: ButtonProps) => {
16
17
  iconPosition="end"
17
18
  isInline
18
19
  target={target}
20
+ // need to explicitly call this out or id doesn't seem to get passed - required for footnotes
21
+ id={id}
19
22
  {...props}
20
23
  >
21
24
  {children}
@@ -24,7 +27,8 @@ const LinkMessage = ({ children, target, href, ...props }: ButtonProps) => {
24
27
  }
25
28
 
26
29
  return (
27
- <Button isInline component="a" href={href} variant="link" {...props}>
30
+ // need to explicitly call this out or id doesn't seem to get passed - required for footnotes
31
+ <Button isInline component="a" href={href} variant="link" id={id} {...props}>
28
32
  {children}
29
33
  </Button>
30
34
  );
@@ -5,6 +5,10 @@
5
5
  import { ExtraProps } from 'react-markdown';
6
6
  import { ListItem } from '@patternfly/react-core';
7
7
 
8
- const ListItemMessage = ({ children }: JSX.IntrinsicElements['li'] & ExtraProps) => <ListItem>{children}</ListItem>;
8
+ const ListItemMessage = ({ children, ...props }: JSX.IntrinsicElements['li'] & ExtraProps) => (
9
+ <ListItem {...props} tabIndex={props?.id?.includes('fn-') ? -1 : props?.tabIndex}>
10
+ {children}
11
+ </ListItem>
12
+ );
9
13
 
10
14
  export default ListItemMessage;
@@ -21,5 +21,22 @@
21
21
  background-color: var(--pf-t--global--color--brand--default);
22
22
  color: var(--pf-t--global--text--color--on-brand--default);
23
23
  padding: var(--pf-t--global--spacer--sm);
24
+
25
+ // prevents issues when highlighting things like footnotes - don't have blue on blue
26
+ .pf-chatbot__message-text {
27
+ background-color: initial;
28
+ }
29
+ }
30
+
31
+ // targets footnotes specifically and prevents misalignment problems
32
+ .footnotes {
33
+ li > span {
34
+ display: inline-flex;
35
+ flex-direction: column;
36
+ }
37
+ }
38
+
39
+ li a {
40
+ color: var(--pf-t--global--text--color--on-brand--default);
24
41
  }
25
42
  }
@@ -89,6 +89,49 @@
89
89
  display: grid;
90
90
  gap: var(--pf-t--global--spacer--sm);
91
91
  }
92
+
93
+ // targets footnotes specifically
94
+ .footnotes,
95
+ .pf-chatbot__message-text.footnotes {
96
+ padding: var(--pf-t--global--spacer--sm) var(--pf-t--global--spacer--sm) 0 var(--pf-t--global--spacer--sm);
97
+ --pf-chatbot-message-text-font-size: var(--pf-t--global--font--size--xs);
98
+ --pf-chatbot-message-text-inline-code-font-size: var(--pf-t--global--font--size--xs);
99
+
100
+ .pf-chatbot__message-text h1,
101
+ h2,
102
+ h3,
103
+ h4,
104
+ h5,
105
+ h6 {
106
+ --pf-v6-c-content--h1--FontSize: var(--pf-t--global--font--size--md);
107
+ --pf-v6-c-content--h2--FontSize: var(--pf-t--global--font--size--md);
108
+ --pf-v6-c-content--h3--FontSize: var(--pf-t--global--font--size--md);
109
+ --pf-v6-c-content--h4--FontSize: var(--pf-t--global--font--size--md);
110
+ --pf-v6-c-content--h5--FontSize: var(--pf-t--global--font--size--md);
111
+ --pf-v6-c-content--h6--FontSize: var(--pf-t--global--font--size--md);
112
+ }
113
+ .pf-chatbot__message-text .pf-v6-c-content,
114
+ .pf-chatbot__message-text .pf-v6-c-content--small,
115
+ .pf-chatbot__message-text .pf-v6-c-content--blockquote,
116
+ .pf-chatbot__message-text p,
117
+ .pf-chatbot__message-text a {
118
+ --pf-v6-c-content--FontSize: var(--pf-t--global--font--size--xs);
119
+ }
120
+ .pf-chatbot__message-inline-code,
121
+ .pf-chatbot__message-text .pf-v6-c-button.pf-m-link,
122
+ .pf-chatbot__message-ordered-list .pf-v6-c-list,
123
+ .pf-chatbot__message-ordered-list ul,
124
+ .pf-chatbot__message-ordered-list li,
125
+ .pf-chatbot__message-unordered-list .pf-v6-c-list,
126
+ .pf-chatbot__message-unordered-list ul,
127
+ .pf-chatbot__message-unordered-list li {
128
+ font-size: var(--pf-t--global--font--size--xs);
129
+ }
130
+ }
131
+
132
+ .footnotes {
133
+ background-color: var(--pf-t--global--background--color--tertiary--default);
134
+ }
92
135
  }
93
136
 
94
137
  // Attachments
@@ -106,6 +149,7 @@
106
149
  @import './MessageLoading';
107
150
  @import './CodeBlockMessage/CodeBlockMessage';
108
151
  @import './TextMessage/TextMessage';
152
+ @import './SuperscriptMessage/SuperscriptMessage.scss';
109
153
 
110
154
  // ============================================================================
111
155
  // Information density styles
@@ -7,6 +7,7 @@ import { monitorSampleAppQuickStart } from './QuickStarts/monitor-sampleapp-quic
7
7
  import { monitorSampleAppQuickStartWithImage } from './QuickStarts/monitor-sampleapp-quickstart-with-image';
8
8
  import rehypeExternalLinks from '../__mocks__/rehype-external-links';
9
9
  import { AlertActionLink } from '@patternfly/react-core';
10
+ import { DeepThinkingProps } from '../DeepThinking';
10
11
 
11
12
  const ALL_ACTIONS = [
12
13
  { label: /Good response/i },
@@ -141,10 +142,30 @@ const EMPTY_TABLE = `
141
142
 
142
143
  `;
143
144
 
145
+ const FOOTNOTE = `This is some text with a footnote[^1] and here's a longer one.[^bignote]
146
+
147
+ You can also reference the same footnote multiple times[^1].
148
+
149
+ [^1]: This is the full footnote text. You can click the arrow to go back up.
150
+
151
+ [^bignote]: Here's one with multiple paragraphs and **formatting**.
152
+
153
+ Indent paragraphs to include them in the footnote.
154
+
155
+ Add as many paragraphs as you like. You can include *italic text*, **bold text**, and even \`code\`.
156
+
157
+ > You can even include blockquotes in footnotes!`;
158
+
144
159
  const IMAGE = `![Multi-colored wavy lines on a black background](https://cdn.dribbble.com/userupload/10651749/file/original-8a07b8e39d9e8bf002358c66fce1223e.gif)`;
145
160
 
146
161
  const INLINE_IMAGE = `inline text ![Multi-colored wavy lines on a black background](https://cdn.dribbble.com/userupload/10651749/file/original-8a07b8e39d9e8bf002358c66fce1223e.gif)`;
147
162
 
163
+ const DEEP_THINKING: DeepThinkingProps = {
164
+ toggleContent: 'Show thinking',
165
+ subheading: 'Thought for 3 seconds',
166
+ body: "Here's why I said this."
167
+ };
168
+
148
169
  const ERROR = {
149
170
  title: 'Could not load chat',
150
171
  children: 'Wait a few minutes and check your network settings. If the issue persists: ',
@@ -675,6 +696,28 @@ describe('Message', () => {
675
696
  );
676
697
  expect(screen.getAllByRole('img')[1]).toHaveAttribute('src', 'test.png');
677
698
  });
699
+ it('should handle tool response correctly', async () => {
700
+ render(
701
+ <Message
702
+ avatar="./img"
703
+ role="user"
704
+ name="User"
705
+ content="Hi"
706
+ toolResponse={{
707
+ toggleContent: 'Tool response: Name',
708
+ subheading: 'Thought for 3 seconds',
709
+ body: 'Lorem ipsum dolor sit amet',
710
+ cardTitle: 'Card title',
711
+ cardBody: 'Card body'
712
+ }}
713
+ />
714
+ );
715
+ expect(screen.getByRole('button', { name: /Tool response: Name/i })).toBeTruthy();
716
+ expect(screen.getByText('Thought for 3 seconds')).toBeTruthy();
717
+ expect(screen.getByText('Lorem ipsum dolor sit amet')).toBeTruthy();
718
+ expect(screen.getByText('Card title')).toBeTruthy();
719
+ expect(screen.getByText('Card body')).toBeTruthy();
720
+ });
678
721
  it('should handle block quote correctly', () => {
679
722
  render(<Message avatar="./img" role="user" name="User" content={BLOCK_QUOTES} />);
680
723
  expect(screen.getByText(/Blockquotes can also be nested.../)).toBeTruthy();
@@ -740,6 +783,28 @@ describe('Message', () => {
740
783
  render(<Message avatar="./img" role="user" name="User" content={TABLE} tableProps={{ 'aria-label': 'Test' }} />);
741
784
  expect(screen.getByRole('grid', { name: /Test/i })).toBeTruthy();
742
785
  });
786
+ it('should render footnote correctly', () => {
787
+ render(<Message avatar="./img" role="user" name="User" content={FOOTNOTE} />);
788
+ expect(screen.getByText(/This is some text with a footnote/i)).toBeTruthy();
789
+ expect(screen.getByText(/and here's a longer one./i)).toBeTruthy();
790
+ expect(screen.getByText(/You can also reference the same footnote multiple times./i)).toBeTruthy();
791
+ expect(screen.getByRole('heading', { name: /Footnotes/i })).toBeTruthy();
792
+ expect(screen.getByText(/This is the full footnote text. You can click the arrow to go back up./i)).toBeTruthy();
793
+ expect(screen.getByText(/Here's one with multiple paragraphs and/i)).toBeTruthy();
794
+ expect(screen.getByText(/formatting/i)).toBeTruthy();
795
+ expect(screen.getByText(/Indent paragraphs to include them in the footnote./i)).toBeTruthy();
796
+ expect(screen.getByText(/Add as many paragraphs as you like. You can include/i)).toBeTruthy();
797
+ expect(screen.getByText(/italic text/i)).toBeTruthy();
798
+ expect(screen.getByText(/bold text/i)).toBeTruthy();
799
+ expect(screen.getByText(/, and even/i)).toBeTruthy();
800
+ expect(screen.getByText(/code/i)).toBeTruthy();
801
+ expect(screen.getByText(/You can even include blockquotes in footnotes!/i)).toBeTruthy();
802
+ expect(screen.getAllByRole('link', { name: '1' })).toHaveLength(2);
803
+ expect(screen.getAllByRole('link', { name: '2' })).toBeTruthy();
804
+ expect(screen.getByRole('link', { name: 'Back to reference 1' })).toBeTruthy();
805
+ expect(screen.getByRole('link', { name: 'Back to reference 1-2' })).toBeTruthy();
806
+ expect(screen.getByRole('link', { name: /Back to reference 2/i })).toBeTruthy();
807
+ });
743
808
  it('should render beforeMainContent with main content', () => {
744
809
  const mainContent = 'Main message content';
745
810
  const beforeMainContentText = 'Before main content';
@@ -962,4 +1027,29 @@ describe('Message', () => {
962
1027
  const form = container.querySelector('form');
963
1028
  expect(form).toHaveClass('test');
964
1029
  });
1030
+ it('should be able to disable markdown parsing', () => {
1031
+ render(<Message avatar="./img" role="user" name="User" content={CODE_MESSAGE} isMarkdownDisabled />);
1032
+ // this is looking for markdown syntax that is ordinarily stripped
1033
+ expect(screen.getByText(/~~~yaml/i)).toBeTruthy();
1034
+ });
1035
+ it('should be able to pass props to react-markdown, such as disabling tags', () => {
1036
+ render(
1037
+ <Message
1038
+ avatar="./img"
1039
+ role="user"
1040
+ name="User"
1041
+ content={CODE_MESSAGE}
1042
+ reactMarkdownProps={{ disallowedElements: ['code'] }}
1043
+ />
1044
+ );
1045
+ expect(screen.getByText('Here is some YAML code:')).toBeTruthy();
1046
+ // code block isn't rendering
1047
+ expect(screen.queryByRole('button', { name: 'Copy code' })).toBeFalsy();
1048
+ });
1049
+ it('should render deep thinking section correctly', () => {
1050
+ render(<Message avatar="./img" role="user" name="User" content="" deepThinking={DEEP_THINKING} />);
1051
+ expect(screen.getByRole('button', { name: /Show thinking/i })).toBeTruthy();
1052
+ expect(screen.getByText('Thought for 3 seconds')).toBeTruthy();
1053
+ expect(screen.getByText("Here's why I said this.")).toBeTruthy();
1054
+ });
965
1055
  });