chatBoxContent.tsx 3.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. import React, { ReactElement, ReactNode, useMemo } from 'react';
  2. import cls from 'classnames';
  3. import { Message, Metadata, RenderContentProps, MarkdownRenderProps } from '../interface';
  4. import MarkdownRender from '../../markdownRender';
  5. import { cssClasses, strings } from '@douyinfe/semi-foundation/chat/constants';
  6. import { MDXProps } from 'mdx/types';
  7. import { FileAttachment, ImageAttachment } from '../attachment';
  8. import Code from './code';
  9. const { PREFIX_CHAT_BOX } = cssClasses;
  10. const { MESSAGE_STATUS, MODE, ROLE } = strings;
  11. interface ChatBoxContentProps {
  12. mode?: 'bubble' | 'noBubble' | 'userBubble';
  13. customMarkDownComponents?: MDXProps['components'];
  14. children?: string;
  15. role?: Metadata;
  16. message?: Message;
  17. customRenderFunc?: (props: RenderContentProps) => ReactNode;
  18. markdownRenderProps?: MarkdownRenderProps
  19. }
  20. const ChatBoxContent = (props: ChatBoxContentProps) => {
  21. const { message = {}, customRenderFunc, role: roleInfo, customMarkDownComponents, mode, markdownRenderProps } = props;
  22. const { content, role, status } = message;
  23. const markdownComponents = useMemo(() => ({
  24. 'code': Code,
  25. 'SemiFile': FileAttachment,
  26. 'img': ImageAttachment,
  27. ...customMarkDownComponents
  28. }), [customMarkDownComponents]);
  29. const wrapCls = useMemo(() => {
  30. const isUser = role === ROLE.USER;
  31. const bubble = mode === MODE.BUBBLE;
  32. const userBubble = mode === MODE.USER_BUBBLE && isUser;
  33. return cls(`${PREFIX_CHAT_BOX}-content`, {
  34. [`${PREFIX_CHAT_BOX}-content-${mode}`]: bubble || userBubble,
  35. [`${PREFIX_CHAT_BOX}-content-user`]: (bubble && isUser) || userBubble,
  36. [`${PREFIX_CHAT_BOX}-content-error`]: status === MESSAGE_STATUS.ERROR && (bubble || userBubble)
  37. });
  38. }, [role, status, mode]);
  39. const node = useMemo(() => {
  40. if (status === MESSAGE_STATUS.LOADING) {
  41. return <span className={`${PREFIX_CHAT_BOX}-content-loading`} >
  42. <span className={`${PREFIX_CHAT_BOX}-content-loading-item`} />
  43. </span>;
  44. } else {
  45. let realContent;
  46. if (typeof content === 'string') {
  47. realContent = <MarkdownRender
  48. format='md'
  49. raw={content}
  50. components={markdownComponents as any}
  51. {...markdownRenderProps}
  52. />;
  53. } else if (Array.isArray(content)) {
  54. realContent = content.map((item, index) => {
  55. if (item.type === 'text') {
  56. return <MarkdownRender
  57. key={`index`}
  58. format='md'
  59. raw={item.text}
  60. components={markdownComponents as any}
  61. {...markdownRenderProps}
  62. />;
  63. } else if (item.type === 'image_url') {
  64. return <ImageAttachment key={`index`} src={item.image_url.url} />;
  65. } else if (item.type === 'file_url') {
  66. const { name, size, url, type } = item.file_url;
  67. const realType = name.split('.').pop() ?? type?.split('/').pop();
  68. return <FileAttachment key={`index`} url={url} name={name} size={size} type={realType}></FileAttachment>;
  69. }
  70. return null;
  71. });
  72. }
  73. return (<>
  74. {realContent}
  75. </>);
  76. }
  77. }, [status, content]);
  78. if (customRenderFunc) {
  79. return customRenderFunc({
  80. message,
  81. role: roleInfo,
  82. defaultContent: node,
  83. className: wrapCls,
  84. }) as ReactElement;
  85. } else {
  86. return <div className={wrapCls}>{node}</div>;
  87. }
  88. };
  89. export default ChatBoxContent;