ChatDialog.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import ModelIcon from '@/new-components/chat/content/ModelIcon';
  2. import MarkdownContext from '@/new-components/common/MarkdownContext';
  3. import { IChatDialogueMessageSchema } from '@/types/chat';
  4. import { Divider } from 'antd';
  5. import cls from 'classnames';
  6. import React, { memo, useContext, useMemo, useRef } from 'react';
  7. import { MobileChatContext } from '../';
  8. import Feedback from './Feedback';
  9. type DBGPTView = {
  10. name: string;
  11. status: 'todo' | 'runing' | 'failed' | 'completed' | (string & {});
  12. result?: string;
  13. err_msg?: string;
  14. };
  15. // 对话气泡
  16. const ChatDialog: React.FC<{
  17. message: IChatDialogueMessageSchema;
  18. index: number;
  19. }> = ({ message, index }) => {
  20. const { scene } = useContext(MobileChatContext);
  21. const { context, model_name, role, thinking } = message;
  22. // GPT回复
  23. const isRobot = useMemo(() => role === 'view', [role]);
  24. const chatDialogRef = useRef<HTMLDivElement>(null);
  25. const { value } = useMemo<{
  26. relations: string[];
  27. value: string;
  28. cachePluginContext: DBGPTView[];
  29. }>(() => {
  30. if (typeof context !== 'string') {
  31. return {
  32. relations: [],
  33. value: '',
  34. cachePluginContext: [],
  35. };
  36. }
  37. const [value, relation] = context.split('\trelations:');
  38. const relations = relation ? relation.split(',') : [];
  39. const cachePluginContext: DBGPTView[] = [];
  40. let cacheIndex = 0;
  41. const result = value.replace(/<dbgpt-view[^>]*>[^<]*<\/dbgpt-view>/gi, matchVal => {
  42. try {
  43. const pluginVal = matchVal.replaceAll('\n', '\\n').replace(/<[^>]*>|<\/[^>]*>/gm, '');
  44. const pluginContext = JSON.parse(pluginVal) as DBGPTView;
  45. const replacement = `<custom-view>${cacheIndex}</custom-view>`;
  46. cachePluginContext.push({
  47. ...pluginContext,
  48. result: formatMarkdownVal(pluginContext.result ?? ''),
  49. });
  50. cacheIndex++;
  51. return replacement;
  52. } catch (e) {
  53. console.log((e as any).message, e);
  54. return matchVal;
  55. }
  56. });
  57. return {
  58. relations,
  59. cachePluginContext,
  60. value: result,
  61. };
  62. }, [context]);
  63. const formatMarkdownVal = (val: string) => {
  64. return val
  65. .replaceAll('\\n', '\n')
  66. .replace(/<table(\w*=[^>]+)>/gi, '<table $1>')
  67. .replace(/<tr(\w*=[^>]+)>/gi, '<tr $1>');
  68. };
  69. const formatMarkdownValForAgent = (val: string) => {
  70. return val?.replace(/<table(\w*=[^>]+)>/gi, '<table $1>').replace(/<tr(\w*=[^>]+)>/gi, '<tr $1>');
  71. };
  72. return (
  73. <div
  74. className={cls('flex w-full', {
  75. 'justify-end': !isRobot,
  76. })}
  77. ref={chatDialogRef}
  78. >
  79. {/* 用户提问 */}
  80. {!isRobot && <div className='flex bg-[#0C75FC] text-white p-3 rounded-xl rounded-br-none'>{context}</div>}
  81. {isRobot && (
  82. <div className='flex max-w-full flex-col flex-wrap bg-white dark:bg-[rgba(255,255,255,0.16)] p-3 rounded-xl rounded-bl-none'>
  83. {typeof context === 'string' && scene === 'chat_agent' && (
  84. <MarkdownContext>{formatMarkdownValForAgent(value)}</MarkdownContext>
  85. )}
  86. {typeof context === 'string' && scene !== 'chat_agent' && (
  87. <MarkdownContext>{formatMarkdownVal(value)}</MarkdownContext>
  88. )}
  89. {/* 正在思考 */}
  90. {thinking && !context && (
  91. <div className='flex items-center gap-2'>
  92. <span className='flex text-sm text-[#1c2533] dark:text-white'>思考中</span>
  93. <div className='flex'>
  94. <div className='w-1 h-1 rounded-full mx-1 animate-pulse1'></div>
  95. <div className='w-1 h-1 rounded-full mx-1 animate-pulse2'></div>
  96. <div className='w-1 h-1 rounded-full mx-1 animate-pulse3'></div>
  97. </div>
  98. </div>
  99. )}
  100. {!thinking && <Divider className='my-2' />}
  101. <div
  102. className={cls('opacity-0 h-0 w-0', {
  103. 'opacity-100 flex items-center justify-between gap-6 w-auto h-auto': !thinking,
  104. })}
  105. >
  106. {/* 用户反馈 */}
  107. <Feedback content={message} index={index} chatDialogRef={chatDialogRef} />
  108. {scene !== 'chat_agent' && (
  109. <div className='flex gap-1 items-center'>
  110. <ModelIcon width={14} height={14} model={model_name} />
  111. <span className='text-xs text-gray-500'>{model_name}</span>
  112. </div>
  113. )}
  114. </div>
  115. </div>
  116. )}
  117. </div>
  118. );
  119. };
  120. export default memo(ChatDialog);