| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- /*
- Copyright (C) 2025 QuantumNous
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
- For commercial licensing, please contact support@quantumnous.com
- */
- import React, { useEffect, useRef } from 'react';
- import { Typography } from '@douyinfe/semi-ui';
- import MarkdownRenderer from '../common/markdown/MarkdownRenderer';
- import { ChevronRight, ChevronUp, Brain, Loader2 } from 'lucide-react';
- import { useTranslation } from 'react-i18next';
- const ThinkingContent = ({
- message,
- finalExtractedThinkingContent,
- thinkingSource,
- styleState,
- onToggleReasoningExpansion,
- }) => {
- const { t } = useTranslation();
- const scrollRef = useRef(null);
- const lastContentRef = useRef('');
- const isThinkingStatus =
- message.status === 'loading' || message.status === 'incomplete';
- const headerText =
- isThinkingStatus && !message.isThinkingComplete
- ? t('思考中...')
- : t('思考过程');
- useEffect(() => {
- if (
- scrollRef.current &&
- finalExtractedThinkingContent &&
- message.isReasoningExpanded
- ) {
- scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
- }
- }, [finalExtractedThinkingContent, message.isReasoningExpanded]);
- useEffect(() => {
- if (!isThinkingStatus) {
- lastContentRef.current = '';
- }
- }, [isThinkingStatus]);
- if (!finalExtractedThinkingContent) return null;
- let prevLength = 0;
- if (isThinkingStatus && lastContentRef.current) {
- if (finalExtractedThinkingContent.startsWith(lastContentRef.current)) {
- prevLength = lastContentRef.current.length;
- }
- }
- if (isThinkingStatus) {
- lastContentRef.current = finalExtractedThinkingContent;
- }
- return (
- <div className='rounded-xl sm:rounded-2xl mb-2 sm:mb-4 overflow-hidden shadow-sm backdrop-blur-sm'>
- <div
- 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'
- style={{
- background:
- 'linear-gradient(135deg, #4c1d95 0%, #6d28d9 50%, #7c3aed 100%)',
- position: 'relative',
- }}
- onClick={() => onToggleReasoningExpansion(message.id)}
- >
- <div className='absolute inset-0 overflow-hidden'>
- <div className='absolute -top-10 -right-10 w-40 h-40 bg-white opacity-5 rounded-full'></div>
- <div className='absolute -bottom-8 -left-8 w-24 h-24 bg-white opacity-10 rounded-full'></div>
- </div>
- <div className='flex items-center gap-2 sm:gap-4 relative'>
- <div className='w-6 h-6 sm:w-8 sm:h-8 rounded-full bg-white/20 flex items-center justify-center shadow-lg'>
- <Brain
- style={{ color: 'white' }}
- size={styleState.isMobile ? 12 : 16}
- />
- </div>
- <div className='flex flex-col'>
- <Typography.Text
- strong
- style={{ color: 'white' }}
- className='text-sm sm:text-base'
- >
- {headerText}
- </Typography.Text>
- {thinkingSource && (
- <Typography.Text
- style={{ color: 'white' }}
- className='text-xs mt-0.5 opacity-80 hidden sm:block'
- >
- 来源: {thinkingSource}
- </Typography.Text>
- )}
- </div>
- </div>
- <div className='flex items-center gap-2 sm:gap-3 relative'>
- {isThinkingStatus && !message.isThinkingComplete && (
- <div className='flex items-center gap-1 sm:gap-2'>
- <Loader2
- style={{ color: 'white' }}
- className='animate-spin'
- size={styleState.isMobile ? 14 : 18}
- />
- <Typography.Text
- style={{ color: 'white' }}
- className='text-xs sm:text-sm font-medium opacity-90'
- >
- 思考中
- </Typography.Text>
- </div>
- )}
- {(!isThinkingStatus || message.isThinkingComplete) && (
- <div className='w-5 h-5 sm:w-6 sm:h-6 rounded-full bg-white/20 flex items-center justify-center'>
- {message.isReasoningExpanded ? (
- <ChevronUp
- size={styleState.isMobile ? 12 : 16}
- style={{ color: 'white' }}
- />
- ) : (
- <ChevronRight
- size={styleState.isMobile ? 12 : 16}
- style={{ color: 'white' }}
- />
- )}
- </div>
- )}
- </div>
- </div>
- <div
- className={`transition-all duration-500 ease-out ${
- message.isReasoningExpanded
- ? 'max-h-96 opacity-100'
- : 'max-h-0 opacity-0'
- } overflow-hidden bg-gradient-to-br from-purple-50 via-indigo-50 to-violet-50`}
- >
- {message.isReasoningExpanded && (
- <div className='p-3 sm:p-5 pt-2 sm:pt-4'>
- <div
- ref={scrollRef}
- 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'
- style={{
- maxHeight: '200px',
- scrollbarWidth: 'thin',
- scrollbarColor: 'rgba(0, 0, 0, 0.3) transparent',
- }}
- >
- <div className='prose prose-xs sm:prose-sm prose-purple max-w-none text-xs sm:text-sm'>
- <MarkdownRenderer
- content={finalExtractedThinkingContent}
- className=''
- animated={isThinkingStatus}
- previousContentLength={prevLength}
- />
- </div>
- </div>
- </div>
- )}
- </div>
- </div>
- );
- };
- export default ThinkingContent;
|