MessageKnowledge.vue 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. <!-- 知识引用组件 -->
  2. <template>
  3. <!-- 知识引用列表 -->
  4. <div v-if="segments && segments.length > 0" class="mt-10px p-10px rounded-8px bg-[#f5f5f5]">
  5. <div class="text-14px text-[#666] mb-8px flex items-center">
  6. <Icon icon="ep:document" class="mr-5px" /> 知识引用
  7. </div>
  8. <div class="flex flex-wrap gap-8px">
  9. <div
  10. v-for="(doc, index) in documentList"
  11. :key="index"
  12. class="p-8px px-12px bg-white rounded-6px cursor-pointer transition-all hover:bg-[#e6f4ff]"
  13. @click="handleClick(doc)"
  14. >
  15. <div class="text-14px text-[#333] mb-4px">
  16. {{ doc.title }}
  17. <span class="text-12px text-[#999] ml-4px">({{ doc.segments.length }} 条)</span>
  18. </div>
  19. </div>
  20. </div>
  21. </div>
  22. <!-- 知识引用详情弹窗 -->
  23. <el-popover
  24. v-model:visible="dialogVisible"
  25. :width="600"
  26. trigger="click"
  27. placement="top-start"
  28. :offset="55"
  29. popper-class="knowledge-popover"
  30. >
  31. <template #reference>
  32. <div ref="documentRef"></div>
  33. </template>
  34. <template #default>
  35. <div class="text-16px font-bold mb-12px">{{ document?.title }}</div>
  36. <div class="max-h-[60vh] overflow-y-auto">
  37. <div
  38. v-for="(segment, index) in document?.segments"
  39. :key="index"
  40. class="p-12px border-b-solid border-b-[#eee] last:border-b-0"
  41. >
  42. <div
  43. class="block mb-8px px-8px py-2px bg-[#f5f5f5] rounded-4px text-12px text-[#666] w-fit"
  44. >
  45. 分段 {{ segment.id }}
  46. </div>
  47. <div class="text-14px leading-[1.6] text-[#333] mt-[10px]">
  48. {{ segment.content }}
  49. </div>
  50. </div>
  51. </div>
  52. </template>
  53. </el-popover>
  54. </template>
  55. <script setup lang="ts">
  56. const props = defineProps<{
  57. segments: {
  58. id: number
  59. documentId: number
  60. documentName: string
  61. content: string
  62. }[]
  63. }>()
  64. const document = ref<{
  65. id: number
  66. title: string
  67. segments: {
  68. id: number
  69. content: string
  70. }[]
  71. } | null>(null) // 知识库文档列表
  72. const dialogVisible = ref(false) // 知识引用详情弹窗
  73. const documentRef = ref<HTMLElement>() // 知识引用详情弹窗 Ref
  74. /** 按照 document 聚合 segments */
  75. const documentList = computed(() => {
  76. if (!props.segments) return []
  77. const docMap = new Map()
  78. props.segments.forEach((segment) => {
  79. if (!docMap.has(segment.documentId)) {
  80. docMap.set(segment.documentId, {
  81. id: segment.documentId,
  82. title: segment.documentName,
  83. segments: []
  84. })
  85. }
  86. docMap.get(segment.documentId).segments.push({
  87. id: segment.id,
  88. content: segment.content
  89. })
  90. })
  91. return Array.from(docMap.values())
  92. })
  93. /** 点击 document 处理 */
  94. const handleClick = (doc: any) => {
  95. document.value = doc
  96. dialogVisible.value = true
  97. }
  98. </script>