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 && (
)}
{/* 引用来源面板 - 放在气泡外面 */} {isAnswer && retriever_resources && retriever_resources.length > 0 && (
)} ); }