import { LikeOutlined, LikeFilled, DislikeOutlined, DislikeFilled, CopyOutlined } from '@ant-design/icons';
import { Button, Card, Spin, Tooltip } from 'antd';
import { useState } from 'react';
import type { ChatItem, Feedbacktype } from '~/api/dify-chat';
import '../../styles/components/chat-with-llm/chat-message.css';
import { parseMessageContent } from '../../utils/message-parser';
import Markdown, { SourcesPanel } from './markdown';
import ThinkingBlock from './thinking-block';
import ThoughtProcess from './thought-process';
interface ChatMessageProps {
message: ChatItem;
onFeedback?: (messageId: string, feedback: Feedbacktype) => void;
isResponding?: boolean;
onRegenerate?: (messageId: string) => void;
onSuggestedQuestionClick?: (question: string) => void;
}
/**
* 聊天消息组件
*/
export default function ChatMessage({
message,
onFeedback,
isResponding = false,
onRegenerate,
onSuggestedQuestionClick,
}: ChatMessageProps) {
const [feedback, setFeedback] = useState<'like' | 'dislike' | null>(
message.feedback?.rating || null
);
const [copied, setCopied] = useState(false);
/**
* 处理复制
*/
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(content);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error('复制失败:', err);
}
};
const { id, content, isAnswer, agent_thoughts, message_files, isOpeningStatement, suggestedQuestions, more, retriever_resources } = message;
const isAgentMode = !!agent_thoughts && agent_thoughts.length > 0;
/**
* 处理反馈
*/
const handleFeedback = async (type: 'like' | 'dislike') => {
if (!id || id.startsWith('placeholder-') || id.startsWith('question-') || id.startsWith('opening-'))
return;
// 如果已经选择了相同的反馈,则取消选择
const newFeedback = feedback === type ? null : type;
setFeedback(newFeedback);
await onFeedback?.(id, {
rating: newFeedback,
});
};
/**
* 渲染AI回答内容
*/
const renderAnswerContent = () => {
// console.log('🎨 渲染AI回答内容:', { content, isResponding, isAgentMode });
// 如果正在响应且没有内容
if (isResponding && (isAgentMode ? (!content && (agent_thoughts || []).filter(item => !!item.thought || !!item.tool).length === 0) : !content)) {
return (
AI 正在思考...
);
}
// Agent模式(有思考过程)
if (isAgentMode) {
return (
{agent_thoughts?.map((thought, index) => (
{thought.thought && (
)}
{thought.tool && (
)}
))}
);
}
// 普通模式 - 解析内容,检查是否包含思考过程
const parsed = parseMessageContent(content);
return (
{/* 思考过程区域 */}
{parsed.hasThinking && (
)}
{/* 实际回复内容 */}
{parsed.response && (
)}
);
};
/**
* 渲染建议问题(继续探索)
*/
const renderSuggestedQuestions = () => {
if (!suggestedQuestions || suggestedQuestions.length === 0) return null;
return (
继续探索
{suggestedQuestions.map((question, index) => (
))}
);
};
/**
* 渲染消息元信息
*/
const renderMessageMeta = () => {
if (!more) return null;
return (
{more.time && 时间: {more.time}}
{more.tokens && Token: {more.tokens}}
{more.latency && 延迟: {more.latency}}
);
};
// 如果是开场白,特殊处理
if (isOpeningStatement) {
return (
{renderSuggestedQuestions()}
);
}
// 判断是否可以显示反馈按钮(AI回答、非占位符、非正在响应)
const canShowFeedback = isAnswer &&
!id.startsWith('placeholder-') &&
!id.startsWith('opening-') &&
!id.startsWith('response-') &&
!isResponding;
return (
{/* 消息内容 */}
{isAnswer ? (
<>
{renderAnswerContent()}
{!isResponding && renderSuggestedQuestions()}
>
) : (
)}
{/* 反馈按钮 - 仅在AI回答且非占位符时显示 */}
{canShowFeedback && (
}
onClick={handleCopy}
className={copied ? 'message-copy-btn copied' : 'message-copy-btn'}
/>
: }
onClick={() => handleFeedback('like')}
className={feedback === 'like' ? 'feedback-active-like' : ''}
/>
: }
onClick={() => handleFeedback('dislike')}
className={feedback === 'dislike' ? 'feedback-active-dislike' : ''}
/>
)}
{/* 引用来源面板 - 放在气泡外面 */}
{isAnswer && retriever_resources && retriever_resources.length > 0 && (
)}
);
}