ThinkingContent.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import React, { useEffect, useRef } from 'react';
  2. import { Typography } from '@douyinfe/semi-ui';
  3. import MarkdownRenderer from '../common/markdown/MarkdownRenderer';
  4. import { ChevronRight, ChevronUp, Brain, Loader2 } from 'lucide-react';
  5. import { useTranslation } from 'react-i18next';
  6. const ThinkingContent = ({
  7. message,
  8. finalExtractedThinkingContent,
  9. thinkingSource,
  10. styleState,
  11. onToggleReasoningExpansion
  12. }) => {
  13. const { t } = useTranslation();
  14. const scrollRef = useRef(null);
  15. const lastContentRef = useRef('');
  16. const isThinkingStatus = message.status === 'loading' || message.status === 'incomplete';
  17. const headerText = (isThinkingStatus && !message.isThinkingComplete) ? t('思考中...') : t('思考过程');
  18. useEffect(() => {
  19. if (scrollRef.current && finalExtractedThinkingContent && message.isReasoningExpanded) {
  20. scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  21. }
  22. }, [finalExtractedThinkingContent, message.isReasoningExpanded]);
  23. useEffect(() => {
  24. if (!isThinkingStatus) {
  25. lastContentRef.current = '';
  26. }
  27. }, [isThinkingStatus]);
  28. if (!finalExtractedThinkingContent) return null;
  29. let prevLength = 0;
  30. if (isThinkingStatus && lastContentRef.current) {
  31. if (finalExtractedThinkingContent.startsWith(lastContentRef.current)) {
  32. prevLength = lastContentRef.current.length;
  33. }
  34. }
  35. if (isThinkingStatus) {
  36. lastContentRef.current = finalExtractedThinkingContent;
  37. }
  38. return (
  39. <div className="rounded-xl sm:rounded-2xl mb-2 sm:mb-4 overflow-hidden shadow-sm backdrop-blur-sm">
  40. <div
  41. className="flex items-center justify-between p-3 cursor-pointer hover:bg-gradient-to-r hover:from-white/20 hover:to-purple-50/30 transition-all"
  42. style={{
  43. background: 'linear-gradient(135deg, #4c1d95 0%, #6d28d9 50%, #7c3aed 100%)',
  44. position: 'relative'
  45. }}
  46. onClick={() => onToggleReasoningExpansion(message.id)}
  47. >
  48. <div className="absolute inset-0 overflow-hidden">
  49. <div className="absolute -top-10 -right-10 w-40 h-40 bg-white opacity-5 rounded-full"></div>
  50. <div className="absolute -bottom-8 -left-8 w-24 h-24 bg-white opacity-10 rounded-full"></div>
  51. </div>
  52. <div className="flex items-center gap-2 sm:gap-4 relative">
  53. <div className="w-6 h-6 sm:w-8 sm:h-8 rounded-full bg-white/20 flex items-center justify-center shadow-lg">
  54. <Brain style={{ color: 'white' }} size={styleState.isMobile ? 12 : 16} />
  55. </div>
  56. <div className="flex flex-col">
  57. <Typography.Text strong style={{ color: 'white' }} className="text-sm sm:text-base">
  58. {headerText}
  59. </Typography.Text>
  60. {thinkingSource && (
  61. <Typography.Text style={{ color: 'white' }} className="text-xs mt-0.5 opacity-80 hidden sm:block">
  62. 来源: {thinkingSource}
  63. </Typography.Text>
  64. )}
  65. </div>
  66. </div>
  67. <div className="flex items-center gap-2 sm:gap-3 relative">
  68. {isThinkingStatus && !message.isThinkingComplete && (
  69. <div className="flex items-center gap-1 sm:gap-2">
  70. <Loader2 style={{ color: 'white' }} className="animate-spin" size={styleState.isMobile ? 14 : 18} />
  71. <Typography.Text style={{ color: 'white' }} className="text-xs sm:text-sm font-medium opacity-90">
  72. 思考中
  73. </Typography.Text>
  74. </div>
  75. )}
  76. {(!isThinkingStatus || message.isThinkingComplete) && (
  77. <div className="w-5 h-5 sm:w-6 sm:h-6 rounded-full bg-white/20 flex items-center justify-center">
  78. {message.isReasoningExpanded ?
  79. <ChevronUp size={styleState.isMobile ? 12 : 16} style={{ color: 'white' }} /> :
  80. <ChevronRight size={styleState.isMobile ? 12 : 16} style={{ color: 'white' }} />
  81. }
  82. </div>
  83. )}
  84. </div>
  85. </div>
  86. <div
  87. className={`transition-all duration-500 ease-out ${message.isReasoningExpanded ? 'max-h-96 opacity-100' : 'max-h-0 opacity-0'
  88. } overflow-hidden bg-gradient-to-br from-purple-50 via-indigo-50 to-violet-50`}
  89. >
  90. {message.isReasoningExpanded && (
  91. <div className="p-3 sm:p-5 pt-2 sm:pt-4">
  92. <div
  93. ref={scrollRef}
  94. className="bg-white/70 backdrop-blur-sm rounded-lg sm:rounded-xl p-2 shadow-inner overflow-x-auto overflow-y-auto thinking-content-scroll"
  95. style={{
  96. maxHeight: '200px',
  97. scrollbarWidth: 'thin',
  98. scrollbarColor: 'rgba(0, 0, 0, 0.3) transparent'
  99. }}
  100. >
  101. <div className="prose prose-xs sm:prose-sm prose-purple max-w-none text-xs sm:text-sm">
  102. <MarkdownRenderer
  103. content={finalExtractedThinkingContent}
  104. className=""
  105. animated={isThinkingStatus}
  106. previousContentLength={prevLength}
  107. />
  108. </div>
  109. </div>
  110. </div>
  111. )}
  112. </div>
  113. </div>
  114. );
  115. };
  116. export default ThinkingContent;