diff --git a/package-lock.json b/package-lock.json index 73350acb2..34a020514 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17714,6 +17714,8 @@ }, "node_modules/mdast-util-find-and-replace/node_modules/unist-util-is": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -17938,6 +17940,8 @@ }, "node_modules/mdast-util-phrasing/node_modules/unist-util-is": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -21307,6 +21311,8 @@ }, "node_modules/rehype-external-links/node_modules/hast-util-is-element": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" @@ -25209,6 +25215,8 @@ }, "node_modules/unist-util-visit-parents/node_modules/unist-util-is": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -25226,6 +25234,8 @@ }, "node_modules/unist-util-visit/node_modules/unist-util-is": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/WhiteEmbeddedChatbot.tsx b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/WhiteEmbeddedChatbot.tsx index 4731d8cdb..8b40a1da8 100644 --- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/WhiteEmbeddedChatbot.tsx +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/WhiteEmbeddedChatbot.tsx @@ -97,6 +97,14 @@ const MessageLoading = () => ( export default MessageLoading; ~~~ + +Here is a table: + + | Version | GA date | User role + |-|-|-| + | 2.5 | September 30, 2024 | Administrator | + | 2.5 | June 27, 2023 | Editor | + | 3.0 | April 1, 2025 | Administrator `; // It's important to set a date and timestamp prop since the Message components re-render. @@ -111,7 +119,8 @@ const initialMessages: MessageProps[] = [ name: 'User', avatar: userAvatar, timestamp: date.toLocaleString(), - avatarProps: { isBordered: true } + avatarProps: { isBordered: true }, + isPrimary: true }, { id: '2', @@ -131,7 +140,9 @@ const initialMessages: MessageProps[] = [ download: { onClick: () => console.log('Download') }, // eslint-disable-next-line no-console listen: { onClick: () => console.log('Listen') } - } + }, + isPrimary: true, + attachments: [{ name: 'auth-operator.yml', id: '1' }] } ]; @@ -220,7 +231,8 @@ export const EmbeddedChatbotDemo: FunctionComponent = () => { name: 'User', avatar: userAvatar, timestamp: date.toLocaleString(), - avatarProps: { isBordered: true } + avatarProps: { isBordered: true }, + isPrimary: true }); newMessages.push({ id: generateId(), @@ -229,7 +241,8 @@ export const EmbeddedChatbotDemo: FunctionComponent = () => { name: 'Bot', avatar: patternflyAvatar, isLoading: true, - timestamp: date.toLocaleString() + timestamp: date.toLocaleString(), + isPrimary: true }); setMessages(newMessages); // make announcement to assistive devices that new messages have been added @@ -261,7 +274,8 @@ export const EmbeddedChatbotDemo: FunctionComponent = () => { // eslint-disable-next-line no-console listen: { onClick: () => console.log('Listen') } }, - timestamp: date.toLocaleString() + timestamp: date.toLocaleString(), + isPrimary: true }); setMessages(loadedMessages); // make announcement to assistive devices that new message has loaded diff --git a/packages/module/patternfly-docs/generated/patternfly-ai/chatbot/overview/demo/primary-color-background.png b/packages/module/patternfly-docs/generated/patternfly-ai/chatbot/overview/demo/primary-color-background.png index 1e54e7cd7..021246c15 100644 Binary files a/packages/module/patternfly-docs/generated/patternfly-ai/chatbot/overview/demo/primary-color-background.png and b/packages/module/patternfly-docs/generated/patternfly-ai/chatbot/overview/demo/primary-color-background.png differ diff --git a/packages/module/src/FileDetailsLabel/FileDetailsLabel.tsx b/packages/module/src/FileDetailsLabel/FileDetailsLabel.tsx index 735709999..907a07c46 100644 --- a/packages/module/src/FileDetailsLabel/FileDetailsLabel.tsx +++ b/packages/module/src/FileDetailsLabel/FileDetailsLabel.tsx @@ -1,10 +1,10 @@ import { PropsWithChildren } from 'react'; -import { Button, Label } from '@patternfly/react-core'; +import { Button, Label, LabelProps } from '@patternfly/react-core'; import FileDetails from '../FileDetails'; import { Spinner } from '@patternfly/react-core'; import { TimesIcon } from '@patternfly/react-icons'; -export interface FileDetailsLabelProps { +export interface FileDetailsLabelProps extends Omit { /** Name of file, including extension */ fileName: string; /** Unique id of file */ diff --git a/packages/module/src/Message/CodeBlockMessage/CodeBlockMessage.scss b/packages/module/src/Message/CodeBlockMessage/CodeBlockMessage.scss index 376f60fdb..e6033c3ca 100644 --- a/packages/module/src/Message/CodeBlockMessage/CodeBlockMessage.scss +++ b/packages/module/src/Message/CodeBlockMessage/CodeBlockMessage.scss @@ -81,6 +81,10 @@ --pf-chatbot-message-text-inline-code-font-size: var(--pf-t--global--font--size--body--default); background-color: var(--pf-t--global--background--color--tertiary--default); font-size: var(--pf-chatbot-message-text-inline-code-font-size); + + &.pf-m-primary { + background-color: var(--pf-t--global--background--color--secondary--default); + } } .pf-chatbot__message-code-toggle { diff --git a/packages/module/src/Message/CodeBlockMessage/CodeBlockMessage.tsx b/packages/module/src/Message/CodeBlockMessage/CodeBlockMessage.tsx index 9578af9fc..7eb295bd3 100644 --- a/packages/module/src/Message/CodeBlockMessage/CodeBlockMessage.tsx +++ b/packages/module/src/Message/CodeBlockMessage/CodeBlockMessage.tsx @@ -39,6 +39,8 @@ export interface CodeBlockMessageProps { collapsedText?: string; /** Custom actions added to header of code block, after any default actions such as the "copy" action. */ customActions?: React.ReactNode; + /** Sets background colors to be appropriate on primary chatbot background */ + isPrimary?: boolean; } const DEFAULT_EXPANDED_TEXT = 'Show less'; @@ -54,6 +56,7 @@ const CodeBlockMessage = ({ expandedText = DEFAULT_EXPANDED_TEXT, collapsedText = DEFAULT_COLLAPSED_TEXT, customActions, + isPrimary, ...props }: CodeBlockMessageProps) => { const [copied, setCopied] = useState(false); @@ -108,7 +111,7 @@ const CodeBlockMessage = ({ if (!String(children).includes('\n')) { return ( - + {children} ); diff --git a/packages/module/src/Message/Message.test.tsx b/packages/module/src/Message/Message.test.tsx index 79db8ec01..bfc7e42f7 100644 --- a/packages/module/src/Message/Message.test.tsx +++ b/packages/module/src/Message/Message.test.tsx @@ -1093,4 +1093,41 @@ describe('Message', () => { expect(screen.getByText('Thought for 3 seconds')).toBeTruthy(); expect(screen.getByText("Here's why I said this.")).toBeTruthy(); }); + it('should handle isPrimary correctly for inline code when it is true', () => { + const { container } = render(); + expect(container.querySelector('.pf-m-primary')).toBeTruthy(); + }); + it('should handle isPrimary correctly for inline code when it is false', () => { + const { container } = render(); + expect(container.querySelector('.pf-m-primary')).toBeFalsy(); + }); + it('should handle isPrimary correctly for table when it is true', () => { + const { container } = render(); + expect(container.querySelector('.pf-m-primary')).toBeTruthy(); + }); + it('should handle isPrimary correctly for table when it is false', () => { + const { container } = render(); + expect(container.querySelector('.pf-m-primary')).toBeFalsy(); + }); + it('should handle isPrimary correctly for loading when it is true', () => { + const { container } = render(); + expect(container.querySelector('.pf-m-primary')).toBeTruthy(); + }); + it('should handle isPrimary correctly for loading when it is false', () => { + const { container } = render(); + + expect(container.querySelector('.pf-m-primary')).toBeFalsy(); + }); + it('should handle isPrimary correctly for attachments when it is true', () => { + const { container } = render( + + ); + expect(container.querySelector('.pf-m-outline')).toBeTruthy(); + }); + it('should handle isPrimary correctly for attachments when it is false', () => { + const { container } = render( + + ); + expect(container.querySelector('.pf-m-outline')).toBeFalsy(); + }); }); diff --git a/packages/module/src/Message/Message.tsx b/packages/module/src/Message/Message.tsx index f0e95d1e3..b51c8cc85 100644 --- a/packages/module/src/Message/Message.tsx +++ b/packages/module/src/Message/Message.tsx @@ -189,6 +189,8 @@ export interface MessageProps extends Omit, 'role'> { toolCall?: ToolCallProps; /** Whether user messages default to stripping out images in markdown */ hasNoImagesInUserMessages?: boolean; + /** Sets background colors to be appropriate on primary chatbot background */ + isPrimary?: boolean; } export const MessageBase: FunctionComponent = ({ @@ -236,6 +238,7 @@ export const MessageBase: FunctionComponent = ({ remarkGfmProps, toolCall, hasNoImagesInUserMessages = true, + isPrimary, ...props }: MessageProps) => { const [messageText, setMessageText] = useState(content); @@ -286,13 +289,13 @@ export const MessageBase: FunctionComponent = ({ p: (props) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { node, ...rest } = props; - return ; + return ; }, code: ({ children, ...props }) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { node, ...codeProps } = props; return ( - + {children} ); @@ -348,7 +351,7 @@ export const MessageBase: FunctionComponent = ({ return ; }, // table requires node attribute for calculating headers for mobile breakpoint - table: (props) => , + table: (props) => , tbody: (props) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { node, ...rest } = props; @@ -416,7 +419,7 @@ export const MessageBase: FunctionComponent = ({ const renderMessage = () => { if (isLoading) { - return ; + return ; } if (isEditable) { return ( @@ -522,6 +525,7 @@ export const MessageBase: FunctionComponent = ({ closeButtonAriaLabel={attachment.closeButtonAriaLabel} languageTestId={attachment.languageTestId} spinnerTestId={attachment.spinnerTestId} + variant={isPrimary ? 'outline' : undefined} /> ))} diff --git a/packages/module/src/Message/MessageLoading.scss b/packages/module/src/Message/MessageLoading.scss index c53385f3b..bfd955fb6 100644 --- a/packages/module/src/Message/MessageLoading.scss +++ b/packages/module/src/Message/MessageLoading.scss @@ -50,4 +50,8 @@ background-color: rgba(41, 41, 41, 0.25); } } + + &.pf-m-primary { + background-color: var(--pf-t--global--background--color--secondary--default); + } } diff --git a/packages/module/src/Message/MessageLoading.tsx b/packages/module/src/Message/MessageLoading.tsx index 3c7a2d164..96f8015fa 100644 --- a/packages/module/src/Message/MessageLoading.tsx +++ b/packages/module/src/Message/MessageLoading.tsx @@ -2,8 +2,8 @@ // Chatbot Main - Message - Processing // ============================================================================ -const MessageLoading = ({ loadingWord }) => ( -
+const MessageLoading = ({ loadingWord, isPrimary }) => ( +
{loadingWord} diff --git a/packages/module/src/Message/TableMessage/TableMessage.scss b/packages/module/src/Message/TableMessage/TableMessage.scss index 6a355e762..8f0583448 100644 --- a/packages/module/src/Message/TableMessage/TableMessage.scss +++ b/packages/module/src/Message/TableMessage/TableMessage.scss @@ -8,6 +8,10 @@ border-block-start: 0; } + &.pf-m-primary { + --pf-v6-c-table--BackgroundColor: var(--pf-t--global--background--color--secondary--default) !important; + } + tbody { border-radius: var(--pf-t--global--border--radius--small); } diff --git a/packages/module/src/Message/TableMessage/TableMessage.tsx b/packages/module/src/Message/TableMessage/TableMessage.tsx index 85d660a1c..befda02c4 100644 --- a/packages/module/src/Message/TableMessage/TableMessage.tsx +++ b/packages/module/src/Message/TableMessage/TableMessage.tsx @@ -19,7 +19,11 @@ export interface TableNode { type: string; } -const TableMessage = ({ children, ...props }: Omit & ExtraProps) => { +export interface TableMessageProps { + isPrimary?: boolean; +} + +const TableMessage = ({ children, isPrimary, ...props }: Omit & ExtraProps & TableMessageProps) => { const { className, ...rest } = props; // This allows us to parse the nested data we get back from the 3rd party Markdown parser @@ -72,7 +76,7 @@ const TableMessage = ({ children, ...props }: Omit & ExtraPro {modifyChildren(children)} diff --git a/packages/module/src/Message/TextMessage/TextMessage.scss b/packages/module/src/Message/TextMessage/TextMessage.scss index ffef33748..b93e9405e 100644 --- a/packages/module/src/Message/TextMessage/TextMessage.scss +++ b/packages/module/src/Message/TextMessage/TextMessage.scss @@ -47,6 +47,12 @@ white-space: nowrap; width: 1px; } + + &.pf-m-primary { + code { + background-color: var(--pf-t--global--background--color--secondary--default); + } + } } // ============================================================================ diff --git a/packages/module/src/Message/TextMessage/TextMessage.tsx b/packages/module/src/Message/TextMessage/TextMessage.tsx index b09a364a8..6b8a23fa5 100644 --- a/packages/module/src/Message/TextMessage/TextMessage.tsx +++ b/packages/module/src/Message/TextMessage/TextMessage.tsx @@ -5,8 +5,17 @@ import { ExtraProps } from 'react-markdown'; import { Content, ContentProps } from '@patternfly/react-core'; -const TextMessage = ({ component, children, ...props }: Omit & ExtraProps) => ( - +export interface TextMessageProps { + isPrimary?: boolean; +} + +const TextMessage = ({ + component, + children, + isPrimary, + ...props +}: Omit & ExtraProps & TextMessageProps) => ( + {children}