feat(feedback): 增加反馈消息和对话标题的展开收起功能
在反馈模态框中添加消息内容和对话标题的展开/收起功能,优化长文本显示体验 添加相关样式和交互逻辑,支持自适应显示和折叠 移除消息内容长度限制,显示完整内容
This commit is contained in:
parent
44ede4d911
commit
15a7a32787
@ -718,7 +718,7 @@ async def get_all_feedbacks(
|
|||||||
"rating": feedback.rating,
|
"rating": feedback.rating,
|
||||||
"reason": feedback.reason,
|
"reason": feedback.reason,
|
||||||
"created_at": feedback.created_at.isoformat(),
|
"created_at": feedback.created_at.isoformat(),
|
||||||
"message_content": message.content[:100] + ("..." if len(message.content) > 100 else ""),
|
"message_content": message.content,
|
||||||
"conversation_title": conversation.title,
|
"conversation_title": conversation.title,
|
||||||
"agent_id": conversation.agent_id,
|
"agent_id": conversation.agent_id,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1067,7 +1067,6 @@ watch(conversations, () => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
background: var(--gray-0);
|
|
||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
|||||||
@ -18,16 +18,22 @@
|
|||||||
<a-button
|
<a-button
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
:icon="h(CopyOutlined)"
|
|
||||||
@click="copyDatabaseId"
|
@click="copyDatabaseId"
|
||||||
title="复制知识库ID"
|
title="复制知识库ID"
|
||||||
/>
|
>
|
||||||
|
<template #icon>
|
||||||
|
<Copy :size="14" />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
<a-button
|
<a-button
|
||||||
@click="showEditModal"
|
@click="showEditModal"
|
||||||
type="text"
|
type="text"
|
||||||
size="small"
|
size="small"
|
||||||
:icon="h(EditOutlined)"
|
>
|
||||||
/>
|
<template #icon>
|
||||||
|
<Pencil :size="14" />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -55,7 +61,10 @@
|
|||||||
<a-modal v-model:open="editModalVisible" title="编辑知识库信息">
|
<a-modal v-model:open="editModalVisible" title="编辑知识库信息">
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<a-button danger @click="deleteDatabase" style="margin-right: auto; margin-left: 0;">
|
<a-button danger @click="deleteDatabase" style="margin-right: auto; margin-left: 0;">
|
||||||
<DeleteOutlined /> 删除数据库
|
<template #icon>
|
||||||
|
<Trash2 :size="16" style="vertical-align: -3px; margin-right: 4px;" />
|
||||||
|
</template>
|
||||||
|
删除数据库
|
||||||
</a-button>
|
</a-button>
|
||||||
<a-button key="back" @click="editModalVisible = false">取消</a-button>
|
<a-button key="back" @click="editModalVisible = false">取消</a-button>
|
||||||
<a-button key="submit" type="primary" @click="handleEditSubmit">确定</a-button>
|
<a-button key="submit" type="primary" @click="handleEditSubmit">确定</a-button>
|
||||||
@ -97,12 +106,12 @@ import { useRouter } from 'vue-router';
|
|||||||
import { useDatabaseStore } from '@/stores/database';
|
import { useDatabaseStore } from '@/stores/database';
|
||||||
import { getKbTypeLabel, getKbTypeColor } from '@/utils/kb_utils';
|
import { getKbTypeLabel, getKbTypeColor } from '@/utils/kb_utils';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
import { LeftOutlined } from '@ant-design/icons-vue';
|
||||||
import {
|
import {
|
||||||
LeftOutlined,
|
Pencil,
|
||||||
EditOutlined,
|
Trash2,
|
||||||
DeleteOutlined,
|
Copy,
|
||||||
CopyOutlined,
|
} from 'lucide-vue-next';
|
||||||
} from '@ant-design/icons-vue';
|
|
||||||
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
|
import ModelSelectorComponent from '@/components/ModelSelectorComponent.vue';
|
||||||
import AiTextarea from '@/components/AiTextarea.vue';
|
import AiTextarea from '@/components/AiTextarea.vue';
|
||||||
|
|
||||||
|
|||||||
@ -48,25 +48,54 @@
|
|||||||
</a-tag>
|
</a-tag>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 卡片内容:消息内容和对话信息 -->
|
<!-- 卡片内容:对话信息、消息内容和反馈原因 -->
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="message-section">
|
<!-- 对话标题 -->
|
||||||
<div class="message-content">{{ feedback.message_content }}</div>
|
<div class="conversation-section" v-if="feedback.conversation_title">
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="conversation-section">
|
|
||||||
<div class="conversation-info">
|
<div class="conversation-info">
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
|
<span
|
||||||
|
class="conversation-title"
|
||||||
|
:class="{ 'collapsed': !expandedStates.get(`${feedback.id}-conversation`) }"
|
||||||
|
>
|
||||||
|
标题:{{ feedback.conversation_title }}
|
||||||
|
</span>
|
||||||
|
<a-button
|
||||||
|
v-if="shouldShowConversationExpandButton(feedback.conversation_title)"
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
@click="toggleConversationExpand(feedback.id)"
|
||||||
|
class="expand-button-inline"
|
||||||
|
>
|
||||||
|
{{ expandedStates.get(`${feedback.id}-conversation`) ? '收起' : '展开' }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<div class="info-item" v-if="!props.agentId">
|
||||||
<span class="label">智能体:</span>
|
<span class="label">智能体:</span>
|
||||||
<span class="value">{{ feedback.agent_id }}</span>
|
<span class="value">{{ feedback.agent_id }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item" v-if="feedback.conversation_title">
|
|
||||||
<span class="label">对话:</span>
|
|
||||||
<span class="value">{{ feedback.conversation_title }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 消息内容 -->
|
||||||
|
<div class="message-section">
|
||||||
|
<div
|
||||||
|
class="message-content"
|
||||||
|
:class="{ 'collapsed': !expandedStates.get(`${feedback.id}-message`) }"
|
||||||
|
>
|
||||||
|
{{ feedback.message_content }}
|
||||||
|
</div>
|
||||||
|
<a-button
|
||||||
|
v-if="shouldShowExpandButton(feedback.message_content)"
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
@click="toggleExpand(feedback.id)"
|
||||||
|
class="expand-button"
|
||||||
|
>
|
||||||
|
{{ expandedStates.get(`${feedback.id}-message`) ? '收起' : '展开全部' }}
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 反馈原因 -->
|
<!-- 反馈原因 -->
|
||||||
<div v-if="feedback.reason" class="reason-section">
|
<div v-if="feedback.reason" class="reason-section">
|
||||||
<div class="reason-content">{{ feedback.reason }}</div>
|
<div class="reason-content">{{ feedback.reason }}</div>
|
||||||
@ -98,6 +127,14 @@ import { LikeOutlined, DislikeOutlined, ClockCircleOutlined } from '@ant-design/
|
|||||||
import { dashboardApi } from '@/apis/dashboard_api'
|
import { dashboardApi } from '@/apis/dashboard_api'
|
||||||
import { formatFullDateTime } from '@/utils/time'
|
import { formatFullDateTime } from '@/utils/time'
|
||||||
|
|
||||||
|
// 常量配置
|
||||||
|
const CONFIG = {
|
||||||
|
MESSAGE_MAX_LINES: 8, // 消息最大显示行数
|
||||||
|
CONVERSATION_MAX_LINES: 2, // 对话标题最大显示行数
|
||||||
|
CONVERSATION_MAX_CHARS: 60, // 对话标题字符数阈值
|
||||||
|
AVG_CHARS_PER_LINE: 30 // 每行平均字符数(中英文混合)
|
||||||
|
}
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
agentId: {
|
agentId: {
|
||||||
@ -119,6 +156,9 @@ const feedbackOptions = [
|
|||||||
{ label: '点踩', value: 'dislike' }
|
{ label: '点踩', value: 'dislike' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
// 展开状态映射(使用 Map 避免直接修改对象)
|
||||||
|
const expandedStates = ref(new Map())
|
||||||
|
|
||||||
// 显示模态框
|
// 显示模态框
|
||||||
const show = () => {
|
const show = () => {
|
||||||
modalVisible.value = true
|
modalVisible.value = true
|
||||||
@ -128,6 +168,37 @@ const show = () => {
|
|||||||
// 暴露方法给父组件
|
// 暴露方法给父组件
|
||||||
defineExpose({ show })
|
defineExpose({ show })
|
||||||
|
|
||||||
|
// 计算文本行数的辅助函数(估算)
|
||||||
|
const estimateLines = (text) => {
|
||||||
|
if (!text) return 0
|
||||||
|
return Math.ceil(text.length / CONFIG.AVG_CHARS_PER_LINE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否显示展开按钮
|
||||||
|
const shouldShowExpandButton = (content) => {
|
||||||
|
return estimateLines(content) > CONFIG.MESSAGE_MAX_LINES
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断对话标题是否需要展开按钮
|
||||||
|
const shouldShowConversationExpandButton = (title) => {
|
||||||
|
if (!title) return false
|
||||||
|
return title.length > CONFIG.CONVERSATION_MAX_CHARS
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换展开/收起状态
|
||||||
|
const toggleExpand = (feedbackId) => {
|
||||||
|
const key = `${feedbackId}-message`
|
||||||
|
const currentState = expandedStates.value.get(key) ?? false
|
||||||
|
expandedStates.value.set(key, !currentState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换对话标题展开/收起状态
|
||||||
|
const toggleConversationExpand = (feedbackId) => {
|
||||||
|
const key = `${feedbackId}-conversation`
|
||||||
|
const currentState = expandedStates.value.get(key) ?? false
|
||||||
|
expandedStates.value.set(key, !currentState)
|
||||||
|
}
|
||||||
|
|
||||||
// 加载反馈列表
|
// 加载反馈列表
|
||||||
const loadFeedbacks = async () => {
|
const loadFeedbacks = async () => {
|
||||||
loadingFeedbacks.value = true
|
loadingFeedbacks.value = true
|
||||||
@ -139,9 +210,12 @@ const loadFeedbacks = async () => {
|
|||||||
|
|
||||||
const response = await dashboardApi.getFeedbacks(params)
|
const response = await dashboardApi.getFeedbacks(params)
|
||||||
feedbacks.value = response
|
feedbacks.value = response
|
||||||
|
// 重置展开状态
|
||||||
|
expandedStates.value.clear()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载反馈列表失败:', error)
|
console.error('加载反馈列表失败:', error)
|
||||||
message.error('加载反馈列表失败')
|
message.error('加载反馈列表失败,请稍后重试')
|
||||||
|
feedbacks.value = []
|
||||||
} finally {
|
} finally {
|
||||||
loadingFeedbacks.value = false
|
loadingFeedbacks.value = false
|
||||||
}
|
}
|
||||||
@ -268,6 +342,25 @@ watch(() => props.agentId, () => {
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
color: var(--gray-800);
|
color: var(--gray-800);
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-content.collapsed {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 8;
|
||||||
|
line-clamp: 8;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-button {
|
||||||
|
padding: 0;
|
||||||
|
height: auto;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 8px;
|
||||||
|
color: var(--main-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.conversation-section {
|
.conversation-section {
|
||||||
@ -277,7 +370,7 @@ watch(() => props.agentId, () => {
|
|||||||
.conversation-info {
|
.conversation-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-item {
|
.info-item {
|
||||||
@ -297,6 +390,41 @@ watch(() => props.agentId, () => {
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 对话标题样式(独立显示)
|
||||||
|
.conversation-title {
|
||||||
|
display: block;
|
||||||
|
color: var(--gray-700);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.4;
|
||||||
|
word-break: break-word;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&.collapsed {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
line-clamp: 2;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 包含对话标题的 info-item 改为垂直布局
|
||||||
|
&:has(.conversation-title) {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-button-inline {
|
||||||
|
padding: 0;
|
||||||
|
height: auto;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--main-color);
|
||||||
|
align-self: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reason-section {
|
.reason-section {
|
||||||
@ -346,14 +474,8 @@ watch(() => props.agentId, () => {
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.feedback-card {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,19 +484,6 @@ watch(() => props.agentId, () => {
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.conversation-info {
|
|
||||||
.info-item {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 2px;
|
|
||||||
|
|
||||||
.label {
|
|
||||||
min-width: auto;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-footer {
|
.card-footer {
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -94,6 +94,8 @@ onMounted(async () => {
|
|||||||
getRemoteConfig()
|
getRemoteConfig()
|
||||||
getRemoteDatabase()
|
getRemoteDatabase()
|
||||||
fetchGithubStars() // Fetch GitHub stars on mount
|
fetchGithubStars() // Fetch GitHub stars on mount
|
||||||
|
// 预加载任务数据,确保任务中心打开时有内容
|
||||||
|
taskerStore.loadTasks()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 打印当前页面的路由信息,使用 vue3 的 setup composition API
|
// 打印当前页面的路由信息,使用 vue3 的 setup composition API
|
||||||
|
|||||||
@ -47,7 +47,7 @@
|
|||||||
<span :style="{ color: !isEvaluationSupported ? 'var(--gray-400)' : '' }">
|
<span :style="{ color: !isEvaluationSupported ? 'var(--gray-400)' : '' }">
|
||||||
RAG评估
|
RAG评估
|
||||||
<a-tooltip v-if="!isEvaluationSupported" title="仅支持 Milvus 类型的知识库">
|
<a-tooltip v-if="!isEvaluationSupported" title="仅支持 Milvus 类型的知识库">
|
||||||
<InfoCircleOutlined style="margin-left: 4px;" />
|
<Info :size="14" style="margin-left: 4px; vertical-align: middle;" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
@ -62,7 +62,7 @@
|
|||||||
<span :style="{ color: !isEvaluationSupported ? 'var(--gray-400)' : '' }">
|
<span :style="{ color: !isEvaluationSupported ? 'var(--gray-400)' : '' }">
|
||||||
评估基准
|
评估基准
|
||||||
<a-tooltip v-if="!isEvaluationSupported" title="仅支持 Milvus 类型的知识库">
|
<a-tooltip v-if="!isEvaluationSupported" title="仅支持 Milvus 类型的知识库">
|
||||||
<InfoCircleOutlined style="margin-left: 4px;" />
|
<Info :size="14" style="margin-left: 4px; vertical-align: middle;" />
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
@ -93,7 +93,7 @@ import { onMounted, reactive, ref, watch, onUnmounted, computed } from 'vue';
|
|||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useDatabaseStore } from '@/stores/database';
|
import { useDatabaseStore } from '@/stores/database';
|
||||||
import { useTaskerStore } from '@/stores/tasker';
|
import { useTaskerStore } from '@/stores/tasker';
|
||||||
import { InfoCircleOutlined } from '@ant-design/icons-vue';
|
import { Info } from 'lucide-vue-next';
|
||||||
import KnowledgeBaseCard from '@/components/KnowledgeBaseCard.vue';
|
import KnowledgeBaseCard from '@/components/KnowledgeBaseCard.vue';
|
||||||
import FileTable from '@/components/FileTable.vue';
|
import FileTable from '@/components/FileTable.vue';
|
||||||
import FileDetailModal from '@/components/FileDetailModal.vue';
|
import FileDetailModal from '@/components/FileDetailModal.vue';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user