2024-08-25 20:29:24 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="refs" v-if="showRefs">
|
|
|
|
|
|
<div class="tags">
|
2025-10-15 02:37:42 +08:00
|
|
|
|
<!-- 反馈 -->
|
2025-10-05 14:50:34 +08:00
|
|
|
|
<span
|
|
|
|
|
|
class="item btn"
|
|
|
|
|
|
:class="{ 'disabled': feedbackState.hasSubmitted }"
|
|
|
|
|
|
@click="likeThisResponse(msg)"
|
|
|
|
|
|
:title="feedbackState.hasSubmitted && feedbackState.rating === 'like' ? '已点赞' : '点赞'"
|
|
|
|
|
|
>
|
2025-12-23 01:51:05 +08:00
|
|
|
|
<ThumbsUp size="12" :fill="feedbackState.rating === 'like' ? 'currentColor' : 'none'" />
|
2025-10-05 14:50:34 +08:00
|
|
|
|
</span>
|
|
|
|
|
|
<span
|
|
|
|
|
|
class="item btn"
|
|
|
|
|
|
:class="{ 'disabled': feedbackState.hasSubmitted }"
|
|
|
|
|
|
@click="dislikeThisResponse(msg)"
|
|
|
|
|
|
:title="feedbackState.hasSubmitted && feedbackState.rating === 'dislike' ? '已点踩' : '点踩'"
|
|
|
|
|
|
>
|
2025-12-23 01:51:05 +08:00
|
|
|
|
<ThumbsDown size="12" :fill="feedbackState.rating === 'dislike' ? 'currentColor' : 'none'" />
|
2025-10-05 14:50:34 +08:00
|
|
|
|
</span>
|
2025-10-15 02:37:42 +08:00
|
|
|
|
<!-- 模型名称 -->
|
2025-06-24 00:15:51 +08:00
|
|
|
|
<span v-if="showKey('model') && getModelName(msg)" class="item" @click="console.log(msg)">
|
2025-12-23 01:51:05 +08:00
|
|
|
|
<Bot size="12" /> {{ getModelName(msg) }}
|
2025-05-09 23:48:17 +08:00
|
|
|
|
</span>
|
2025-10-15 02:37:42 +08:00
|
|
|
|
<!-- 复制 -->
|
2024-09-18 21:03:12 +08:00
|
|
|
|
<span
|
2025-04-02 13:00:25 +08:00
|
|
|
|
v-if="showKey('copy')"
|
2025-12-23 01:51:05 +08:00
|
|
|
|
class="item btn" @click="copyText(msg.content)" title="复制">
|
|
|
|
|
|
<Check v-if="isCopied" size="12" />
|
|
|
|
|
|
<Copy v-else size="12" />
|
2025-02-24 14:58:35 +08:00
|
|
|
|
</span>
|
2025-10-15 02:37:42 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 重试 -->
|
2025-04-27 18:59:38 +08:00
|
|
|
|
<span
|
2025-10-15 02:37:42 +08:00
|
|
|
|
v-if="showKey('regenerate')"
|
2025-12-23 01:51:05 +08:00
|
|
|
|
class="item btn" @click="regenerateMessage()" title="重新生成"><RotateCcw size="12" />
|
2024-08-25 20:29:24 +08:00
|
|
|
|
</span>
|
2025-10-15 02:37:42 +08:00
|
|
|
|
</div>
|
2024-08-25 20:29:24 +08:00
|
|
|
|
</div>
|
2025-10-05 14:50:34 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- Dislike reason modal -->
|
|
|
|
|
|
<a-modal
|
|
|
|
|
|
v-model:open="dislikeModalVisible"
|
|
|
|
|
|
title="请告诉我们不满意的原因"
|
|
|
|
|
|
@ok="submitDislikeFeedback"
|
|
|
|
|
|
@cancel="cancelDislike"
|
|
|
|
|
|
:confirmLoading="submittingFeedback"
|
|
|
|
|
|
okText="提交"
|
|
|
|
|
|
cancelText="取消"
|
|
|
|
|
|
>
|
|
|
|
|
|
<a-textarea
|
|
|
|
|
|
v-model:value="dislikeReason"
|
|
|
|
|
|
:rows="4"
|
|
|
|
|
|
placeholder="您的反馈将帮助我们改进服务(可选)"
|
|
|
|
|
|
:maxlength="500"
|
|
|
|
|
|
show-count
|
|
|
|
|
|
/>
|
|
|
|
|
|
</a-modal>
|
2024-08-25 20:29:24 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2025-12-24 11:41:03 +08:00
|
|
|
|
import { ref, computed, reactive, watch } from 'vue'
|
2024-09-18 21:03:12 +08:00
|
|
|
|
import { useClipboard } from '@vueuse/core'
|
|
|
|
|
|
import { message } from 'ant-design-vue'
|
2024-08-25 20:29:24 +08:00
|
|
|
|
import {
|
2025-12-23 01:51:05 +08:00
|
|
|
|
ThumbsUp,
|
|
|
|
|
|
ThumbsDown,
|
|
|
|
|
|
Bot,
|
|
|
|
|
|
Copy,
|
|
|
|
|
|
Check,
|
|
|
|
|
|
RotateCcw,
|
|
|
|
|
|
} from 'lucide-vue-next'
|
2025-10-05 14:50:34 +08:00
|
|
|
|
import { agentApi } from '@/apis'
|
2025-03-30 14:51:18 +08:00
|
|
|
|
|
2025-04-27 18:59:38 +08:00
|
|
|
|
const emit = defineEmits(['retry', 'openRefs']);
|
2024-08-25 20:29:24 +08:00
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
message: Object,
|
2025-04-02 13:00:25 +08:00
|
|
|
|
showRefs: {
|
|
|
|
|
|
type: [Array, Boolean],
|
|
|
|
|
|
default: () => false
|
2025-04-27 18:59:38 +08:00
|
|
|
|
},
|
|
|
|
|
|
isLatestMessage: {
|
|
|
|
|
|
type: Boolean,
|
|
|
|
|
|
default: false
|
2025-04-02 13:00:25 +08:00
|
|
|
|
}
|
2024-08-25 20:29:24 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2024-09-18 21:03:12 +08:00
|
|
|
|
const msg = ref(props.message)
|
|
|
|
|
|
|
2025-10-05 14:50:34 +08:00
|
|
|
|
// Feedback state
|
|
|
|
|
|
const feedbackState = reactive({
|
|
|
|
|
|
hasSubmitted: false,
|
|
|
|
|
|
rating: null, // 'like' or 'dislike'
|
|
|
|
|
|
reason: null,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-12-24 11:41:03 +08:00
|
|
|
|
// 初始化反馈状态 - 从 message.feedback 读取历史反馈
|
|
|
|
|
|
const initFeedbackState = () => {
|
|
|
|
|
|
if (msg.value?.feedback) {
|
|
|
|
|
|
feedbackState.hasSubmitted = true
|
|
|
|
|
|
feedbackState.rating = msg.value.feedback.rating
|
|
|
|
|
|
feedbackState.reason = msg.value.feedback.reason
|
|
|
|
|
|
} else {
|
|
|
|
|
|
feedbackState.hasSubmitted = false
|
|
|
|
|
|
feedbackState.rating = null
|
|
|
|
|
|
feedbackState.reason = null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 监听 message prop 变化 (用于切换对话时更新状态)
|
|
|
|
|
|
watch(() => props.message, () => {
|
|
|
|
|
|
msg.value = props.message
|
|
|
|
|
|
initFeedbackState()
|
|
|
|
|
|
}, { immediate: true })
|
|
|
|
|
|
|
2025-10-05 14:50:34 +08:00
|
|
|
|
// Modal state for dislike
|
|
|
|
|
|
const dislikeModalVisible = ref(false)
|
|
|
|
|
|
const dislikeReason = ref('')
|
|
|
|
|
|
const submittingFeedback = ref(false)
|
|
|
|
|
|
|
2024-09-18 21:03:12 +08:00
|
|
|
|
// 使用 useClipboard 实现复制功能
|
|
|
|
|
|
const { copy, isSupported } = useClipboard()
|
|
|
|
|
|
|
2025-04-02 13:00:25 +08:00
|
|
|
|
const showKey = (key) => {
|
|
|
|
|
|
if (props.showRefs === true) {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
return props.showRefs.includes(key)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-23 01:51:05 +08:00
|
|
|
|
// 复制状态
|
|
|
|
|
|
const isCopied = ref(false)
|
|
|
|
|
|
|
2024-09-18 21:03:12 +08:00
|
|
|
|
// 定义 copy 方法
|
|
|
|
|
|
const copyText = async (text) => {
|
|
|
|
|
|
if (isSupported) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await copy(text)
|
|
|
|
|
|
message.success('文本已复制到剪贴板')
|
2025-12-23 01:51:05 +08:00
|
|
|
|
isCopied.value = true
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
isCopied.value = false
|
|
|
|
|
|
}, 2000)
|
2024-09-18 21:03:12 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('复制失败:', error)
|
|
|
|
|
|
message.error('复制失败,请手动复制')
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.warn('浏览器不支持自动复制')
|
|
|
|
|
|
message.warning('浏览器不支持自动复制,请手动复制')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-24 00:15:51 +08:00
|
|
|
|
const showRefs = computed(() => {
|
|
|
|
|
|
// 如果只是为了显示模型信息,不需要检查状态
|
|
|
|
|
|
if (props.showRefs && Array.isArray(props.showRefs) && props.showRefs.includes('model')) {
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 原有的逻辑
|
|
|
|
|
|
return (msg.value.role=='received' || msg.value.role=='assistant') && msg.value.status=='finished';
|
|
|
|
|
|
})
|
2024-09-18 21:03:12 +08:00
|
|
|
|
|
2025-03-30 14:51:18 +08:00
|
|
|
|
|
|
|
|
|
|
// 添加重新生成方法
|
2025-03-30 20:46:26 +08:00
|
|
|
|
const regenerateMessage = () => {
|
|
|
|
|
|
emit('retry')
|
2025-03-30 14:51:18 +08:00
|
|
|
|
}
|
2025-06-24 00:15:51 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取模型名称
|
|
|
|
|
|
const getModelName = (msg) => {
|
|
|
|
|
|
// 优先检查 response_metadata.model_name
|
|
|
|
|
|
if (msg.response_metadata?.model_name) {
|
|
|
|
|
|
return msg.response_metadata.model_name;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 兼容旧格式 meta.server_model_name
|
|
|
|
|
|
if (msg.meta?.server_model_name) {
|
|
|
|
|
|
return msg.meta.server_model_name;
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
2025-10-05 14:50:34 +08:00
|
|
|
|
// Handle like action
|
|
|
|
|
|
const likeThisResponse = async (msg) => {
|
|
|
|
|
|
if (feedbackState.hasSubmitted) {
|
|
|
|
|
|
message.info('您已经提交过反馈了')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!msg?.id) {
|
|
|
|
|
|
message.error('无法提交反馈:消息ID不存在')
|
|
|
|
|
|
console.error('Message object:', msg)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
submittingFeedback.value = true
|
|
|
|
|
|
await agentApi.submitMessageFeedback(msg.id, 'like', null)
|
|
|
|
|
|
|
|
|
|
|
|
feedbackState.hasSubmitted = true
|
|
|
|
|
|
feedbackState.rating = 'like'
|
|
|
|
|
|
|
|
|
|
|
|
message.success('感谢您的反馈!')
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to submit like feedback:', error)
|
|
|
|
|
|
if (error.message?.includes('already submitted')) {
|
|
|
|
|
|
message.info('您已经提交过反馈了')
|
|
|
|
|
|
feedbackState.hasSubmitted = true
|
|
|
|
|
|
} else {
|
|
|
|
|
|
message.error('提交反馈失败,请稍后重试')
|
|
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
submittingFeedback.value = false
|
|
|
|
|
|
}
|
2025-06-24 00:15:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-05 14:50:34 +08:00
|
|
|
|
// Handle dislike action
|
|
|
|
|
|
const dislikeThisResponse = async (msg) => {
|
|
|
|
|
|
if (feedbackState.hasSubmitted) {
|
|
|
|
|
|
message.info('您已经提交过反馈了')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!msg?.id) {
|
|
|
|
|
|
message.error('无法提交反馈:消息ID不存在')
|
|
|
|
|
|
console.error('Message object:', msg)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Open modal to get reason
|
|
|
|
|
|
dislikeModalVisible.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Submit dislike feedback with reason
|
|
|
|
|
|
const submitDislikeFeedback = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
submittingFeedback.value = true
|
|
|
|
|
|
await agentApi.submitMessageFeedback(
|
|
|
|
|
|
msg.value.id,
|
|
|
|
|
|
'dislike',
|
|
|
|
|
|
dislikeReason.value || null
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
feedbackState.hasSubmitted = true
|
|
|
|
|
|
feedbackState.rating = 'dislike'
|
|
|
|
|
|
feedbackState.reason = dislikeReason.value
|
|
|
|
|
|
|
|
|
|
|
|
dislikeModalVisible.value = false
|
|
|
|
|
|
dislikeReason.value = ''
|
|
|
|
|
|
|
|
|
|
|
|
message.success('感谢您的反馈!')
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to submit dislike feedback:', error)
|
|
|
|
|
|
if (error.message?.includes('already submitted')) {
|
|
|
|
|
|
message.info('您已经提交过反馈了')
|
|
|
|
|
|
feedbackState.hasSubmitted = true
|
|
|
|
|
|
dislikeModalVisible.value = false
|
|
|
|
|
|
} else {
|
|
|
|
|
|
message.error('提交反馈失败,请稍后重试')
|
|
|
|
|
|
}
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
submittingFeedback.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Cancel dislike modal
|
|
|
|
|
|
const cancelDislike = () => {
|
|
|
|
|
|
dislikeModalVisible.value = false
|
|
|
|
|
|
dislikeReason.value = ''
|
2025-06-24 00:15:51 +08:00
|
|
|
|
}
|
2024-08-25 20:29:24 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="less" scoped>
|
|
|
|
|
|
.refs {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
margin-bottom: 20px;
|
2025-05-20 20:49:50 +08:00
|
|
|
|
margin-top: 10px;
|
2024-09-28 00:41:02 +08:00
|
|
|
|
color: var(--gray-500);
|
2025-02-23 16:38:56 +08:00
|
|
|
|
font-size: 13px;
|
2024-08-25 20:29:24 +08:00
|
|
|
|
gap: 10px;
|
|
|
|
|
|
|
|
|
|
|
|
.item {
|
2025-08-26 22:20:23 +08:00
|
|
|
|
background: var(--gray-50);
|
2025-02-23 16:38:56 +08:00
|
|
|
|
color: var(--gray-700);
|
2025-12-23 01:51:05 +08:00
|
|
|
|
padding: 6px 8px;
|
2024-08-25 20:29:24 +08:00
|
|
|
|
border-radius: 8px;
|
2025-02-23 16:38:56 +08:00
|
|
|
|
font-size: 13px;
|
2024-09-28 00:41:02 +08:00
|
|
|
|
user-select: none;
|
2025-10-05 14:50:34 +08:00
|
|
|
|
transition: all 0.2s ease;
|
2025-12-23 01:51:05 +08:00
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
line-height: 1;
|
2024-09-18 21:03:12 +08:00
|
|
|
|
|
|
|
|
|
|
&.btn {
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
&:hover {
|
2025-08-26 22:20:23 +08:00
|
|
|
|
background: var(--gray-100);
|
2024-09-18 21:03:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
&:active {
|
2025-08-26 22:20:23 +08:00
|
|
|
|
background: var(--gray-200);
|
2024-09-18 21:03:12 +08:00
|
|
|
|
}
|
2025-10-05 14:50:34 +08:00
|
|
|
|
|
|
|
|
|
|
// Disabled state - when feedback has been submitted
|
|
|
|
|
|
&.disabled {
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
background: var(--gray-50);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-09-18 21:03:12 +08:00
|
|
|
|
}
|
2024-08-25 20:29:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tags {
|
|
|
|
|
|
display: flex;
|
2024-09-06 12:54:17 +08:00
|
|
|
|
flex-wrap: wrap;
|
2024-08-25 20:29:24 +08:00
|
|
|
|
gap: 10px;
|
2025-02-24 14:58:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-09-18 21:03:12 +08:00
|
|
|
|
</style>
|