From c7d2a135242bd28518a441f0305b75ffef0a0ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9D=9E=E6=B3=95=E6=93=8D=E4=BD=9C?= Date: Wed, 3 Dec 2025 13:42:40 +0800 Subject: [PATCH] fix: improve chat message log feedback (#29045) Co-authored-by: yyh --- .../base/chat/chat/answer/operation.tsx | 245 ++++++++++++------ 1 file changed, 168 insertions(+), 77 deletions(-) diff --git a/web/app/components/base/chat/chat/answer/operation.tsx b/web/app/components/base/chat/chat/answer/operation.tsx index 6868d76c73..fca0ae5cae 100644 --- a/web/app/components/base/chat/chat/answer/operation.tsx +++ b/web/app/components/base/chat/chat/answer/operation.tsx @@ -11,7 +11,10 @@ import { RiThumbDownLine, RiThumbUpLine, } from '@remixicon/react' -import type { ChatItem } from '../../types' +import type { + ChatItem, + Feedback, +} from '../../types' import { useChatContext } from '../context' import copy from 'copy-to-clipboard' import Toast from '@/app/components/base/toast' @@ -22,6 +25,7 @@ import ActionButton, { ActionButtonState } from '@/app/components/base/action-bu import NewAudioButton from '@/app/components/base/new-audio-button' import Modal from '@/app/components/base/modal/modal' import Textarea from '@/app/components/base/textarea' +import Tooltip from '@/app/components/base/tooltip' import cn from '@/utils/classnames' type OperationProps = { @@ -66,8 +70,9 @@ const Operation: FC = ({ adminFeedback, agent_thoughts, } = item - const [localFeedback, setLocalFeedback] = useState(config?.supportAnnotation ? adminFeedback : feedback) + const [userLocalFeedback, setUserLocalFeedback] = useState(feedback) const [adminLocalFeedback, setAdminLocalFeedback] = useState(adminFeedback) + const [feedbackTarget, setFeedbackTarget] = useState<'user' | 'admin'>('user') // Separate feedback types for display const userFeedback = feedback @@ -79,24 +84,68 @@ const Operation: FC = ({ return messageContent }, [agent_thoughts, messageContent]) - const handleFeedback = async (rating: 'like' | 'dislike' | null, content?: string) => { + const displayUserFeedback = userLocalFeedback ?? userFeedback + + const hasUserFeedback = !!displayUserFeedback?.rating + const hasAdminFeedback = !!adminLocalFeedback?.rating + + const shouldShowUserFeedbackBar = !isOpeningStatement && config?.supportFeedback && !!onFeedback && !config?.supportAnnotation + const shouldShowAdminFeedbackBar = !isOpeningStatement && config?.supportFeedback && !!onFeedback && !!config?.supportAnnotation + + const userFeedbackLabel = t('appLog.table.header.userRate') || 'User feedback' + const adminFeedbackLabel = t('appLog.table.header.adminRate') || 'Admin feedback' + const feedbackTooltipClassName = 'max-w-[260px]' + + const buildFeedbackTooltip = (feedbackData?: Feedback | null, label = userFeedbackLabel) => { + if (!feedbackData?.rating) + return label + + const ratingLabel = feedbackData.rating === 'like' + ? (t('appLog.detail.operation.like') || 'like') + : (t('appLog.detail.operation.dislike') || 'dislike') + const feedbackText = feedbackData.content?.trim() + + if (feedbackText) + return `${label}: ${ratingLabel} - ${feedbackText}` + + return `${label}: ${ratingLabel}` + } + + const handleFeedback = async (rating: 'like' | 'dislike' | null, content?: string, target: 'user' | 'admin' = 'user') => { if (!config?.supportFeedback || !onFeedback) return await onFeedback?.(id, { rating, content }) - setLocalFeedback({ rating }) - // Update admin feedback state separately if annotation is supported - if (config?.supportAnnotation) - setAdminLocalFeedback(rating ? { rating } : undefined) + const nextFeedback = rating === null ? { rating: null } : { rating, content } + + if (target === 'admin') + setAdminLocalFeedback(nextFeedback) + else + setUserLocalFeedback(nextFeedback) } - const handleThumbsDown = () => { + const handleLikeClick = (target: 'user' | 'admin') => { + const currentRating = target === 'admin' ? adminLocalFeedback?.rating : displayUserFeedback?.rating + if (currentRating === 'like') { + handleFeedback(null, undefined, target) + return + } + handleFeedback('like', undefined, target) + } + + const handleDislikeClick = (target: 'user' | 'admin') => { + const currentRating = target === 'admin' ? adminLocalFeedback?.rating : displayUserFeedback?.rating + if (currentRating === 'dislike') { + handleFeedback(null, undefined, target) + return + } + setFeedbackTarget(target) setIsShowFeedbackModal(true) } const handleFeedbackSubmit = async () => { - await handleFeedback('dislike', feedbackContent) + await handleFeedback('dislike', feedbackContent, feedbackTarget) setFeedbackContent('') setIsShowFeedbackModal(false) } @@ -116,12 +165,13 @@ const Operation: FC = ({ width += 26 if (!isOpeningStatement && config?.supportAnnotation && config?.annotation_reply?.enabled) width += 26 - if (config?.supportFeedback && !localFeedback?.rating && onFeedback && !isOpeningStatement) - width += 60 + 8 - if (config?.supportFeedback && localFeedback?.rating && onFeedback && !isOpeningStatement) - width += 28 + 8 + if (shouldShowUserFeedbackBar) + width += hasUserFeedback ? 28 + 8 : 60 + 8 + if (shouldShowAdminFeedbackBar) + width += (hasAdminFeedback ? 28 : 60) + 8 + (hasUserFeedback ? 28 : 0) + return width - }, [isOpeningStatement, showPromptLog, config?.text_to_speech?.enabled, config?.supportAnnotation, config?.annotation_reply?.enabled, config?.supportFeedback, localFeedback?.rating, onFeedback]) + }, [config?.annotation_reply?.enabled, config?.supportAnnotation, config?.text_to_speech?.enabled, hasAdminFeedback, hasUserFeedback, isOpeningStatement, shouldShowAdminFeedbackBar, shouldShowUserFeedbackBar, showPromptLog]) const positionRight = useMemo(() => operationWidth < maxSize, [operationWidth, maxSize]) @@ -136,6 +186,110 @@ const Operation: FC = ({ )} style={(!hasWorkflowProcess && positionRight) ? { left: contentWidth + 8 } : {}} > + {shouldShowUserFeedbackBar && ( +
+ {hasUserFeedback ? ( + + handleFeedback(null, undefined, 'user')} + > + {displayUserFeedback?.rating === 'like' + ? + : } + + + ) : ( + <> + handleLikeClick('user')} + > + + + handleDislikeClick('user')} + > + + + + )} +
+ )} + {shouldShowAdminFeedbackBar && ( +
+ {/* User Feedback Display */} + {displayUserFeedback?.rating && ( + + {displayUserFeedback.rating === 'like' ? ( + + + + ) : ( + + + + )} + + )} + + {/* Admin Feedback Controls */} + {displayUserFeedback?.rating &&
} + {hasAdminFeedback ? ( + + handleFeedback(null, undefined, 'admin')} + > + {adminLocalFeedback?.rating === 'like' + ? + : } + + + ) : ( + <> + + handleLikeClick('admin')} + > + + + + + handleDislikeClick('admin')} + > + + + + + )} +
+ )} {showPromptLog && !isOpeningStatement && (
@@ -174,69 +328,6 @@ const Operation: FC = ({ )}
)} - {!isOpeningStatement && config?.supportFeedback && !localFeedback?.rating && onFeedback && ( -
- {!localFeedback?.rating && ( - <> - handleFeedback('like')}> - - - - - - - )} -
- )} - {!isOpeningStatement && config?.supportFeedback && onFeedback && ( -
- {/* User Feedback Display */} - {userFeedback?.rating && ( -
- User - {userFeedback.rating === 'like' ? ( - - - - ) : ( - - - - )} -
- )} - - {/* Admin Feedback Controls */} - {config?.supportAnnotation && ( -
- {userFeedback?.rating &&
} - {!adminLocalFeedback?.rating ? ( - <> - handleFeedback('like')}> - - - - - - - ) : ( - <> - {adminLocalFeedback.rating === 'like' ? ( - handleFeedback(null)}> - - - ) : ( - handleFeedback(null)}> - - - )} - - )} -
- )} - -
- )}