fix: improve chat message log feedback (#29045)

Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
This commit is contained in:
非法操作 2025-12-03 13:42:40 +08:00 committed by GitHub
parent 9b9588f20d
commit c7d2a13524
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -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<OperationProps> = ({
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<OperationProps> = ({
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<OperationProps> = ({
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<OperationProps> = ({
)}
style={(!hasWorkflowProcess && positionRight) ? { left: contentWidth + 8 } : {}}
>
{shouldShowUserFeedbackBar && (
<div className={cn(
'ml-1 items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm',
hasUserFeedback ? 'flex' : 'hidden group-hover:flex',
)}>
{hasUserFeedback ? (
<Tooltip
popupContent={buildFeedbackTooltip(displayUserFeedback, userFeedbackLabel)}
popupClassName={feedbackTooltipClassName}
>
<ActionButton
state={displayUserFeedback?.rating === 'like' ? ActionButtonState.Active : ActionButtonState.Destructive}
onClick={() => handleFeedback(null, undefined, 'user')}
>
{displayUserFeedback?.rating === 'like'
? <RiThumbUpLine className='h-4 w-4' />
: <RiThumbDownLine className='h-4 w-4' />}
</ActionButton>
</Tooltip>
) : (
<>
<ActionButton
state={displayUserFeedback?.rating === 'like' ? ActionButtonState.Active : ActionButtonState.Default}
onClick={() => handleLikeClick('user')}
>
<RiThumbUpLine className='h-4 w-4' />
</ActionButton>
<ActionButton
state={displayUserFeedback?.rating === 'dislike' ? ActionButtonState.Destructive : ActionButtonState.Default}
onClick={() => handleDislikeClick('user')}
>
<RiThumbDownLine className='h-4 w-4' />
</ActionButton>
</>
)}
</div>
)}
{shouldShowAdminFeedbackBar && (
<div className={cn(
'ml-1 items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm',
(hasAdminFeedback || hasUserFeedback) ? 'flex' : 'hidden group-hover:flex',
)}>
{/* User Feedback Display */}
{displayUserFeedback?.rating && (
<Tooltip
popupContent={buildFeedbackTooltip(displayUserFeedback, userFeedbackLabel)}
popupClassName={feedbackTooltipClassName}
>
{displayUserFeedback.rating === 'like' ? (
<ActionButton state={ActionButtonState.Active}>
<RiThumbUpLine className='h-4 w-4' />
</ActionButton>
) : (
<ActionButton state={ActionButtonState.Destructive}>
<RiThumbDownLine className='h-4 w-4' />
</ActionButton>
)}
</Tooltip>
)}
{/* Admin Feedback Controls */}
{displayUserFeedback?.rating && <div className='mx-1 h-3 w-[0.5px] bg-components-actionbar-border' />}
{hasAdminFeedback ? (
<Tooltip
popupContent={buildFeedbackTooltip(adminLocalFeedback, adminFeedbackLabel)}
popupClassName={feedbackTooltipClassName}
>
<ActionButton
state={adminLocalFeedback?.rating === 'like' ? ActionButtonState.Active : ActionButtonState.Destructive}
onClick={() => handleFeedback(null, undefined, 'admin')}
>
{adminLocalFeedback?.rating === 'like'
? <RiThumbUpLine className='h-4 w-4' />
: <RiThumbDownLine className='h-4 w-4' />}
</ActionButton>
</Tooltip>
) : (
<>
<Tooltip
popupContent={buildFeedbackTooltip(adminLocalFeedback, adminFeedbackLabel)}
popupClassName={feedbackTooltipClassName}
>
<ActionButton
state={adminLocalFeedback?.rating === 'like' ? ActionButtonState.Active : ActionButtonState.Default}
onClick={() => handleLikeClick('admin')}
>
<RiThumbUpLine className='h-4 w-4' />
</ActionButton>
</Tooltip>
<Tooltip
popupContent={buildFeedbackTooltip(adminLocalFeedback, adminFeedbackLabel)}
popupClassName={feedbackTooltipClassName}
>
<ActionButton
state={adminLocalFeedback?.rating === 'dislike' ? ActionButtonState.Destructive : ActionButtonState.Default}
onClick={() => handleDislikeClick('admin')}
>
<RiThumbDownLine className='h-4 w-4' />
</ActionButton>
</Tooltip>
</>
)}
</div>
)}
{showPromptLog && !isOpeningStatement && (
<div className='hidden group-hover:block'>
<Log logItem={item} />
@ -174,69 +328,6 @@ const Operation: FC<OperationProps> = ({
)}
</div>
)}
{!isOpeningStatement && config?.supportFeedback && !localFeedback?.rating && onFeedback && (
<div className='ml-1 hidden items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm group-hover:flex'>
{!localFeedback?.rating && (
<>
<ActionButton onClick={() => handleFeedback('like')}>
<RiThumbUpLine className='h-4 w-4' />
</ActionButton>
<ActionButton onClick={handleThumbsDown}>
<RiThumbDownLine className='h-4 w-4' />
</ActionButton>
</>
)}
</div>
)}
{!isOpeningStatement && config?.supportFeedback && onFeedback && (
<div className='ml-1 flex items-center gap-0.5 rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md backdrop-blur-sm'>
{/* User Feedback Display */}
{userFeedback?.rating && (
<div className='flex items-center'>
<span className='mr-1 text-xs text-text-tertiary'>User</span>
{userFeedback.rating === 'like' ? (
<ActionButton state={ActionButtonState.Active} title={userFeedback.content ? `User liked this response: ${userFeedback.content}` : 'User liked this response'}>
<RiThumbUpLine className='h-3 w-3' />
</ActionButton>
) : (
<ActionButton state={ActionButtonState.Destructive} title={userFeedback.content ? `User disliked this response: ${userFeedback.content}` : 'User disliked this response'}>
<RiThumbDownLine className='h-3 w-3' />
</ActionButton>
)}
</div>
)}
{/* Admin Feedback Controls */}
{config?.supportAnnotation && (
<div className='flex items-center'>
{userFeedback?.rating && <div className='mx-1 h-3 w-[0.5px] bg-components-actionbar-border' />}
{!adminLocalFeedback?.rating ? (
<>
<ActionButton onClick={() => handleFeedback('like')}>
<RiThumbUpLine className='h-4 w-4' />
</ActionButton>
<ActionButton onClick={handleThumbsDown}>
<RiThumbDownLine className='h-4 w-4' />
</ActionButton>
</>
) : (
<>
{adminLocalFeedback.rating === 'like' ? (
<ActionButton state={ActionButtonState.Active} onClick={() => handleFeedback(null)}>
<RiThumbUpLine className='h-4 w-4' />
</ActionButton>
) : (
<ActionButton state={ActionButtonState.Destructive} onClick={() => handleFeedback(null)}>
<RiThumbDownLine className='h-4 w-4' />
</ActionButton>
)}
</>
)}
</div>
)}
</div>
)}
</div>
<EditReplyModal
isShow={isShowReplyModal}