mirror of
https://github.com/langgenius/dify.git
synced 2025-12-14 11:51:09 +00:00
fix: improve chat message log feedback (#29045)
Co-authored-by: yyh <yuanyouhuilyz@gmail.com>
This commit is contained in:
parent
9b9588f20d
commit
c7d2a13524
@ -11,7 +11,10 @@ import {
|
|||||||
RiThumbDownLine,
|
RiThumbDownLine,
|
||||||
RiThumbUpLine,
|
RiThumbUpLine,
|
||||||
} from '@remixicon/react'
|
} from '@remixicon/react'
|
||||||
import type { ChatItem } from '../../types'
|
import type {
|
||||||
|
ChatItem,
|
||||||
|
Feedback,
|
||||||
|
} from '../../types'
|
||||||
import { useChatContext } from '../context'
|
import { useChatContext } from '../context'
|
||||||
import copy from 'copy-to-clipboard'
|
import copy from 'copy-to-clipboard'
|
||||||
import Toast from '@/app/components/base/toast'
|
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 NewAudioButton from '@/app/components/base/new-audio-button'
|
||||||
import Modal from '@/app/components/base/modal/modal'
|
import Modal from '@/app/components/base/modal/modal'
|
||||||
import Textarea from '@/app/components/base/textarea'
|
import Textarea from '@/app/components/base/textarea'
|
||||||
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
type OperationProps = {
|
type OperationProps = {
|
||||||
@ -66,8 +70,9 @@ const Operation: FC<OperationProps> = ({
|
|||||||
adminFeedback,
|
adminFeedback,
|
||||||
agent_thoughts,
|
agent_thoughts,
|
||||||
} = item
|
} = item
|
||||||
const [localFeedback, setLocalFeedback] = useState(config?.supportAnnotation ? adminFeedback : feedback)
|
const [userLocalFeedback, setUserLocalFeedback] = useState(feedback)
|
||||||
const [adminLocalFeedback, setAdminLocalFeedback] = useState(adminFeedback)
|
const [adminLocalFeedback, setAdminLocalFeedback] = useState(adminFeedback)
|
||||||
|
const [feedbackTarget, setFeedbackTarget] = useState<'user' | 'admin'>('user')
|
||||||
|
|
||||||
// Separate feedback types for display
|
// Separate feedback types for display
|
||||||
const userFeedback = feedback
|
const userFeedback = feedback
|
||||||
@ -79,24 +84,68 @@ const Operation: FC<OperationProps> = ({
|
|||||||
return messageContent
|
return messageContent
|
||||||
}, [agent_thoughts, 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)
|
if (!config?.supportFeedback || !onFeedback)
|
||||||
return
|
return
|
||||||
|
|
||||||
await onFeedback?.(id, { rating, content })
|
await onFeedback?.(id, { rating, content })
|
||||||
setLocalFeedback({ rating })
|
|
||||||
|
|
||||||
// Update admin feedback state separately if annotation is supported
|
const nextFeedback = rating === null ? { rating: null } : { rating, content }
|
||||||
if (config?.supportAnnotation)
|
|
||||||
setAdminLocalFeedback(rating ? { rating } : undefined)
|
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)
|
setIsShowFeedbackModal(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFeedbackSubmit = async () => {
|
const handleFeedbackSubmit = async () => {
|
||||||
await handleFeedback('dislike', feedbackContent)
|
await handleFeedback('dislike', feedbackContent, feedbackTarget)
|
||||||
setFeedbackContent('')
|
setFeedbackContent('')
|
||||||
setIsShowFeedbackModal(false)
|
setIsShowFeedbackModal(false)
|
||||||
}
|
}
|
||||||
@ -116,12 +165,13 @@ const Operation: FC<OperationProps> = ({
|
|||||||
width += 26
|
width += 26
|
||||||
if (!isOpeningStatement && config?.supportAnnotation && config?.annotation_reply?.enabled)
|
if (!isOpeningStatement && config?.supportAnnotation && config?.annotation_reply?.enabled)
|
||||||
width += 26
|
width += 26
|
||||||
if (config?.supportFeedback && !localFeedback?.rating && onFeedback && !isOpeningStatement)
|
if (shouldShowUserFeedbackBar)
|
||||||
width += 60 + 8
|
width += hasUserFeedback ? 28 + 8 : 60 + 8
|
||||||
if (config?.supportFeedback && localFeedback?.rating && onFeedback && !isOpeningStatement)
|
if (shouldShowAdminFeedbackBar)
|
||||||
width += 28 + 8
|
width += (hasAdminFeedback ? 28 : 60) + 8 + (hasUserFeedback ? 28 : 0)
|
||||||
|
|
||||||
return width
|
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])
|
const positionRight = useMemo(() => operationWidth < maxSize, [operationWidth, maxSize])
|
||||||
|
|
||||||
@ -136,6 +186,110 @@ const Operation: FC<OperationProps> = ({
|
|||||||
)}
|
)}
|
||||||
style={(!hasWorkflowProcess && positionRight) ? { left: contentWidth + 8 } : {}}
|
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 && (
|
{showPromptLog && !isOpeningStatement && (
|
||||||
<div className='hidden group-hover:block'>
|
<div className='hidden group-hover:block'>
|
||||||
<Log logItem={item} />
|
<Log logItem={item} />
|
||||||
@ -174,69 +328,6 @@ const Operation: FC<OperationProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
<EditReplyModal
|
<EditReplyModal
|
||||||
isShow={isShowReplyModal}
|
isShow={isShowReplyModal}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user