diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index cefa2896..2f21726f 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -55,8 +55,8 @@ :class="{ 'has-content': hasAgentStateContent }" :title="hasAgentStateContent ? '查看工作状态' : '暂无工作状态'" > - - {{ totalAgentStateItems }} + + 状态({{ totalAgentStateItems }})
-

对话/工具调用分布

+

对话/工具调用分布 (TOP 3)

@@ -182,11 +182,31 @@ const initConversationToolChart = () => { const conversationData = props.agentStats.agent_conversation_counts || [] const toolData = props.agentStats.agent_tool_usage || [] - // 获取所有智能体ID - const allAgentIds = [...new Set([ - ...conversationData.map(item => item.agent_id), - ...toolData.map(item => item.agent_id) - ])] + // 获取所有智能体ID并按对话数+工具调用数排序,取前3个 + const allAgentStats = {} + + // 统计每个智能体的总数据量(对话数 + 工具调用数) + conversationData.forEach(item => { + if (!allAgentStats[item.agent_id]) { + allAgentStats[item.agent_id] = { conversation: 0, tool: 0, total: 0 } + } + allAgentStats[item.agent_id].conversation = item.conversation_count + allAgentStats[item.agent_id].total += item.conversation_count + }) + + toolData.forEach(item => { + if (!allAgentStats[item.agent_id]) { + allAgentStats[item.agent_id] = { conversation: 0, tool: 0, total: 0 } + } + allAgentStats[item.agent_id].tool = item.tool_usage_count + allAgentStats[item.agent_id].total += item.tool_usage_count + }) + + // 按总数据量降序排序,取前3个 + const topAgentIds = Object.entries(allAgentStats) + .sort(([,a], [,b]) => b.total - a.total) + .slice(0, 3) + .map(([agentId]) => agentId) const option = { tooltip: { @@ -216,7 +236,7 @@ const initConversationToolChart = () => { }, xAxis: { type: 'category', - data: allAgentIds, + data: topAgentIds, axisLine: { lineStyle: { color: '#e8e8e8' @@ -248,7 +268,7 @@ const initConversationToolChart = () => { { name: '对话数', type: 'bar', - data: allAgentIds.map(agentId => { + data: topAgentIds.map(agentId => { const item = conversationData.find(d => d.agent_id === agentId) return item ? item.conversation_count : 0 }), @@ -267,7 +287,7 @@ const initConversationToolChart = () => { { name: '工具调用数', type: 'bar', - data: allAgentIds.map(agentId => { + data: topAgentIds.map(agentId => { const item = toolData.find(d => d.agent_id === agentId) return item ? item.tool_usage_count : 0 }), diff --git a/web/src/views/DataBaseView.vue b/web/src/views/DataBaseView.vue index 4f2b6368..dd7ffdd8 100644 --- a/web/src/views/DataBaseView.vue +++ b/web/src/views/DataBaseView.vue @@ -871,57 +871,60 @@ onMounted(() => { } .database, .graphbase { - background-color: white; - box-shadow: 0px 1px 2px 0px rgba(16,24,40,.06),0px 1px 3px 0px rgba(16,24,40,.1); - border: 2px solid white; - transition: box-shadow 0.2s ease-in-out; - - &:hover { - box-shadow: 0px 4px 6px -2px rgba(16,24,40,.03),0px 12px 16px -4px rgba(16,24,40,.08); - } + background: linear-gradient(145deg, #ffffff 0%, #fafbfc 100%); + box-shadow: + 0px 1px 2px 0px rgba(16,24,40,.06), + inset 0px 1px 0px 0px rgba(255,255,255,.6); + border: 1px solid rgba(233, 236, 239, 0.6); + transition: none; + position: relative; } .dbcard, .database { width: 100%; - padding: 10px; - border-radius: 12px; - height: 160px; - padding: 20px; + padding: 24px; + border-radius: 16px; + height: 180px; cursor: pointer; display: flex; flex-direction: column; position: relative; // 为绝对定位的锁定图标提供参考 + overflow: hidden; .private-lock-icon { position: absolute; - top: 24px; - right: 16px; - color: var(--gray-700); - background: var(--gray-100); + top: 20px; + right: 20px; + color: var(--gray-600); + background: linear-gradient(135deg, rgba(255,255,255,0.9) 0%, rgba(248,250,252,0.9) 100%); font-size: 12px; - border-radius: 6px; - padding: 5px; - z-index: 1; + border-radius: 8px; + padding: 6px; + z-index: 2; + box-shadow: 0px 2px 4px rgba(0,0,0,0.1); + border: 1px solid rgba(229, 231, 235, 0.5); } + .top { display: flex; align-items: center; - height: 50px; - margin-bottom: 10px; + height: 54px; + margin-bottom: 14px; .icon { - width: 50px; - height: 50px; - font-size: 28px; - margin-right: 10px; + width: 54px; + height: 54px; + font-size: 26px; + margin-right: 14px; display: flex; justify-content: center; align-items: center; - background-color: #F5F8FF; - border-radius: 8px; - border: 1px solid #E0EAFF; + background: var(--main-30); + border-radius: 12px; + border: 1px solid rgba(1, 97, 121, 0.05); color: var(--main-color); + position: relative; } .info { @@ -931,35 +934,47 @@ onMounted(() => { } h3 { - font-size: 16px; - font-weight: bold; + font-size: 17px; + font-weight: 600; + letter-spacing: -0.02em; + line-height: 1.4; } p { - color: var(--gray-900); - font-size: small; + color: var(--gray-700); + font-size: 13px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; + margin-top: 4px; + font-weight: 400; .created-time-inline { color: var(--gray-500); - font-size: 12px; + font-size: 11px; + font-weight: 400; + background: var(--gray-50); + padding: 2px 6px; + border-radius: 4px; } } } } .description { - color: var(--gray-900); + color: var(--gray-600); overflow: hidden; display: -webkit-box; - line-clamp: 1; - -webkit-line-clamp: 1; + line-clamp: 2; + -webkit-line-clamp: 2; -webkit-box-orient: vertical; text-overflow: ellipsis; - margin-bottom: 10px; + margin-bottom: 12px; + font-size: 13px; + line-height: 1.5; + font-weight: 400; + flex: 1; } diff --git a/web/src/views/GraphView.vue b/web/src/views/GraphView.vue index 15cb67b7..568d9bcc 100644 --- a/web/src/views/GraphView.vue +++ b/web/src/views/GraphView.vue @@ -71,9 +71,14 @@ @@ -150,6 +155,7 @@ import HeaderComponent from '@/components/HeaderComponent.vue'; import { neo4jApi } from '@/apis/graph_api'; import { useUserStore } from '@/stores/user'; import GraphCanvas from '@/components/GraphCanvas.vue'; +import GraphInfoPanel from '@/components/GraphInfoPanel.vue'; const configStore = useConfigStore(); const cur_embed_model = computed(() => configStore.config?.embed_model); @@ -294,20 +300,6 @@ const graphStatusText = computed(() => { return graphInfo.value?.status === 'open' ? '已连接' : '已关闭'; }); -// 新增:将图谱信息拆分为多条标签用于展示 -const graphTags = computed(() => { - const tags = []; - const dbName = graphInfo.value?.graph_name; - const entityCount = graphInfo.value?.entity_count; - const relationCount = graphInfo.value?.relationship_count; - - if (dbName) tags.push({ key: 'name', text: `图谱 ${dbName}`, type: 'blue' }); - if (typeof entityCount === 'number') tags.push({ key: 'entities', text: `实体 ${graphData.nodes.length} of ${entityCount}`, type: 'success' }); - if (typeof relationCount === 'number') tags.push({ key: 'relations', text: `关系 ${graphData.edges.length} of ${relationCount}`, type: 'purple' }); - if (unindexedCount.value > 0) tags.push({ key: 'unindexed', text: `未索引 ${unindexedCount.value}`, type: 'warning' }); - - return tags; -}); // 为未索引节点添加索引 const indexNodes = () => { @@ -343,6 +335,32 @@ const getAuthHeaders = () => { return userStore.getAuthHeaders(); }; +const exportGraphData = () => { + const dataStr = JSON.stringify({ + nodes: graphData.nodes, + edges: graphData.edges, + graphInfo: graphInfo.value, + exportTime: new Date().toISOString() + }, null, 2); + + const dataBlob = new Blob([dataStr], { type: 'application/json' }); + const url = URL.createObjectURL(dataBlob); + const link = document.createElement('a'); + link.href = url; + link.download = `graph-data-${new Date().toISOString().slice(0, 10)}.json`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + + message.success('图谱数据已导出'); +}; + +const showQueryHistory = () => { + // 这里可以实现查询历史功能 + message.info('查询历史功能开发中...'); +}; + const openLink = (url) => { window.open(url, '_blank') }