MessageBs.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. import { AvatarIcon } from "@/components/bs-icons/avatar";
  2. import { LoadIcon } from "@/components/bs-icons/loading";
  3. import { CodeBlock } from "@/modals/formModal/chatMessage/codeBlock";
  4. import { ChatMessageType } from "@/types/chat";
  5. import { copyText } from "@/utils";
  6. import { useMemo, useRef } from "react";
  7. import ReactMarkdown from "react-markdown";
  8. import rehypeMathjax from "rehype-mathjax";
  9. import remarkGfm from "remark-gfm";
  10. import remarkMath from "remark-math";
  11. import MessageButtons from "./MessageButtons";
  12. import SourceEntry from "./SourceEntry";
  13. import { useMessageStore } from "./messageStore";
  14. // 颜色列表
  15. const colorList = [
  16. "#111",
  17. "#FF5733",
  18. "#3498DB",
  19. "#27AE60",
  20. "#E74C3C",
  21. "#9B59B6",
  22. "#F1C40F",
  23. "#34495E",
  24. "#16A085",
  25. "#E67E22",
  26. "#95A5A6"
  27. ]
  28. export default function MessageBs({ data, onUnlike = () => { }, onSource }: { data: ChatMessageType, onUnlike?: any, onSource?: any }) {
  29. const avatarColor = colorList[
  30. (data.sender?.split('').reduce((num, s) => num + s.charCodeAt(), 0) || 0) % colorList.length
  31. ]
  32. const mkdown = useMemo(
  33. () => (
  34. <ReactMarkdown
  35. remarkPlugins={[remarkGfm, remarkMath]}
  36. rehypePlugins={[rehypeMathjax]}
  37. linkTarget="_blank"
  38. className="bs-mkdown inline-block break-all max-w-full text-sm text-text-answer "
  39. components={{
  40. code: ({ node, inline, className, children, ...props }) => {
  41. if (children.length) {
  42. if (children[0] === "▍") {
  43. return (<span className="form-modal-markdown-span"> ▍ </span>);
  44. }
  45. children[0] = (children[0] as string).replace("`▍`", "▍");
  46. }
  47. const match = /language-(\w+)/.exec(className || "");
  48. return !inline ? (
  49. <CodeBlock
  50. key={Math.random()}
  51. language={(match && match[1]) || ""}
  52. value={String(children).replace(/\n$/, "")}
  53. {...props}
  54. />
  55. ) : (
  56. <code className={className} {...props}> {children} </code>
  57. );
  58. },
  59. }}
  60. >
  61. {data.message.toString()}
  62. </ReactMarkdown>
  63. ),
  64. [data.message, data.message.toString()]
  65. )
  66. const messageRef = useRef<HTMLDivElement>(null)
  67. const handleCopyMessage = () => {
  68. copyText(messageRef.current)
  69. }
  70. const chatId = useMessageStore(state => state.chatId)
  71. return <div className="flex w-full py-1">
  72. <div className="w-fit max-w-[90%]">
  73. {data.sender && <p className="text-gray-600 text-xs mb-2">{data.sender}</p>}
  74. <div className="ml-[14px] min-h-8 px-6 py-4 rounded-2xl bg-[#13110D] dark:bg-[#13110D] text-[#fff]">
  75. <div className="flex gap-2">
  76. <div className="w-6 h-6 min-w-6 flex justify-center items-center rounded-full" style={{ background: avatarColor }} ><AvatarIcon /></div>
  77. {data.message.toString() ?
  78. <div ref={messageRef} className="text-sm max-w-[calc(100%-24px)]">
  79. {mkdown}
  80. {/* @user */}
  81. {data.receiver && <p className="text-blue-500 text-sm">@ {data.receiver.user_name}</p>}
  82. {/* 光标 */}
  83. {/* {data.message.toString() && !data.end && <div className="animate-cursor absolute w-2 h-5 ml-1 bg-gray-600" style={{ left: cursor.x, top: cursor.y }}></div>} */}
  84. </div>
  85. : <div><LoadIcon className="text-gray-400" /></div>
  86. }
  87. </div>
  88. </div>
  89. {/* 附加信息 */}
  90. {
  91. !!data.id && data.end && <div className="flex justify-between mt-2">
  92. <SourceEntry
  93. extra={data.extra}
  94. end={data.end}
  95. source={data.source}
  96. className="pl-4"
  97. onSource={() => onSource?.({
  98. chatId,
  99. messageId: data.id,
  100. message: data.message || data.thought,
  101. })} />
  102. <MessageButtons
  103. id={data.id}
  104. data={data.liked}
  105. onUnlike={onUnlike}
  106. onCopy={handleCopyMessage}
  107. ></MessageButtons>
  108. </div>
  109. }
  110. </div>
  111. </div>
  112. };