2025/5/21 [new feat]

新增了
  分段分页与搜索的ui
  分段的分页 与搜索功能还未对接 接口
  分段启用禁用对接完成
  命中测试增加了命中文件
This commit is contained in:
陈昱达
2025-05-21 16:07:26 +08:00
parent 0a59c4578c
commit 95016472a1
10 changed files with 210 additions and 399 deletions

View File

@@ -468,3 +468,12 @@ export function exportSegment(data) {
`/datasetDocumentEx/segment/export?documentId=${data.documentId}` `/datasetDocumentEx/segment/export?documentId=${data.documentId}`
) )
} }
// 分段启用禁用
export function switchStatus(data) {
return request({
url: getUrl(`/datasetDocumentEx/segment/switchStatus`),
method: 'post',
data
})
}

View File

@@ -346,3 +346,16 @@
color: $--color-primary; color: $--color-primary;
} }
} }
.el-input-group__append .el-button,
.el-input-group__append .el-select,
.el-input-group__prepend .el-button,
.el-input-group__prepend .el-select {
margin: -10px 0;
}
.el-input-group__append,
.el-input-group__prepend {
background: #fff;
border-color: #fff;
padding: 0;
}

View File

@@ -90,3 +90,16 @@
} }
} }
} }
.el-pagination__sizes {
& .el-select {
& .el-input {
&.el-input--mini {
& .el-input__inner {
height: 22px;
line-height: 22px;
}
}
}
}
}

View File

