chatBoxContent.tsx 3.7 KB

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