|
@@ -152,15 +152,47 @@
|
|
|
</template>
|
|
</template>
|
|
|
</template>
|
|
</template>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <!-- 入边列表 -->
|
|
|
|
|
+ <div v-if="nodeInEdges.length > 0" class="mt-3 pt-2 border-t border-base-content/10">
|
|
|
|
|
+ <div class="text-[10px] text-base-content/50 mb-1">入边 ({{ nodeInEdges.length }})</div>
|
|
|
|
|
+ <div class="space-y-1 max-h-24 overflow-y-auto">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="edge in nodeInEdges"
|
|
|
|
|
+ :key="`in-${edge.source}-${edge.type}`"
|
|
|
|
|
+ class="flex items-center gap-1 text-[10px] px-1 py-0.5 rounded hover:bg-base-300 cursor-pointer"
|
|
|
|
|
+ @click="store.selectEdge(edge)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="w-2 h-0.5" :style="{ backgroundColor: edgeTypeColors[edge.type] }"></span>
|
|
|
|
|
+ <span class="truncate flex-1">{{ getNodeName(edge.source) }}</span>
|
|
|
|
|
+ <span class="text-base-content/40">{{ edge.score?.toFixed(2) || '-' }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <!-- 出边列表 -->
|
|
|
|
|
+ <div v-if="nodeOutEdges.length > 0" class="mt-2 pt-2 border-t border-base-content/10">
|
|
|
|
|
+ <div class="text-[10px] text-base-content/50 mb-1">出边 ({{ nodeOutEdges.length }})</div>
|
|
|
|
|
+ <div class="space-y-1 max-h-24 overflow-y-auto">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="edge in nodeOutEdges"
|
|
|
|
|
+ :key="`out-${edge.target}-${edge.type}`"
|
|
|
|
|
+ class="flex items-center gap-1 text-[10px] px-1 py-0.5 rounded hover:bg-base-300 cursor-pointer"
|
|
|
|
|
+ @click="store.selectEdge(edge)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="w-2 h-0.5" :style="{ backgroundColor: edgeTypeColors[edge.type] }"></span>
|
|
|
|
|
+ <span class="truncate flex-1">{{ getNodeName(edge.target) }}</span>
|
|
|
|
|
+ <span class="text-base-content/40">{{ edge.score?.toFixed(2) || '-' }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</template>
|
|
</template>
|
|
|
- <!-- 边详情 -->
|
|
|
|
|
- <template v-else-if="store.selectedEdge">
|
|
|
|
|
|
|
+ <!-- 边详情(hover 优先于 selected) -->
|
|
|
|
|
+ <template v-else-if="displayEdge">
|
|
|
<div class="flex items-center gap-2">
|
|
<div class="flex items-center gap-2">
|
|
|
- <span class="w-4 h-0.5 shrink-0" :style="{ backgroundColor: selectedEdgeColor }"></span>
|
|
|
|
|
- <span class="text-secondary font-medium">{{ store.selectedEdge.type }} 边</span>
|
|
|
|
|
|
|
+ <span class="w-4 h-0.5 shrink-0" :style="{ backgroundColor: displayEdgeColor }"></span>
|
|
|
|
|
+ <span class="text-secondary font-medium">{{ displayEdge.type }} 边</span>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="space-y-1.5 text-[11px]">
|
|
<div class="space-y-1.5 text-[11px]">
|
|
|
- <template v-for="(value, key) in store.selectedEdge" :key="key">
|
|
|
|
|
|
|
+ <template v-for="(value, key) in displayEdge" :key="key">
|
|
|
<template v-if="value !== null && value !== undefined && value !== ''">
|
|
<template v-if="value !== null && value !== undefined && value !== ''">
|
|
|
<div v-if="typeof value !== 'object'" class="flex justify-between gap-2">
|
|
<div v-if="typeof value !== 'object'" class="flex justify-between gap-2">
|
|
|
<span class="text-base-content/50 shrink-0">{{ formatKey(key) }}</span>
|
|
<span class="text-base-content/50 shrink-0">{{ formatKey(key) }}</span>
|
|
@@ -311,6 +343,65 @@ const selectedEdgeColor = computed(() => {
|
|
|
return edgeTypeColors[store.selectedEdge.type] || '#888'
|
|
return edgeTypeColors[store.selectedEdge.type] || '#888'
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+// 显示的边(hover 优先于 selected)
|
|
|
|
|
+const displayEdge = computed(() => {
|
|
|
|
|
+ return store.hoverEdgeData || store.selectedEdge
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 显示的边颜色
|
|
|
|
|
+const displayEdgeColor = computed(() => {
|
|
|
|
|
+ if (!displayEdge.value) return '#888'
|
|
|
|
|
+ return edgeTypeColors[displayEdge.value.type] || '#888'
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 节点的入边列表(按分数降序)
|
|
|
|
|
+const nodeInEdges = computed(() => {
|
|
|
|
|
+ if (!displayNode.value) return []
|
|
|
|
|
+ const nodeId = displayNode.value.id || displayNode.value.data?.id
|
|
|
|
|
+ if (!nodeId) return []
|
|
|
|
|
+
|
|
|
|
|
+ const postGraph = store.currentPostGraph
|
|
|
|
|
+ if (!postGraph?.edges) return []
|
|
|
|
|
+
|
|
|
|
|
+ return Object.values(postGraph.edges)
|
|
|
|
|
+ .filter(e => e.target === nodeId && (e.type === '推导' || e.type === '组成'))
|
|
|
|
|
+ .sort((a, b) => (b.score || 0) - (a.score || 0))
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 节点的出边列表(按分数降序)
|
|
|
|
|
+const nodeOutEdges = computed(() => {
|
|
|
|
|
+ if (!displayNode.value) return []
|
|
|
|
|
+ const nodeId = displayNode.value.id || displayNode.value.data?.id
|
|
|
|
|
+ if (!nodeId) return []
|
|
|
|
|
+
|
|
|
|
|
+ const postGraph = store.currentPostGraph
|
|
|
|
|
+ if (!postGraph?.edges) return []
|
|
|
|
|
+
|
|
|
|
|
+ return Object.values(postGraph.edges)
|
|
|
|
|
+ .filter(e => e.source === nodeId && (e.type === '推导' || e.type === '组成'))
|
|
|
|
|
+ .sort((a, b) => (b.score || 0) - (a.score || 0))
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// 获取节点名称(根据节点ID)
|
|
|
|
|
+function getNodeName(nodeId) {
|
|
|
|
|
+ if (!nodeId) return '-'
|
|
|
|
|
+
|
|
|
|
|
+ // 先从当前帖子图中查找
|
|
|
|
|
+ const postGraph = store.currentPostGraph
|
|
|
|
|
+ if (postGraph?.nodes?.[nodeId]) {
|
|
|
|
|
+ return postGraph.nodes[nodeId].name || nodeId.split(':').pop()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 再从人设节点中查找
|
|
|
|
|
+ const personaNode = store.getNode(nodeId)
|
|
|
|
|
+ if (personaNode) {
|
|
|
|
|
+ return personaNode.name || nodeId.split(':').pop()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 回退到从ID提取名称
|
|
|
|
|
+ return nodeId.split(':').pop() || nodeId
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// 获取边ID
|
|
// 获取边ID
|
|
|
function getEdgeId(edge) {
|
|
function getEdgeId(edge) {
|
|
|
return `${edge.source}|${edge.type}|${edge.target}`
|
|
return `${edge.source}|${edge.type}|${edge.target}`
|