feat:dify聊天页面接入消息反馈、复制功能
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user