更新AI聊天页面样式
This commit is contained in:
@@ -1,208 +1,179 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Button, Tooltip, Avatar, Card, Image, Spin } from 'antd';
|
||||
import { AntDesignOutlined, SmileTwoTone, RobotOutlined } from '@ant-design/icons';
|
||||
import type { ChatItem, Feedbacktype, ThoughtItem, VisionFile } from '../../types/dify_chat';
|
||||
import { CHAT_CONFIG } from '../../config/chat';
|
||||
import Markdown from './markdown';
|
||||
import ThoughtProcess from './thought-process';
|
||||
import { Dayjs } from 'dayjs';
|
||||
import '../../styles/components/chat-with-llm/chat-message.css';
|
||||
|
||||
interface ChatMessageProps {
|
||||
message: ChatItem;
|
||||
onFeedback?: (messageId: string, feedback: Feedbacktype) => void;
|
||||
isResponding?: boolean;
|
||||
onRegenerate?: (messageId: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天消息组件
|
||||
*/
|
||||
export default function ChatMessage({
|
||||
message,
|
||||
onFeedback,
|
||||
isResponding = false,
|
||||
onRegenerate
|
||||
}: ChatMessageProps) {
|
||||
const [feedback, setFeedback] = useState<'like' | 'dislike' | null>(
|
||||
message.feedback?.rating || null
|
||||
);
|
||||
|
||||
const { id, content, isAnswer, agent_thoughts, message_files, isOpeningStatement, suggestedQuestions, more } = 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 (
|
||||
<div className="responding-indicator">
|
||||
<Spin size="small" />
|
||||
<span>AI 正在思考...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Agent模式(有思考过程)
|
||||
if (isAgentMode) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{agent_thoughts?.map((thought, index) => (
|
||||
<div key={index}>
|
||||
{thought.thought && (
|
||||
<div className={isResponding && index === agent_thoughts.length - 1 ? 'streaming-text' : ''}>
|
||||
<Markdown content={thought.thought} />
|
||||
</div>
|
||||
)}
|
||||
{thought.tool && (
|
||||
<ThoughtProcess
|
||||
thought={thought}
|
||||
isFinished={!!thought.observation || !isResponding}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 普通模式 - 恢复Markdown渲染
|
||||
return (
|
||||
<div>
|
||||
<div className={isResponding ? 'streaming-text' : ''}>
|
||||
<Markdown content={content} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 渲染建议问题
|
||||
*/
|
||||
const renderSuggestedQuestions = () => {
|
||||
if (!suggestedQuestions || suggestedQuestions.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="suggested-questions">
|
||||
<div className="text-sm text-gray-500 mb-2">建议问题:</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{suggestedQuestions.map((question, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
size="small"
|
||||
type="dashed"
|
||||
className="question-button text-left"
|
||||
onClick={() => {
|
||||
// 这里可以添加点击建议问题的处理逻辑
|
||||
// console.log('Suggested question clicked:', question);
|
||||
}}
|
||||
>
|
||||
{question}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 渲染消息元信息
|
||||
*/
|
||||
const renderMessageMeta = () => {
|
||||
if (!more) return null;
|
||||
|
||||
return (
|
||||
<div className="message-timestamp">
|
||||
{more.time && <span>时间: {more.time}</span>}
|
||||
{more.tokens && <span className="ml-2">Token: {more.tokens}</span>}
|
||||
{more.latency && <span className="ml-2">延迟: {more.latency}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 如果是开场白,特殊处理
|
||||
if (isOpeningStatement) {
|
||||
return (
|
||||
<div className="chat-message">
|
||||
<Card className="message-card assistant" size="small">
|
||||
<div className="flex items-start gap-3">
|
||||
{/* <Avatar icon={<RobotOutlined />} className="flex-shrink-0" />
|
||||
*/}
|
||||
<Avatar
|
||||
size={{ xs: 24, sm: 32, md: 40, lg: 64, xl: 80, xxl: 100 }}
|
||||
icon={<RobotOutlined />}
|
||||
/>
|
||||
<div className="flex-1">
|
||||
<Markdown content={content} />
|
||||
{renderSuggestedQuestions()}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="chat-message">
|
||||
<div className={`flex ${isAnswer ? 'justify-start' : 'justify-end'} mb-2`}>
|
||||
<div className={`message-card ${isAnswer ? 'assistant' : 'user'}`}>
|
||||
<Card size="small" className="shadow-sm">
|
||||
<div className="flex items-start gap-2">
|
||||
{/* 头像 */}
|
||||
<Avatar
|
||||
icon={isAnswer ? <RobotOutlined /> : <SmileTwoTone />}
|
||||
className="flex-shrink-0"
|
||||
style={{
|
||||
backgroundColor: isAnswer ? '#1890ff' : '#52c41a'
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 消息内容 */}
|
||||
<div className="flex-1 min-w-0">
|
||||
{isAnswer ? renderAnswerContent() : (
|
||||
<div>
|
||||
<Markdown content={content} />
|
||||
{/* {renderImages(message_files)} */}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 建议问题 */}
|
||||
{/* {isAnswer && renderSuggestedQuestions()} */}
|
||||
|
||||
{/* 消息元信息 */}
|
||||
{/* {renderMessageMeta()} */}
|
||||
|
||||
{/* 反馈按钮 */}
|
||||
{/* {isAnswer && (
|
||||
<div className="feedback-buttons">
|
||||
{renderFeedbackButtons()}
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
import React, { useState } from 'react';
|
||||
import { Button, Card, Spin } from 'antd';
|
||||
import type { ChatItem, Feedbacktype, ThoughtItem, VisionFile } from '../../types/dify_chat';
|
||||
import { CHAT_CONFIG } from '../../config/chat';
|
||||
import Markdown from './markdown';
|
||||
import ThoughtProcess from './thought-process';
|
||||
import { Dayjs } from 'dayjs';
|
||||
import '../../styles/components/chat-with-llm/chat-message.css';
|
||||
|
||||
interface ChatMessageProps {
|
||||
message: ChatItem;
|
||||
onFeedback?: (messageId: string, feedback: Feedbacktype) => void;
|
||||
isResponding?: boolean;
|
||||
onRegenerate?: (messageId: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 聊天消息组件
|
||||
*/
|
||||
export default function ChatMessage({
|
||||
message,
|
||||
onFeedback,
|
||||
isResponding = false,
|
||||
onRegenerate
|
||||
}: ChatMessageProps) {
|
||||
const [feedback, setFeedback] = useState<'like' | 'dislike' | null>(
|
||||
message.feedback?.rating || null
|
||||
);
|
||||
|
||||
const { id, content, isAnswer, agent_thoughts, message_files, isOpeningStatement, suggestedQuestions, more } = 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 (
|
||||
<div className="responding-indicator">
|
||||
<Spin size="small" />
|
||||
<span>AI 正在思考...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Agent模式(有思考过程)
|
||||
if (isAgentMode) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{agent_thoughts?.map((thought, index) => (
|
||||
<div key={index}>
|
||||
{thought.thought && (
|
||||
<div className={isResponding && index === agent_thoughts.length - 1 ? 'streaming-text' : ''}>
|
||||
<Markdown content={thought.thought} />
|
||||
</div>
|
||||
)}
|
||||
{thought.tool && (
|
||||
<ThoughtProcess
|
||||
thought={thought}
|
||||
isFinished={!!thought.observation || !isResponding}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 普通模式 - 恢复Markdown渲染
|
||||
return (
|
||||
<div>
|
||||
<div className={isResponding ? 'streaming-text' : ''}>
|
||||
<Markdown content={content} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 渲染建议问题
|
||||
*/
|
||||
const renderSuggestedQuestions = () => {
|
||||
if (!suggestedQuestions || suggestedQuestions.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="suggested-questions">
|
||||
<div className="text-sm text-gray-500 mb-2">建议问题:</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{suggestedQuestions.map((question, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
size="small"
|
||||
type="dashed"
|
||||
className="question-button text-left"
|
||||
onClick={() => {
|
||||
// 这里可以添加点击建议问题的处理逻辑
|
||||
// console.log('Suggested question clicked:', question);
|
||||
}}
|
||||
>
|
||||
{question}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 渲染消息元信息
|
||||
*/
|
||||
const renderMessageMeta = () => {
|
||||
if (!more) return null;
|
||||
|
||||
return (
|
||||
<div className="message-timestamp">
|
||||
{more.time && <span>时间: {more.time}</span>}
|
||||
{more.tokens && <span className="ml-2">Token: {more.tokens}</span>}
|
||||
{more.latency && <span className="ml-2">延迟: {more.latency}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 如果是开场白,特殊处理
|
||||
if (isOpeningStatement) {
|
||||
return (
|
||||
<div className="chat-message">
|
||||
<Card className="message-card assistant" size="small">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex-1">
|
||||
<Markdown content={content} />
|
||||
{renderSuggestedQuestions()}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="chat-message">
|
||||
<div className={`flex ${isAnswer ? 'justify-start' : 'justify-end'} mb-2`}>
|
||||
<div className={`message-card ${isAnswer ? 'assistant' : 'user'}`}>
|
||||
<Card size="small" className="shadow-sm">
|
||||
<div className="flex items-start gap-2">
|
||||
{/* 消息内容 */}
|
||||
<div className="flex-1 min-w-0">
|
||||
{isAnswer ? renderAnswerContent() : (
|
||||
<div>
|
||||
<Markdown content={content} />
|
||||
{/* {renderImages(message_files)} */}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user