2024-08-25 20:29:24 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div class="refs" v-if="showRefs">
|
|
|
|
|
|
<div class="tags">
|
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' ? '已点赞' : '点赞'"
|
|
|
|
|
|
>
|
|
|
|
|
|
<LikeFilled v-if="feedbackState.rating === 'like'" />
|
|
|
|
|
|
<LikeOutlined v-else />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span
|
|
|
|
|
|
class="item btn"
|
|
|
|
|
|
:class="{ 'disabled': feedbackState.hasSubmitted }"
|
|
|
|
|
|
@click="dislikeThisResponse(msg)"
|
|
|
|
|
|
:title="feedbackState.hasSubmitted && feedbackState.rating === 'dislike' ? '已点踩' : '点踩'"
|
|
|
|
|
|
>
|
|
|
|
|
|
<DislikeFilled v-if="feedbackState.rating === 'dislike'" />
|
|
|
|
|
|
<DislikeOutlined v-else />
|
|
|
|
|
|
</span>
|
2025-06-24 00:15:51 +08:00
|
|
|
|
<span v-if="showKey('model') && getModelName(msg)" class="item" @click="console.log(msg)">
|
|
|
|
|
|
<BulbOutlined /> {{ getModelName(msg) }}
|
2025-05-09 23:48:17 +08:00
|
|
|
|
</span>
|
2024-09-18 21:03:12 +08:00
|
|
|
|
<span
|
2025-04-02 13:00:25 +08:00
|
|
|
|
v-if="showKey('copy')"
|
|
|
|
|
|
class="item btn" @click="copyText(msg.content)" title="复制"><CopyOutlined /></span>
|
|
|
|
|
|
<span
|
|
|
|
|
|
v-if="showKey('regenerate')"
|
|
|
|
|
|
class="item btn" @click="regenerateMessage()" title="重新生成"><ReloadOutlined /></span>
|
|
|
|
|
|
<span
|
2025-04-27 18:59:38 +08:00
|
|
|
|
v-if="isLatestMessage && showKey('subGraph') && hasSubGraphData(msg)"
|
2024-09-18 21:03:12 +08:00
|
|
|
|
class="item btn"
|
2025-04-27 18:59:38 +08:00
|
|
|
|
@click="openGlobalRefs('graph')"
|
2024-09-18 21:03:12 +08:00
|
|
|
|
>
|
2025-02-24 22:56:13 +08:00
|
|
|
|
<DeploymentUnitOutlined /> 关系图
|
2024-09-18 21:03:12 +08:00
|
|
|
|
</span>
|
2025-02-24 14:58:35 +08:00
|
|
|
|
<span
|
|
|
|
|
|
class="item btn"
|
2025-04-27 18:59:38 +08:00
|
|
|
|
v-if="isLatestMessage && showKey('webSearch') && msg.refs?.web_search.results.length > 0"
|
|
|
|
|
|
@click="openGlobalRefs('webSearch')"
|
2025-02-24 14:58:35 +08:00
|
|
|
|
>
|
2025-02-24 19:43:40 +08:00
|
|
|
|
<GlobalOutlined /> 网页搜索 {{ msg.refs.web_search?.results.length }}
|
2025-02-24 14:58:35 +08:00
|
|
|
|
</span>
|
2025-04-27 18:59:38 +08:00
|
|
|
|
<span
|
|
|
|
|
|
class="item btn"
|
|
|
|
|
|
v-if="isLatestMessage && showKey('knowledgeBase') && hasKnowledgeBaseData(msg)"
|
|
|
|
|
|
@click="openGlobalRefs('knowledgeBase')"
|
2024-08-25 20:29:24 +08:00
|
|
|
|
>
|
2025-04-27 18:59:38 +08:00
|
|
|
|
<FileTextOutlined /> 知识库
|
2024-08-25 20:29:24 +08:00
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</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-10-05 14:50:34 +08:00
|
|
|
|
import { ref, computed, reactive, onMounted } 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 {
|
|
|
|
|
|
GlobalOutlined,
|
|
|
|
|
|
FileTextOutlined,
|
2024-09-18 21:03:12 +08:00
|
|
|
|
CopyOutlined,
|
2025-02-24 22:56:13 +08:00
|
|
|
|
DeploymentUnitOutlined,
|
|
|
|
|
|
BulbOutlined,
|
2025-03-30 14:51:18 +08:00
|
|
|
|
ReloadOutlined,
|
2025-06-24 00:15:51 +08:00
|
|
|
|
LikeOutlined,
|
2025-10-05 14:50:34 +08:00
|
|
|
|
LikeFilled,
|
2025-06-24 00:15:51 +08:00
|
|
|
|
DislikeOutlined,
|
2025-10-05 14:50:34 +08:00
|
|
|
|
DislikeFilled,
|
2024-08-25 20:29:24 +08:00
|
|
|
|
} from '@ant-design/icons-vue'
|
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,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-18 21:03:12 +08:00
|
|
|
|
// 定义 copy 方法
|
|
|
|
|
|
const copyText = async (text) => {
|
|
|
|
|
|
if (isSupported) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await copy(text)
|
|
|
|
|
|
message.success('文本已复制到剪贴板')
|
|
|
|
|
|
} 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-04-27 18:59:38 +08:00
|
|
|
|
// 打开全局refs抽屉
|
|
|
|
|
|
const openGlobalRefs = (type) => {
|
|
|
|
|
|
emit('openRefs', {
|
|
|
|
|
|
type,
|
|
|
|
|
|
refs: msg.value.refs
|
|
|
|
|
|
})
|
2025-02-24 14:58:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-09-18 21:03:12 +08:00
|
|
|
|
const hasSubGraphData = (msg) => {
|
|
|
|
|
|
return msg.refs &&
|
|
|
|
|
|
msg.refs.graph_base &&
|
|
|
|
|
|
msg.refs.graph_base.results.nodes.length > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-27 18:59:38 +08:00
|
|
|
|
const hasKnowledgeBaseData = (msg) => {
|
|
|
|
|
|
return msg.refs &&
|
|
|
|
|
|
msg.refs.knowledge_base &&
|
|
|
|
|
|
msg.refs.knowledge_base.results.length > 0;
|
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
|
|
|
|
// Load existing feedback on mount
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
|
if (msg.value?.id) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await agentApi.getMessageFeedback(msg.value.id)
|
|
|
|
|
|
if (response.has_feedback) {
|
|
|
|
|
|
feedbackState.hasSubmitted = true
|
|
|
|
|
|
feedbackState.rating = response.feedback.rating
|
|
|
|
|
|
feedbackState.reason = response.feedback.reason
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to load feedback:', error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 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);
|
2024-08-25 20:29:24 +08:00
|
|
|
|
padding: 2px 8px;
|
|
|
|
|
|
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;
|
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 {
|
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
|
opacity: 0.7;
|
|
|
|
|
|
|
|
|
|
|
|
&: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>
|