/*
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 .
For commercial licensing, please contact support@quantumnous.com
*/
import React, { useRef, useEffect } from 'react';
import { Typography, TextArea, Button } from '@douyinfe/semi-ui';
import MarkdownRenderer from '../common/markdown/MarkdownRenderer';
import ThinkingContent from './ThinkingContent';
import { Loader2, Check, X } from 'lucide-react';
import { useTranslation } from 'react-i18next';
const MessageContent = ({
message,
className,
styleState,
onToggleReasoningExpansion,
isEditing = false,
onEditSave,
onEditCancel,
editValue,
onEditValueChange,
}) => {
const { t } = useTranslation();
const previousContentLengthRef = useRef(0);
const lastContentRef = useRef('');
const isThinkingStatus =
message.status === 'loading' || message.status === 'incomplete';
useEffect(() => {
if (!isThinkingStatus) {
previousContentLengthRef.current = 0;
lastContentRef.current = '';
}
}, [isThinkingStatus]);
if (message.status === 'error') {
let errorText;
if (Array.isArray(message.content)) {
const textContent = message.content.find((item) => item.type === 'text');
errorText =
textContent && textContent.text && typeof textContent.text === 'string'
? textContent.text
: t('请求发生错误');
} else if (typeof message.content === 'string') {
errorText = message.content;
} else {
errorText = t('请求发生错误');
}
return (
{errorText}
);
}
let currentExtractedThinkingContent = null;
let currentDisplayableFinalContent = '';
let thinkingSource = null;
const getTextContent = (content) => {
if (Array.isArray(content)) {
const textItem = content.find((item) => item.type === 'text');
return textItem && textItem.text && typeof textItem.text === 'string'
? textItem.text
: '';
} else if (typeof content === 'string') {
return content;
}
return '';
};
currentDisplayableFinalContent = getTextContent(message.content);
if (message.role === 'assistant') {
let baseContentForDisplay = getTextContent(message.content);
let combinedThinkingContent = '';
if (message.reasoningContent) {
combinedThinkingContent = message.reasoningContent;
thinkingSource = 'reasoningContent';
}
if (baseContentForDisplay.includes('')) {
const thinkTagRegex = /([\s\S]*?)<\/think>/g;
let match;
let thoughtsFromPairedTags = [];
let replyParts = [];
let lastIndex = 0;
while ((match = thinkTagRegex.exec(baseContentForDisplay)) !== null) {
replyParts.push(
baseContentForDisplay.substring(lastIndex, match.index),
);
thoughtsFromPairedTags.push(match[1]);
lastIndex = match.index + match[0].length;
}
replyParts.push(baseContentForDisplay.substring(lastIndex));
if (thoughtsFromPairedTags.length > 0) {
const pairedThoughtsStr = thoughtsFromPairedTags.join('\n\n---\n\n');
if (combinedThinkingContent) {
combinedThinkingContent += '\n\n---\n\n' + pairedThoughtsStr;
} else {
combinedThinkingContent = pairedThoughtsStr;
}
thinkingSource = thinkingSource
? thinkingSource + ' & tags'
: ' tags';
}
baseContentForDisplay = replyParts.join('');
}
if (isThinkingStatus) {
const lastOpenThinkIndex = baseContentForDisplay.lastIndexOf('');
if (lastOpenThinkIndex !== -1) {
const fragmentAfterLastOpen =
baseContentForDisplay.substring(lastOpenThinkIndex);
if (!fragmentAfterLastOpen.includes('')) {
const unclosedThought = fragmentAfterLastOpen
.substring(''.length)
.trim();
if (unclosedThought) {
if (combinedThinkingContent) {
combinedThinkingContent += '\n\n---\n\n' + unclosedThought;
} else {
combinedThinkingContent = unclosedThought;
}
thinkingSource = thinkingSource
? thinkingSource + ' + streaming '
: 'streaming ';
}
baseContentForDisplay = baseContentForDisplay.substring(
0,
lastOpenThinkIndex,
);
}
}
}
currentExtractedThinkingContent = combinedThinkingContent || null;
currentDisplayableFinalContent = baseContentForDisplay
.replace(/<\/?think>/g, '')
.trim();
}
const finalExtractedThinkingContent = currentExtractedThinkingContent;
const finalDisplayableFinalContent = currentDisplayableFinalContent;
if (
message.role === 'assistant' &&
isThinkingStatus &&
!finalExtractedThinkingContent &&
(!finalDisplayableFinalContent ||
finalDisplayableFinalContent.trim() === '')
) {
return (
);
}
return (
{message.role === 'system' && (
)}
{message.role === 'assistant' && (
)}
{isEditing ? (
) : (
(() => {
if (Array.isArray(message.content)) {
const textContent = message.content.find(
(item) => item.type === 'text',
);
const imageContents = message.content.filter(
(item) => item.type === 'image_url',
);
return (
{imageContents.length > 0 && (
{imageContents.map((imgItem, index) => (

{
e.target.style.display = 'none';
e.target.nextSibling.style.display = 'block';
}}
/>
图片加载失败: {imgItem.image_url.url}
))}
)}
{textContent &&
textContent.text &&
typeof textContent.text === 'string' &&
textContent.text.trim() !== '' && (
)}
);
}
if (typeof message.content === 'string') {
if (message.role === 'assistant') {
if (
finalDisplayableFinalContent &&
finalDisplayableFinalContent.trim() !== ''
) {
// 获取上一次的内容长度
let prevLength = 0;
if (isThinkingStatus && lastContentRef.current) {
// 只有当前内容包含上一次内容时,才使用上一次的长度
if (
finalDisplayableFinalContent.startsWith(
lastContentRef.current,
)
) {
prevLength = lastContentRef.current.length;
}
}
// 更新最后内容的引用
if (isThinkingStatus) {
lastContentRef.current = finalDisplayableFinalContent;
}
return (
);
}
} else {
return (
);
}
}
return null;
})()
)}
);
};
export default MessageContent;