feat:dify聊天页面接入消息反馈、复制功能

This commit is contained in:
PingChuan
2025-12-10 21:57:43 +08:00
parent ba517d7b9c
commit 38c57f7acc
3 changed files with 116 additions and 5 deletions
+53 -1
View File
@@ -1,4 +1,5 @@
import { Button, Card, Spin } from 'antd';
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';
@@ -26,6 +27,20 @@ export default function ChatMessage({
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;
@@ -168,6 +183,13 @@ export default function ChatMessage({
);
}
// 判断是否可以显示反馈按钮(AI回答、非占位符、非正在响应)
const canShowFeedback = isAnswer &&
!id.startsWith('placeholder-') &&
!id.startsWith('opening-') &&
!id.startsWith('response-') &&
!isResponding;
return (
<div className="chat-message">
<div className={`flex ${isAnswer ? 'justify-start' : 'justify-end'} mb-2`}>
@@ -184,6 +206,36 @@ export default function ChatMessage({
)}
</div>
</div>
{/* 反馈按钮 - 仅在AI回答且非占位符时显示 */}
{canShowFeedback && (
<div className="message-feedback">
<Tooltip title={copied ? '已复制' : '复制'}>
<Button
size="small"
type="text"
icon={<CopyOutlined />}
onClick={handleCopy}
className={copied ? 'message-copy-btn copied' : 'message-copy-btn'}
/>
</Tooltip>
<div className="feedback-actions-right">
<Button
size="small"
type="text"
icon={feedback === 'like' ? <LikeFilled /> : <LikeOutlined />}
onClick={() => handleFeedback('like')}
className={feedback === 'like' ? 'feedback-active-like' : ''}
/>
<Button
size="small"
type="text"
icon={feedback === 'dislike' ? <DislikeFilled /> : <DislikeOutlined />}
onClick={() => handleFeedback('dislike')}
className={feedback === 'dislike' ? 'feedback-active-dislike' : ''}
/>
</div>
</div>
)}
</Card>
</div>
</div>
@@ -16,7 +16,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
// 检查 JWT 是否存在
if (!frontendJWT) {
console.error('[API] Message Feedback - JWT不存在');
console.error('[API] Message Feedback - JWT不存在');
return new Response(
JSON.stringify({ error: 'JWT认证失败,请重新登录' }),
{
@@ -40,21 +40,21 @@ export async function action({ request, params }: ActionFunctionArgs) {
const body = await request.json();
const { rating } = body;
console.log('👍 [API] Message Feedback - 提交反馈:', {
console.log('[API] Message Feedback - 提交反馈:', {
messageId,
rating,
});
const result = await difyClient.updateMessageFeedback(messageId, rating, frontendJWT);
console.log('[API] Message Feedback - Success');
console.log('[API] Message Feedback - Success');
return new Response(JSON.stringify(result), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
} catch (error: any) {
console.error('[API] Message Feedback - Error:', error.message);
console.error('[API] Message Feedback - Error:', error.message);
const status = error.message?.includes('JWT认证失败') ? 401 : 500;
return new Response(
JSON.stringify({ error: error.message || 'Failed to submit feedback' }),
@@ -106,6 +106,65 @@
gap: 8px;
}
/* 消息反馈按钮 */
.message-feedback {
display: flex;
gap: 4px;
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid rgba(0, 0, 0, 0.06);
justify-content: space-between;
align-items: center;
}
.message-copy-btn {
color: #52c41a !important;
transition: all 0.2s ease;
}
.message-copy-btn:hover {
color: #00684a !important;
background: rgba(0, 104, 74, 0.1) !important;
}
.message-copy-btn.copied {
color: #00684a !important;
}
.feedback-actions-right {
display: flex;
gap: 4px;
align-items: center;
}
.message-feedback .ant-btn {
color: #8c8c8c;
transition: all 0.2s ease;
}
.message-feedback .ant-btn:hover {
color: #52c41a;
background: rgba(24, 144, 255, 0.1);
}
.message-feedback .feedback-active-like {
color: #52c41a !important;
}
.message-feedback .feedback-active-like:hover {
color: #52c41a !important;
background: rgba(82, 196, 26, 0.1);
}
.message-feedback .feedback-active-dislike {
color: #ff4d4f !important;
}
.message-feedback .feedback-active-dislike:hover {
color: #ff4d4f !important;
background: rgba(255, 77, 79, 0.1);
}
/* 建议问题 */
.suggested-questions {
margin-top: 16px;