@@ -17,7 +17,7 @@ const RenderSlot = {
'el-tooltip', 'el-tooltip',
{ {
props: { props: {
placement: 'left', placement: 'bottom',
content: first[0].data.props.title content: first[0].data.props.title
// effect: 'light' // effect: 'light'
} }

View File

@@ -16,6 +16,7 @@ export default {
}, },
data() { data() {
return { return {
agentType,
agentConfig: { agentConfig: {
title: '', title: '',
component: 'agent', component: 'agent',
@@ -26,12 +27,17 @@ export default {
visibleRange: 0 visibleRange: 0
} }
}, },
searchAppType: '',
searchOption: { searchOption: {
nameLike: '', nameLike: '',
appType: '',
handleSearch: async () => { handleSearch: async () => {
this.page = 1 this.page = 1
this.list = [] this.list = []
await this.fetchAgentList({ nameLike: this.searchOption.nameLike }) await this.fetchAgentList({
nameLike: this.searchOption.nameLike,
appType: this.searchOption.appType
})
} }
}, },
/** /**
@@ -201,7 +207,30 @@ export default {
@keydown.enter.native="searchOption.handleSearch" @keydown.enter.native="searchOption.handleSearch"
> >
<template slot="prepend"> <template slot="prepend">
<el-button slot="append" icon="el-icon-search"></el-button> <el-select
style="width: 140px"
v-model="searchOption.appType"
placeholder="请选择"
slot="prepend"
clearable
@change="searchOption.handleSearch"
>
<el-option label="全部分类" value=""></el-option>
<el-option
v-for="item in agentType"
:label="item.label"
:value="item.value"
:key="item.value"
></el-option>
</el-select>
</template>
<template slot="append">
<el-button
slot="append"
icon="el-icon-search"
@click="searchOption.handleSearch"
></el-button>
</template> </template>
</el-input> </el-input>
</el-col> </el-col>

View File

@@ -263,6 +263,14 @@ export default {
{{ index + keyword }} {{ index + keyword }}
#{{ keyword }} #{{ keyword }}
</el-tag> </el-tag>
<p
class="fs12 r mt5 theme-line-back fw600"
style="background-clip:text;color:transparent"
v-if="contentItem.knowledgeName"
>
命中文档{{ contentItem.knowledgeName }}
</p>
</section> </section>
<!-- score --> <!-- score -->
<span class="score"> <span class="score">

View File

@@ -54,7 +54,6 @@
<script> <script>
import knowledgeInfo from '@/views/track/views/knowledge-info/Index.vue' import knowledgeInfo from '@/views/track/views/knowledge-info/Index.vue'
import TextModel from './TextModel.vue'
import QAModel from './QAModel.vue' import QAModel from './QAModel.vue'
import { import {
documentSourceOptions, documentSourceOptions,
@@ -67,7 +66,6 @@ import MetadataOperator from '@/views/knowledge/detail/components/metaData/Metad
export default { export default {
components: { components: {
MetadataOperator, MetadataOperator,
TextModel,
QAModel, QAModel,
RenderFile, RenderFile,
knowledgeInfo knowledgeInfo
@@ -77,7 +75,7 @@ export default {
visible: Boolean, visible: Boolean,
descriptions: { descriptions: {
type: Object, type: Object,
default: () => ({data: []}) default: () => ({ data: [] })
}, },
documentDetail: { documentDetail: {
type: Object, type: Object,

View File

@@ -19,8 +19,8 @@
</div> </div>
<div <div
class="actions ml10 flex align-items-c mr10" class="actions flex align-items-c mr10 "
style="gap: 10px" style="gap: 10px;margin-top: -2px"
v-if="!noEdit" v-if="!noEdit"
> >
<!-- 删除 编辑--> <!-- 删除 编辑-->
@@ -29,28 +29,43 @@
@click.native.stop="handleSegmentClick(index)" @click.native.stop="handleSegmentClick(index)"
></el-icon> ></el-icon>
<el-icon <el-icon
class="el-icon-delete" class="el-icon-delete mr10"
@click.native.stop="deleteSegment(segment, index)" @click.native.stop="deleteSegment(segment, index)"
></el-icon> ></el-icon>
<!-- <el-switch--> <el-switch
<!-- size="mini"--> size="mini"
<!-- @click.native.stop=""--> @click.native.stop=""
<!-- v-model="segment.enabled"--> @change="$event => changeEnable($event, segment.id)"
<!-- ></el-switch>--> v-model="segment.enabled"
></el-switch>
</div> </div>
</div> </div>
<!-- {{ descriptions.doc_form }}-->
<div> <!-- 普通分段-->
<div v-if="descriptions.doc_form === 'text_model'">
<div>
<p class="context">{{ segment.content }}</p>
</div>
<div
class="segment-keywords flex"
v-if="segment.keywords && segment.keywords.length"
>
<p
v-for="(item, index) in segment.keywords"
:key="index"
class="mr10"
>
#{{ item }}
</p>
</div>
</div>
<!-- QA分段-->
<div v-else>
<div class="context"> <div class="context">
<p>Q {{ segment.content }}</p> <p>Q {{ segment.content }}</p>
<p>A {{ segment.answer }}</p> <p>A {{ segment.answer }}</p>
</div> </div>
</div> </div>
<!-- <div class="segment-keywords flex" v-if="segment.keywords && segment.keywords.length">-->
<!-- <p v-for="(item, index) in segment.keywords" :key="index" class="mr10">#{{ item }}</p>-->
<!-- </div>-->
<!-- <span class="segment-chars">{{ segment.characters || 0 }} characters</span>-->
</div> </div>
</div> </div>
@@ -73,14 +88,17 @@
" "
> >
<div class="segment-content"> <div class="segment-content">
<!-- Q & A-->
<div> <div>
<div> <div>
<p class="">QUESTION</p> <p class="" v-if="descriptions.doc_form !== 'text_model'">
QUESTION
</p>
<p contenteditable class="resetHtml fs13" ref="content"> <p contenteditable class="resetHtml fs13" ref="content">
{{ descriptions.data[activeSegment].content }} {{ descriptions.data[activeSegment].content }}
</p> </p>
</div> </div>
<div> <div v-if="descriptions.doc_form !== 'text_model'">
<p class="">ANSWER</p> <p class="">ANSWER</p>
<p contenteditable class="resetHtml fs13" ref="answer"> <p contenteditable class="resetHtml fs13" ref="answer">
{{ descriptions.data[activeSegment].answer }} {{ descriptions.data[activeSegment].answer }}
@@ -95,11 +113,11 @@
" "
style="flex-wrap: wrap" style="flex-wrap: wrap"
> >
<span>关键词 </span> <span class="mb10">关键词 </span>
<el-tag <el-tag
v-for="(item, index) in descriptions.data[activeSegment].keywords" v-for="(item, index) in descriptions.data[activeSegment].keywords"
:key="index" :key="index"
class="mr10" class="mr10 mb10"
size="medium" size="medium"
type="info" type="info"
:closable=" :closable="
@@ -110,18 +128,18 @@
{{ item }} {{ item }}
</el-tag> </el-tag>
<el-input <el-input
class="input-new-tag" class="input-new-tag mb5"
v-if="createdTag" v-if="createdTag"
v-model="inputValue" v-model="inputValue"
ref="saveTagInput" ref="saveTagInput"
size="small" size="mini"
@keyup.enter.native="handleInputConfirm" @keyup.enter.native="handleInputConfirm"
/> />
<el-button <el-button
v-if="!noEdit && !createdTag" v-if="!noEdit && !createdTag"
size="medium" size="medium"
@click="showInput" @click="showInput"
class="fs12" class="fs12 mb10"
style="padding: 7px;border-radius: 4px;font-size: 12px" style="padding: 7px;border-radius: 4px;font-size: 12px"
>添加标签</el-button >添加标签</el-button
> >
@@ -166,6 +184,7 @@ export default {
dialogVisible: false dialogVisible: false
} }
}, },
inject: ['changeEnable'],
mounted() {}, mounted() {},
methods: { methods: {
deleteSegment(segment, index) { deleteSegment(segment, index) {
@@ -191,7 +210,7 @@ export default {
let params = { let params = {
keywords: this.descriptions.data[this.activeSegment].keywords, keywords: this.descriptions.data[this.activeSegment].keywords,
content: this.$refs.content.innerHTML, content: this.$refs.content.innerHTML,
answer: this.$refs.answer.innerHTML answer: this.$refs.answer ? this.$refs.answer.innerHTML : undefined
} }
this.saveSegment(params, () => { this.saveSegment(params, () => {
this.$message.success('保存成功') this.$message.success('保存成功')
@@ -207,7 +226,7 @@ export default {
let params = { let params = {
keywords: this.descriptions.data[this.activeSegment].keywords, keywords: this.descriptions.data[this.activeSegment].keywords,
content: this.$refs.content.innerHTML, content: this.$refs.content.innerHTML,
answer: this.$refs.answer.innerHTML answer: this.$refs.answer ? this.$refs.answer.innerHTML : undefined
} }
// this.saveSegment(params) // this.saveSegment(params)
}, },
@@ -219,7 +238,7 @@ export default {
this.inputValue this.inputValue
], ],
content: this.$refs.content.innerHTML, content: this.$refs.content.innerHTML,
answer: this.$refs.answer.innerHTML answer: this.$refs.answer ? this.$refs.answer.innerHTML : undefined
} }
this.saveSegment(params) this.saveSegment(params)
}, },

View File

@@ -1,348 +0,0 @@
<template>
<div class="segment-split-view">
<div class="segment-list">
<!-- <el-checkbox-group v-model="selectedSegments">-->
<div
class="segment-list-item"
v-for="(segment, index) in descriptions.data"
:key="index"
:class="{ active: activeSegment === index }"
@click="handleSegmentClick(index)"
>
<div class="flex align-items-c justify-content-b" style="gap: 8px">
<div class="flex align-items-c " style="gap: 8px">
<!-- <el-checkbox class="mr pt15" :label="index">-->
<span class="el-icon-s-unfold"></span>
<span class="segment-number"
>分段 - {{ (index + 1).toString().padStart(2, '0') }}</span
>
<span v-if="segment.word_count > 0">
· {{ segment.word_count }}个字符 ·</span
>
<span> {{ segment.hit_count }} 次召回次数</span>
</div>
<div
class="actions ml10 flex align-items-c mr10"
style="gap: 10px"
v-if="!noEdit"
>
<!-- 删除 编辑-->
<el-icon
class="el-icon-edit mr10"
@click.native.stop="handleSegmentClick(index)"
></el-icon>
<el-icon
class="el-icon-delete"
@click.native.stop="deleteSegment(segment, index)"
></el-icon>
<!-- <el-switch-->
<!-- size="mini"-->
<!-- @click.native.stop=""-->
<!-- v-model="segment.enabled"-->
<!-- ></el-switch>-->
</div>
</div>
<!-- </el-checkbox>-->
<div>
<div>
<p class="context">{{ segment.content }}</p>
</div>
<div
class="segment-keywords flex"
v-if="segment.keywords && segment.keywords.length"
>
<p
v-for="(item, index) in segment.keywords"
:key="index"
class="mr10"
>
#{{ item }}
</p>
</div>
</div>
</div>
<!-- </el-checkbox-group>-->
</div>
<!-- 弹窗 -->
<el-drawer
title="分段详情"
:wrapperClosable="false"
:visible.sync="dialogVisible"
:modal="false"
append-to-body
:before-close="handleClose"
>
<div
style="height:calc(100% - 32px);overflow-x: hidden;overflow-y: auto"
v-if="
activeSegment !== null &&
descriptions.data &&
descriptions.data.length > 0
"
>
<div class="segment-content">
<p contenteditable class="resetHtml fs13" ref="content">
{{ descriptions.data[activeSegment].content }}
</p>
<div
class="flex align-items-c mt20"
v-if="
descriptions.data[activeSegment].keywords &&
descriptions.data[activeSegment].keywords.length
"
style="flex-wrap: wrap"
>
<span>关键词 </span>
<el-tag
v-for="(item, index) in descriptions.data[activeSegment].keywords"
:key="item"
class="mr10 ellipsis"
size="medium"
:closable="
descriptions.data[activeSegment].keywords.length > 1 && !noEdit
"
type="info"
@close="tagClose(item)"
>
{{ item }}
</el-tag>
<el-input
class="input-new-tag"
v-if="createdTag"
v-model="inputValue"
ref="saveTagInput"
size="small"
@keyup.enter.native="handleInputConfirm"
/>
<el-button
v-if="!noEdit && !createdTag"
size="medium"
@click="showInput"
class="fs12"
style="padding: 7px;border-radius: 4px;font-size: 12px"
>添加标签</el-button
>
</div>
</div>
</div>
<div class="text-right">
<el-button @click="dialogVisible = false" size="medium">关闭</el-button>
<el-button @click="saveUS" size="medium" type="primary" v-if="!noEdit"
>保存</el-button
>
</div>
</el-drawer>
</div>
</template>
<script>
import { segmentDelete, segmentUpdate } from '@/api/generatedApi'
export default {
name: 'TextModel',
props: {
noEdit: {
type: Boolean,
default: false
},
visible: Boolean,
descriptions: {
type: Object,
default: () => ({ data: [] })
},
parentForm: {
type: Object,
default: () => ({})
}
},
data() {
return {
createdTag: false,
inputValue: '',
activeSegment: null,
dialogVisible: false,
selectedSegments: []
}
},
watch: {},
methods: {
deleteSegment(segment, index) {
this.$messageBox(
() => {
segmentDelete({
documentId: this.parentForm.id,
segmentId: segment.id
}).then(res => {
if (res) {
this.$message.success('删除成功')
this.descriptions.data.splice(index, 1)
}
})
},
'是否删除当前分段,删除后不可恢复',
'warning',
'提示'
)
},
saveUS() {
let params = {
keywords: this.descriptions.data[this.activeSegment].keywords,
content: this.$refs.content.innerHTML,
answer: null
}
this.saveSegment(params, () => {
this.$message.success('保存成功')
})
},
showInput() {
this.createdTag = true
},
tagClose() {
this.descriptions.data[this.activeSegment].keywords.splice(index, 1)
let params = {
keywords: this.descriptions.data[this.activeSegment].keywords,
content: this.$refs.content.innerHTML,
answer: null
}
this.saveSegment(params)
},
handleInputConfirm() {
let params = {
keywords: [
...this.descriptions.data[this.activeSegment].keywords,
this.inputValue
],
content: this.$refs.content.innerHTML,
answer: null
}
this.saveSegment(params)
},
saveSegment(params, fun) {
params.documentId = this.parentForm.id
params.segmentId = this.descriptions.data[this.activeSegment].id
segmentUpdate(params).then(res => {
if (res) {
this.descriptions.data[this.activeSegment] = JSON.parse(
JSON.stringify(res.content.content)
)
// res.content.content.keywords
this.descriptions.data[this.activeSegment].keywords = JSON.parse(
JSON.stringify(res.content.content.keywords)
)
// this.descriptions.data[this.activeSegment].answer =
// res.content.content.answer
this.createdTag = false
this.inputValue = ''
if (fun) {
fun()
}
}
})
},
handleSegmentClick(index) {
this.activeSegment = index
this.dialogVisible = true
},
handleClose() {
this.dialogVisible = false
}
}
}
</script>
<style scoped lang="scss">
.segment-split-view {
display: block;
}
.context {
width: auto;
overflow: hidden;
//省略号
text-overflow: ellipsis;
white-space: nowrap;
color: #3a3f4f;
font-size: 14px;
}
.segment-list {
width: 100%;
height: 100%;
overflow-y: auto;
}
.segment-list-item {
color: #70778d;
cursor: pointer;
padding: 15px 0 20px 15px;
border-radius: 2px;
transition: background-color 0.3s;
font-size: 14px;
border-bottom: 1px solid #f3f5f7;
position: relative;
//margin-bottom: 10px;
&.active {
background: #f3f5f7;
}
.actions {
position: absolute;
display: none; /* 默认隐藏 */
background: #fff;
padding: 3px 5px;
border-radius: 5px;
right: 10px;
top: 15px;
}
&:hover .actions {
display: flex; /* 悬停或激活时显示操作按钮 */
gap: 10px;
}
&:hover {
background: #f3f5f7;
}
&.active {
background: #f3f5f7;
}
p {
margin: 15px 0;
}
.segment-number {
//color: #0a84ff;
}
.segment-keywords {
font-weight: 400;
font-size: 12px;
color: #70778d;
flex-wrap: wrap;
p {
margin: 0 10px 0 0;
}
}
}
.segment-content {
padding: 20px;
background: #f9f9f9;
border-radius: 15px;
//white-space: pre-wrap;
line-height: 35px;
color: #666;
}
.input-new-tag {
width: 100px;
}
</style>

View File

@@ -4,9 +4,9 @@ import {
importSegment, importSegment,
queryTask, queryTask,
segmentTemplate, segmentTemplate,
exportSegment exportSegment,
switchStatus
} from '@/api/generatedApi' } from '@/api/generatedApi'
import TextModel from '@/views/knowledge/detail/components/documentDetail/TextModel.vue'
import QAModel from '@/views/knowledge/detail/components/documentDetail/QAModel.vue' import QAModel from '@/views/knowledge/detail/components/documentDetail/QAModel.vue'
import RenderFile from '@/components/RenderFile/Index.vue' import RenderFile from '@/components/RenderFile/Index.vue'
import MetadataOperator from '@/views/knowledge/detail/components/metaData/MetadataOperator.vue' import MetadataOperator from '@/views/knowledge/detail/components/metaData/MetadataOperator.vue'
@@ -19,14 +19,17 @@ export default {
components: { components: {
MetadataOperator, MetadataOperator,
QAModel, QAModel,
TextModel,
RenderFile, RenderFile,
AddSegment, AddSegment,
BatchAddSegment BatchAddSegment
}, },
data() { data() {
return { return {
segmentPage: 1,
segmentLimit: 10,
segmentTotal: 101,
searchText: '', searchText: '',
keyWords: '',
batchAddSegmentDialog: false, batchAddSegmentDialog: false,
iframeSrc: window.location.origin, iframeSrc: window.location.origin,
newForm: {}, newForm: {},
@@ -35,6 +38,13 @@ export default {
batchSegmentList: [] batchSegmentList: []
} }
}, },
provide() {
return {
changeEnable: this.changeEnable
}
},
inject: ['viewDocumentDetail'], inject: ['viewDocumentDetail'],
props: { props: {
@@ -97,6 +107,41 @@ export default {
} }
}, },
methods: { methods: {
changeEnable(state, id) {
let params = {
documentId: this.form.id,
segmentIds: [id],
status: state ? 0 : 1 //枚举值0-启用 1-禁用
}
switchStatus(params).then(res => {
if (res) {
console.log(res)
}
})
},
inputSegment() {
if (!this.searchText.trim()) {
this.keyWords = ''
this.segmentPage = 1
this._getSplitResultPreview()
}
},
searchSegment() {
this.keyWords = this.searchText
this.segmentPage = 1
this._getSplitResultPreview()
},
sizeChange(size) {
this.segmentPage = 1
this.segmentLimit = size
this._getSplitResultPreview()
},
currentChange(page) {
this.segmentPage = page
this._getSplitResultPreview()
},
importSegmentToKnowledge() { importSegmentToKnowledge() {
let form = new FormData() let form = new FormData()
form.append('documentId', this.form.id) form.append('documentId', this.form.id)
@@ -165,8 +210,15 @@ export default {
if (this.descriptions && this.descriptions.data) { if (this.descriptions && this.descriptions.data) {
this.descriptions.data = [] this.descriptions.data = []
} }
datasetQuerySegments({ documentId: this.form.id }).then(res => { let params = {
documentId: this.form.id,
page: this.segmentPage,
size: this.segmentLimit,
keyword: this.keyWords
}
datasetQuerySegments(params).then(res => {
this.descriptions = res.content.content this.descriptions = res.content.content
this.segmentTotal = res.content.content.total
}) })
}, },
openMetaDrawer() { openMetaDrawer() {
@@ -194,11 +246,11 @@ export default {
<template> <template>
<div <div
class=" upload-info-container" class="upload-info-container"
:class="!fullscreen ? 'render-container' : ''" :class="!fullscreen ? 'render-container' : ''"
> >
<div class="form-container"> <div class="form-container ph10">
<el-descriptions class="" :column="4"> <el-descriptions class="mt10" :column="4">
<el-descriptions-item label="知识库"> <el-descriptions-item label="知识库">
{{ newForm.datasetName }} {{ newForm.datasetName }}
</el-descriptions-item> </el-descriptions-item>
@@ -255,9 +307,10 @@ export default {
<el-input <el-input
style="width: 50vh;" style="width: 50vh;"
size="small" size="small"
disabled
placeholder="分段搜索" placeholder="分段搜索"
v-model="searchText" v-model="searchText"
@input="inputSegment"
@keydown.enter.native="searchSegment"
></el-input> ></el-input>
</div> </div>
<el-row :gutter="20" class="mt10"> <el-row :gutter="20" class="mt10">
@@ -272,24 +325,40 @@ export default {
<!-- 知识内容 --> <!-- 知识内容 -->
<div class="content-card el-card "> <div class="content-card el-card ">
<div <div
class="knowledge-content"
v-if="descriptions" v-if="descriptions"
class="knowledge-content"
v-loading="descriptions.data && descriptions.data.length <= 0" v-loading="descriptions.data && descriptions.data.length <= 0"
> >
<text-model
:noEdit="noEdit"
v-if="descriptions.doc_form === 'text_model'"
:descriptions="descriptions"
:parentForm="form"
:key="descriptions.data.length"
/>
<q-a-model <q-a-model
:class="segmentTotal > 0 ? '' : ''"
:noEdit="noEdit" :noEdit="noEdit"
v-else-if="descriptions.doc_form === 'qa_model'"
:descriptions="descriptions" :descriptions="descriptions"
:parentForm="form" :parentForm="form"
:key="descriptions.data.length" style="height: calc(100% - 40px);overflow-x: hidden;overflow-y: auto"
/> />
<div
style="height: 40px;width:100%;position: sticky;bottom: -1px;color:Red;background: #fff;"
class="flex align-items-c justify-content-b"
>
<div>
<el-pagination
background
small
hide-on-single-page
@size-change="sizeChange"
@current-change="currentChange"
:total="segmentTotal"
:currentPage="segmentPage"
layout="prev,sizes, pager, next"
:page-sizes="[10, 20, 30, 40, 50]"
/>
</div>
<!-- <div v-for="item in getSegmentPages()">-->
<!-- {{ item }}-->
<!-- </div>-->
</div>
</div> </div>
<div v-else class="p20 flex align-items-c"> <div v-else class="p20 flex align-items-c">
<div class="mr10 fs14">暂无知识内容</div> <div class="mr10 fs14">暂无知识内容</div>
@@ -393,7 +462,8 @@ export default {
overflow: hidden; overflow: hidden;
.knowledge-content { .knowledge-content {
height: calc(100vh - 250px); position: relative;
height: calc(100vh - 270px);
overflow-y: auto; overflow-y: auto;
//&::-webkit-scrollbar { //&::-webkit-scrollbar {
// width: 4px; // width: 4px;
@@ -402,7 +472,7 @@ export default {
} }
.full-height { .full-height {
height: calc(100vh - 250px); height: calc(100vh - 270px);
padding-bottom: 20px; padding-bottom: 20px;
} }