Skip to content

Commit 8240eb1

Browse files
committed
refactor: 优化知识来源显示
--story=1016652 --user=王孝刚 【南区】应用对话回复支持显示引用分段来知识库的文档来源等信息 https://www.tapd.cn/57709429/s/1616642
1 parent 936e55d commit 8240eb1

File tree

5 files changed

+137
-16
lines changed

5 files changed

+137
-16
lines changed

apps/application/chat_pipeline/I_base_chat_pipeline.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class ParagraphPipelineModel:
1919

2020
def __init__(self, _id: str, document_id: str, dataset_id: str, content: str, title: str, status: str,
2121
is_active: bool, comprehensive_score: float, similarity: float, dataset_name: str, document_name: str,
22-
hit_handling_method: str, directly_return_similarity: float):
22+
hit_handling_method: str, directly_return_similarity: float, meta: dict = None):
2323
self.id = _id
2424
self.document_id = document_id
2525
self.dataset_id = dataset_id
@@ -33,6 +33,7 @@ def __init__(self, _id: str, document_id: str, dataset_id: str, content: str, ti
3333
self.document_name = document_name
3434
self.hit_handling_method = hit_handling_method
3535
self.directly_return_similarity = directly_return_similarity
36+
self.meta = meta
3637

3738
def to_dict(self):
3839
return {
@@ -46,7 +47,8 @@ def to_dict(self):
4647
'comprehensive_score': self.comprehensive_score,
4748
'similarity': self.similarity,
4849
'dataset_name': self.dataset_name,
49-
'document_name': self.document_name
50+
'document_name': self.document_name,
51+
'meta': self.meta,
5052
}
5153

5254
class builder:
@@ -58,6 +60,7 @@ def __init__(self):
5860
self.dataset_name = None
5961
self.hit_handling_method = None
6062
self.directly_return_similarity = 0.9
63+
self.meta = {}
6164

6265
def add_paragraph(self, paragraph):
6366
if isinstance(paragraph, Paragraph):
@@ -97,14 +100,19 @@ def add_similarity(self, similarity: float):
97100
self.similarity = similarity
98101
return self
99102

103+
def add_meta(self, meta: dict):
104+
self.meta = meta
105+
return self
106+
100107
def build(self):
101108
return ParagraphPipelineModel(str(self.paragraph.get('id')), str(self.paragraph.get('document_id')),
102109
str(self.paragraph.get('dataset_id')),
103110
self.paragraph.get('content'), self.paragraph.get('title'),
104111
self.paragraph.get('status'),
105112
self.paragraph.get('is_active'),
106113
self.comprehensive_score, self.similarity, self.dataset_name,
107-
self.document_name, self.hit_handling_method, self.directly_return_similarity)
114+
self.document_name, self.hit_handling_method, self.directly_return_similarity,
115+
self.meta)
108116

109117

110118
class IBaseChatPipelineStep:

apps/application/chat_pipeline/step/search_dataset_step/impl/base_search_dataset_step.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def reset_paragraph(paragraph: Dict, embedding_list: List) -> ParagraphPipelineM
7979
.add_document_name(paragraph.get('document_name'))
8080
.add_hit_handling_method(paragraph.get('hit_handling_method'))
8181
.add_directly_return_similarity(paragraph.get('directly_return_similarity'))
82+
.add_meta(paragraph.get('meta'))
8283
.build())
8384

8485
@staticmethod

apps/application/sql/list_dataset_paragraph_by_paragraph_id.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ SELECT
22
paragraph.*,
33
dataset."name" AS "dataset_name",
44
"document"."name" AS "document_name",
5+
"document"."meta" AS "meta",
56
"document"."hit_handling_method" AS "hit_handling_method",
67
"document"."directly_return_similarity" as "directly_return_similarity"
78
FROM

ui/src/components/ai-chat/KnowledgeSource.vue

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,28 @@
99
</div>
1010
<div class="mt-8" v-if="!isWorkFlow(props.type)">
1111
<el-space wrap>
12-
<el-button
13-
v-for="(dataset, index) in data.dataset_list"
14-
:key="index"
15-
size="small"
16-
class="source_dataset-button"
17-
@click="openParagraph(data, dataset.id)"
18-
>
19-
<span class="ellipsis" :title="dataset.name"> {{ dataset.name }}</span>
20-
</el-button>
12+
<div v-for="(paragraph, index) in uniqueParagraphList" :key="index">
13+
<el-icon class="mr-4" :size="25">
14+
<img :src="getIconPath(paragraph.document_name)" style="width: 90%" alt="" />
15+
</el-icon>
16+
<span
17+
v-if="!paragraph.source_url"
18+
class="ellipsis"
19+
:title="paragraph?.document_name?.trim()"
20+
>
21+
{{ paragraph?.document_name }}
22+
</span>
23+
<a
24+
v-else
25+
@click="openLink(paragraph.source_url)"
26+
class="ellipsis"
27+
:title="paragraph?.document_name?.trim()"
28+
>
29+
<span :title="paragraph?.document_name?.trim()">
30+
{{ paragraph?.document_name }}
31+
</span>
32+
</a>
33+
</div>
2134
</el-space>
2235
</div>
2336

@@ -42,7 +55,7 @@
4255
<ExecutionDetailDialog ref="ExecutionDetailDialogRef" />
4356
</template>
4457
<script setup lang="ts">
45-
import { ref } from 'vue'
58+
import { computed, ref } from 'vue'
4659
import ParagraphSourceDialog from './ParagraphSourceDialog.vue'
4760
import ExecutionDetailDialog from './ExecutionDetailDialog.vue'
4861
import { isWorkFlow } from '@/utils/application'
@@ -57,6 +70,15 @@ const props = defineProps({
5770
default: ''
5871
}
5972
})
73+
const iconMap: { [key: string]: string } = {
74+
doc: '../../assets/doc-icon.svg',
75+
docx: '../../assets/docx-icon.svg',
76+
pdf: '../../assets/pdf-icon.svg',
77+
md: '../../assets/md-icon.svg',
78+
txt: '../../assets/txt-icon.svg',
79+
xls: '../../assets/xls-icon.svg',
80+
xlsx: '../../assets/xlsx-icon.svg'
81+
}
6082
6183
const ParagraphSourceDialogRef = ref()
6284
const ExecutionDetailDialogRef = ref()
@@ -66,6 +88,40 @@ function openParagraph(row: any, id?: string) {
6688
function openExecutionDetail(row: any) {
6789
ExecutionDetailDialogRef.value.open(row)
6890
}
91+
const uniqueParagraphList = computed(() => {
92+
const seen = new Set()
93+
return (
94+
props.data.paragraph_list?.filter((paragraph: any) => {
95+
const key = paragraph.document_name.trim()
96+
if (seen.has(key)) {
97+
return false
98+
}
99+
seen.add(key)
100+
// 判断如果 meta 属性不是 {} 需要json解析 转对象
101+
if (paragraph.meta && typeof paragraph.meta === 'string') {
102+
paragraph.meta = JSON.parse(paragraph.meta)
103+
paragraph.source_url = paragraph.meta.source_url
104+
}
105+
return true
106+
}) || []
107+
)
108+
})
109+
110+
function getIconPath(documentName: string) {
111+
const extension = documentName.split('.').pop()?.toLowerCase()
112+
if (!documentName || !extension) return new URL(`${iconMap['doc']}`, import.meta.url).href
113+
if (iconMap && extension && iconMap[extension]) {
114+
return new URL(`${iconMap[extension]}`, import.meta.url).href
115+
}
116+
return new URL(`${iconMap['doc']}`, import.meta.url).href
117+
}
118+
function openLink(url: string) {
119+
// 如果url不是以/结尾,加上/
120+
if (url && !url.endsWith('/')) {
121+
url += '/'
122+
}
123+
window.open(url, '_blank')
124+
}
69125
</script>
70126
<style lang="scss" scoped>
71127
.source_dataset-button {

ui/src/components/ai-chat/component/ParagraphCard.vue

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,48 @@
1818
<template #footer>
1919
<div class="footer-content flex-between">
2020
<el-text class="flex align-center" style="width: 70%">
21-
<el-icon class="mr-4">
22-
<Document />
21+
<el-icon class="mr-4" :size="25">
22+
<img
23+
src="@/assets/doc-icon.svg"
24+
style="width: 90%"
25+
alt=""
26+
v-if="data?.document_name?.includes('doc')"
27+
/>
28+
<img
29+
src="@/assets/docx-icon.svg"
30+
style="width: 90%"
31+
alt=""
32+
v-else-if="data?.document_name?.includes('docx')"
33+
/>
34+
<img
35+
src="@/assets/pdf-icon.svg"
36+
style="width: 90%"
37+
alt=""
38+
v-else-if="data?.document_name?.includes('pdf')"
39+
/>
40+
<img
41+
src="@/assets/md-icon.svg"
42+
style="width: 90%"
43+
alt=""
44+
v-else-if="data?.document_name?.includes('md')"
45+
/>
46+
<img
47+
src="@/assets/xls-icon.svg"
48+
style="width: 90%"
49+
alt=""
50+
v-else-if="data?.document_name?.includes('xls')"
51+
/>
52+
<img
53+
src="@/assets/txt-icon.svg"
54+
style="width: 90%"
55+
alt=""
56+
v-else-if="data?.document_name?.includes('txt')"
57+
/>
58+
<img src="@/assets/doc-icon.svg" style="width: 90%" alt="" v-else />
2359
</el-icon>
24-
<span class="ellipsis" :title="data?.document_name?.trim()"> {{ data?.document_name.trim() }}</span>
60+
<span class="ellipsis" :title="data?.document_name?.trim()">
61+
{{ data?.document_name.trim() }}</span
62+
>
2563
</el-text>
2664
<div class="flex align-center" style="line-height: 32px">
2765
<AppAvatar class="mr-8 avatar-blue" shape="square" :size="18">
@@ -45,6 +83,23 @@ const props = defineProps({
4583
default: 0
4684
}
4785
})
86+
const iconMap: { [key: string]: string } = {
87+
doc: '../../assets/doc-icon.svg',
88+
docx: '../../assets/docx-icon.svg',
89+
pdf: '../../assets/pdf-icon.svg',
90+
md: '../../assets/md-icon.svg',
91+
txt: '../../assets/txt-icon.svg',
92+
xls: '../../assets/xls-icon.svg',
93+
xlsx: '../../assets/xlsx-icon.svg'
94+
}
95+
function getIconPath(documentName: string) {
96+
const extension = documentName.split('.').pop()?.toLowerCase()
97+
if (!documentName || !extension) return new URL(`${iconMap['doc']}`, import.meta.url).href
98+
if (iconMap && extension && iconMap[extension]) {
99+
return new URL(`${iconMap[extension]}`, import.meta.url).href
100+
}
101+
return new URL(`${iconMap['doc']}`, import.meta.url).href
102+
}
48103
</script>
49104
<style lang="scss" scoped>
50105
.paragraph-source-card-height {

0 commit comments

Comments
 (0)