Files
ebiz-ai-knowledge-manage/src/views/knowledge/detail/index.vue
2025-05-23 13:49:08 +08:00

1108 lines
28 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class=" render-container">
<div class="clearfix flex align-items-c justify-content-b ">
<div class=" flex align-items-c justify-content-b" style="flex:1">
<div class="hover-back mr5">
<el-icon
class="el-icon-arrow-left"
style="font-size: 24px;cursor: pointer"
@click.native="$router.history.go(-1)"
></el-icon>
</div>
<img :src="knowledgeImage" class="header-icon" />
<div class="ml10" style="flex:1">
<div class="flex align-items-c">
<div class="mr20 header" v-if="!editKnowledge">
{{ knowledgeName ? knowledgeName : '' }}
</div>
<el-input
class="mr20 w200"
size="small"
v-else
v-model="copyKnowledgeName"
>{{ knowledgeName }}</el-input
>
<el-icon
class="fs16 el-icon-edit-outline cursor-pointer"
@click.native="editKnowledgeName"
v-if="!editKnowledge"
/>
<div v-else>
<el-button type="primary" size="medium" @click="saveKnowledgeName"
>保存</el-button
>
<el-button size="medium" @click="cancelKnowledgeName"
>取消</el-button
>
</div>
<span class="segment-content">{{
segmentedMode | filterSegmentedMode
}}</span>
<el-dropdown
size="medium"
placement="right"
v-if="relatedAppList.length > 0"
@command="handleRelatedApp"
>
<span class="segment-content cursor-pointer"
>{{ relatedAppList.length }} 个关联应用</span
>
<el-dropdown-menu>
<el-dropdown-item
v-for="item in relatedAppList"
:command="item"
:key="item.id"
disabled
>
<span>{{ item.appName }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<div class="flex align-items-c mt10 ">
<p class="fs14 knowledgeDesc" style="line-height: 20px">
描述{{ knowledgeDesc }}
</p>
</div>
<!-- <p class="mt10 fs14" style="line-height: 20px">分段模式{{ segmentedMode | filterSegmentedMode }}</p>-->
</div>
</div>
<div style="margin-left: auto" class="text-right">
<!-- <el-button type="primary" size="medium" class="normal-button" @click="jumpEditKnowledge">修改知识库</el-button>-->
<el-button
type="primary"
size="medium"
icon="el-icon-plus"
class="primary-button"
disbaled
@click="jumpAddKnowledge"
>上传知识
</el-button>
<el-button
type="primary"
size="medium"
icon="el-icon-edit-outline"
class="primary-button"
@click="jumpEditKnowledge"
>修改知识库
</el-button>
<el-button
type="primary"
size="medium"
icon="el-icon-search"
class="line-button"
@click="searchSetting"
>检索设置
</el-button>
<el-button
type="primary"
icon="el-icon-s-promotion"
size="medium"
class="line-button"
@click="handleHitTestClick"
>命中测试
</el-button>
<el-button
type="primary"
icon="el-icon-help"
size="medium"
class="line-button"
@click="handleMetaData"
>元数据
</el-button>
</div>
</div>
<div class="mt20 card-body">
<el-empty v-if="!hasList">
<div class="mt20">
<el-button
type="primary"
size="medium"
class="fs14"
@click="jumpAddKnowledge"
>立即添加</el-button
>
</div>
</el-empty>
<div class="table-container" v-else>
<div class="flex align-items-c justify-content-b">
<el-form
:model="form"
label-width="100px"
label-position="top"
inline
>
<el-form-item label="知识文件名称" prop="fileName">
<el-input
v-model="form.knowledgeNameLike"
size="medium"
placeholder="请输入知识文件名称"
@keydown.enter.native="search"
></el-input>
</el-form-item>
<el-form-item label="知识文件来源" prop="documentSource">
<el-select v-model="form.documentSource" size="medium">
<el-option label="全部" value=""></el-option>
<el-option
v-for="item in documentSourceOptions"
:label="item.label"
:value="item.value"
:key="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="上传用户" prop="createdUserLike">
<el-select v-model="form.createdUserLike" size="medium">
<el-option label="全部" value=""></el-option>
<el-option
v-for="item in createdUserOptions"
:label="item.label"
:value="item.value"
:key="item.value"
></el-option>
</el-select>
</el-form-item>
<!-- <el-form-item label="关键字">-->
<!-- <el-input-->
<!-- v-model="form.name"-->
<!-- placeholder="请输入关键字/敏感词"-->
<!-- disabled-->
<!-- size="medium"-->
<!-- ></el-input>-->
<!-- </el-form-item>-->
<el-form-item label="上传时间" prop="times">
<el-date-picker
size="medium"
style="width:100%"
v-model="form.times"
value-format="yyyy-MM-dd"
start-placeholder="开始时间"
end-placeholder="结束时间"
type="daterange"
></el-date-picker>
</el-form-item>
</el-form>
<div class="mt15 flex align-items-c justify-content-b">
<el-button size="medium" type="primary" @click="search"
>查询</el-button
>
<el-button size="medium" @click="reset">重置</el-button>
</div>
</div>
<r-table
:columns="columns"
:data="list"
:deletion="false"
:total="total"
@page-change="pageChange"
@current-change="currentChange"
:current-page="page"
:page-size="pageSize"
></r-table>
</div>
</div>
<document-drawer
:visible.sync="drawer"
:descriptions="descriptions"
:document-detail="documentDetail"
:active-segment="activeSegment"
@openMetaDrawer="handleMetaData"
@update:visible="val => (drawer = val)"
/>
<knowledgeForm
:visible.sync="drawerForm"
:datasetId="$route.query.datasetId"
@update:visible="getKnowledgeDetail"
>
</knowledgeForm>
<!-- 命中测试抽屉弹窗 -->
<el-drawer
:title="hitTestConfig.title"
:visible.sync="hitTestConfig.visible"
:wrapperClosable="false"
size="80%"
>
<hitTest></hitTest>
</el-drawer>
<!--元数据-->
<el-drawer
:title="metaDataDrawer.title"
:visible.sync="metaDataDrawer.visible"
:wrapperClosable="false"
size="30%"
>
<meta-data ref="metaData" :datasetId="$route.query.datasetId"></meta-data>
</el-drawer>
<!--元数据操作抽屉-->
<el-drawer
:title="metadataOperatorDrawer.title"
:visible.sync="metadataOperatorDrawer.visible"
size="30%"
:wrapperClosable="false"
>
<metadata-operator
ref="metadataOperator"
@openMetaDrawer="handleMetaData"
@close="close"
></metadata-operator>
</el-drawer>
<!--检索设置弹窗-->
<search-setting ref="searchSetting"></search-setting>
</div>
</template>
<script>
import {
datasetDocumentEx,
datasetQueryDelete,
datasetQuerySegments,
datasetsExPages,
datasetUpdate,
downloadOriginalFile,
getDatasetById,
relatedApps
} from '@/api/generatedApi'
import { getUserList } from '@/api/generatedApi/system'
import {
documentSourceOptions,
segmentedModeOptionsMap
} from '@/assets/js/utils/utilOptions'
import DocumentDrawer from './components/documentDetail/DocumentDrawer.vue'
import knowledgeForm from '@/views/knowledge/detail/components/knowledgeForm.vue'
import hitTest from '@/views/knowledge/detail/components/HitTest/Index.vue'
import MetaData from '@/views/knowledge/detail/components/metaData/Index.vue'
import MetadataOperator from '@/views/knowledge/detail/components/metaData/MetadataOperator.vue'
import SearchSetting from '@/views/knowledge/detail/components/SearchSetting/Index.vue'
import { displayStatus } from '@/assets/js/utils/utilOptions'
export default {
name: 'index',
// 父子组件共享
provide() {
return {
viewDocumentDetail: row => this.viewDocumentDetail(row)
}
},
data() {
return {
relatedAppList: [],
displayStatus,
hitTestConfig: {
title: '命中测试',
visible: false
},
metaDataDrawer: {
title: '元数据',
visible: false
},
metadataOperatorDrawer: {
title: '标注元数据',
visible: false
},
activeName: -1,
drawer: false,
drawerForm: false,
editKnowledge: false,
form: {
knowledgeNameLike: '',
documentSource: '',
createdUserLike: '',
times: ''
},
createdUserOptions: [],
//知识库名称
knowledgeName: '监管',
copyKnowledgeName: '监管',
knowledgeDesc: '监管',
segmentedMode: '分段模式',
knowledgeImage: '',
list: [],
page: 1,
pageSize: 10,
total: 0,
hasList: false,
documentSourceOptions,
descriptions: { data: [{ content: '' }], dataset: { knowledgeName: '' } },
activeSegmentTab: 'content',
activeSegments: [0],
activeSegment: 0,
documentDetail: {
splitRules: '',
extractRules: ''
}
}
},
props: {},
watch: {},
components: {
SearchSetting,
MetaData,
hitTest,
knowledgeForm,
DocumentDrawer,
MetadataOperator
},
methods: {
getUserData() {
getUserList({}).then(res => {
this.createdUserOptions = res.content.content.map(item => {
return {
label: item.realName,
value: item.userName
}
})
})
},
// 开启编辑 知识库标题
editKnowledgeName() {
this.editKnowledge = true
this.copyKnowledgeName = this.knowledgeName
},
// 保存知识库标题
saveKnowledgeName() {
let { datasetId } = this.$route.query
this.knowledgeName = this.copyKnowledgeName
// 调用update接口
datasetUpdate({
name: this.knowledgeName,
description: this.knowledgeDesc,
segmentedMode: this.segmentedMode,
id: datasetId
}).then(res => {
this.editKnowledge = false
})
},
// 取消保存
cancelKnowledgeName() {
this.editKnowledge = false
},
// 跳转去上传文件
jumpAddKnowledge() {
sessionStorage.removeItem('documentId')
let { datasetId } = this.$route.query
this.$router.push({
path: '/knowledge/detail/create',
query: {
datasetId: datasetId
}
})
},
// 跳转到知识库编辑
jumpEditKnowledge() {
this.drawerForm = true
},
/**
* 检索设置
*/
searchSetting() {
this.$refs.searchSetting.init()
},
/**
* @name 根据id 获取知识内容详情
* @author Chen Yuda
* @created_date 2025/4/9
* @description
**/
getKnowledgeDetail() {
let { datasetId } = this.$route.query
getDatasetById({ id: datasetId }).then(res => {
this.knowledgeName = res.content.content.name
this.knowledgeDesc = res.content.content.description
this.segmentedMode = res.content.content.segmentedMode
this.knowledgeImage = res.content.content.image
})
},
// 查询
search() {
this.page = 1
this.getKnowledgeFiledList()
},
reset() {
this.form = {
fileName: '',
documentSource: '',
createdUserLike: '',
times: ''
}
this.getKnowledgeFiledList()
},
//获取知识库文件
getKnowledgeFiledList() {
let { datasetId } = this.$route.query
datasetsExPages({
...this.form,
datasetId,
page: this.page,
pageSize: this.pageSize,
startCreatedDate: this.form.times[0]
? this.form.times[0] + ' 00:00:00'
: '',
endCreatedDate: this.form.times[1]
? this.form.times[1] + ' 23:59:59'
: ''
}).then(res => {
this.list = res.content.content.list
this.total = res.content.content.total
if (!this.hasList && this.list.length > 0) {
this.hasList = true
}
})
},
pageChange(pageSize) {
this.pageSize = pageSize
this.page = 1
this.getKnowledgeFiledList()
},
currentChange(page) {
this.page = page
this.getKnowledgeFiledList()
},
// 查看文档详情
viewDocumentDetail(row) {
this.descriptions = {
...row,
dataset: {
knowledgeName: this.knowledgeName,
segmentedMode: this.segmentedMode,
knowledgeImage: this.knowledgeImage
}
}
this.drawer = true
this.getDocumentExInfo(row.id)
},
jumpToUpload(params) {
sessionStorage.removeItem('documentId')
let { datasetId } = this.$route.query
let querys = {}
querys.datasetId = datasetId
querys.documentId = params.row.id
if (params.row.optStatus === 0) {
if (params.row.useMineru === 1) {
querys.activeLevel = 0
querys.isMd = true
} else {
querys.activeLevel = 1
}
} else {
querys.activeLevel = params.row.optStatus - 1
querys.isMd = true
}
this.$router.push({
path: '/knowledge/detail/create',
query: {
...querys
}
})
},
// 元数据操作
handleAddMetadata(row) {
this.metadataOperatorDrawer.visible = true
this.$nextTick(() => {
if (this.$refs.metadataOperator) {
// 如果需要初始化或传递数据,可以在这里处理
// 例如传递当前行数据
this.$refs.metadataOperator.init(row.id)
}
})
},
// 获取文档详细信息(包含分词规则和词频规则)
getDocumentExInfo(documentId) {
datasetDocumentEx({ documentId })
.then(res => {
if (res && res.content && res.content.content) {
const docData = res.content.content
// 提取分词规则和词频规则
this.documentDetail.splitRules = docData.splitRules
this.documentDetail.extractRules = docData.extractRules
}
})
.catch(err => {
console.error('获取文档详情失败', err)
})
},
// 下载原文件
handleDownload(row) {
let documentId = row.id
window.open(downloadOriginalFile(documentId))
},
// 提取规则文本处理可能是JSON字符串的情况
extractRuleText(rule) {
if (!rule) return '暂无规则'
try {
// 尝试解析JSON字符串
if (
typeof rule === 'string' &&
(rule.startsWith('{') || rule.startsWith('['))
) {
const parsedRule = JSON.parse(rule)
return JSON.stringify(parsedRule, null, 2)
}
return rule
} catch (e) {
// 如果解析失败,直接返回原始文本
return rule
}
},
// 删除文件
deleteKnowledge(row) {
this.$messageBox(
() => {
datasetQueryDelete({ id: row.id }).then(res => {
if (res) {
this.$message({
type: 'success',
message: '删除成功!'
})
this.page = 1
this.getKnowledgeFiledList()
}
})
},
'是否确认删除当前知识文件?',
'warning'
)
},
/**
* @name 点击命中测试控制弹窗
*/
handleHitTestClick() {
this.hitTestConfig.visible = true
},
handleMetaData() {
this.metaDataDrawer.visible = true
this.$nextTick(() => {
this.$refs.metaData.init()
})
},
close() {
this.metadataOperatorDrawer.visible = false
},
getRelatedApps() {
relatedApps({ id: this.$route.query.datasetId }).then(res => {
if (res) {
this.relatedAppList = res.content.content.intelligentAppDTOS
}
})
},
handleRelatedApp(item) {
// console.log(item)
}
},
filters: {
filterSegmentedMode(val) {
switch (val) {
case 0:
return '传统分段模式'
case 1:
return ' Q&A分段模式'
default:
return ''
}
},
filterUseMineru(val) {
let item = segmentedModeOptionsMap.find(
item => item.value === String(val)
)
return item ? item.label : '否'
}
},
created() {},
async mounted() {
this.getKnowledgeDetail()
// 获取知识库文件列表
this.getKnowledgeFiledList()
// 获取用户下拉列表
this.getUserData()
// 获取关联智能体
this.getRelatedApps()
// let documentId = sessionStorage.getItem('documentId')
// if (documentId) {
// setTimeout(() => {
// let row = this.list.filter(item => item.id === documentId)
// this.viewDocumentDetail(row[0])
// sessionStorage.removeItem('documentId')
// }, 1000)
},
computed: {
datasetId() {
return {
datasetId: this.$route.query.datasetId,
segmentedMode: this.filterSegmentedMode(this.segmentedMode)
}
},
columns(vm) {
return [
{
key: '知识文件名称',
prop: 'knowledgeName'
},
{
key: '知识文件来源',
prop: 'documentSource',
render: (h, params) => {
let text = documentSourceOptions.find(
item => item.value === String(params.row.documentSource)
).label
return h(
'div',
{
class: 'flex align-items-c'
},
[
h(
'span',
{
props: {
// iconClass: params.row.useMineru === 1 ? 'miner' : 'none'
},
class: 'mr5',
style: {
// display: params.row.useMineru === 1 ? '' : 'none',
width: '19px',
height: '19px',
fontSize: '13px',
borderRadius: '6px',
textAlign: 'center',
lineHeight: '19px',
color: '#fff',
background:
params.row.useMineru === 1 ? '#4f47f5' : 'unset'
}
},
params.row.useMineru === 1 ? '预' : ''
),
h('span', {}, text)
]
)
}
},
{
key: '状态',
prop: 'statusLabel'
// render: (h, params) => {
// let label = this.displayStatus.find(item => {
// return item.value === params.row.displayStatus
// }).label
// return h('div', label ? label : 'N/A')
// }
},
{
key: '召回次数',
prop: 'hitCount',
render: (h, params) => {
return h('div', !params.row.hitCount ? '0' : params.row.hitCount)
}
},
{
key: '上传用户',
prop: 'createdUser',
width: '100',
render: (h, params) => {
return h(
'div',
!params.row.createdUser ? '-' : params.row.createdUser
)
}
},
{
key: '上传时间',
prop: 'createdDate',
width: '100'
},
{
key: '操作',
prop: 'knowledgeDesc',
isRedraw: true,
render: (h, params) => {
return h('div', [
h('el-button', {
class: 'floatSpan',
props: {
type: 'primary',
size: 'mini',
icon: 'el-icon-tickets',
title: '查看详情'
},
on: {
click: () => this.viewDocumentDetail(params.row)
}
}),
h(
'el-button',
{
class: 'floatSpan',
props: {
type: 'danger',
size: 'mini',
icon: 'el-icon-delete',
title: '删除'
},
on: {
click: () => this.deleteKnowledge(params.row)
}
}
// '删除'
),
// h(
// 'el-button',
// {
// class: 'normal-button',
// props: {
// type: 'primary',
// size: 'mini',
// disabled: true,
// icon: 'el-icon-edit-outline',
// title: '编辑'
// },
// on: {}
// },
// '编辑'
// ),
h(
'el-button',
{
class: 'floatSpan',
props: {
type: 'primary',
size: 'mini',
icon: 'el-icon-help',
title: '添加元数据',
disabled: params.row.documentStatus !== 1
},
on: {
click: () => this.handleAddMetadata(params.row)
}
}
// '标注元数据'
),
h(
'el-button',
{
class: 'floatSpan',
props: {
type: 'primary',
size: 'mini',
icon: 'el-icon-download',
title: '下载原文件'
},
on: {
click: () => this.handleDownload(params.row)
}
}
// '标注元数据'
),
params.row.optStatus < 4
? h(
'el-button',
{
class: 'floatSpan',
props: {
type: 'primary',
size: 'mini',
icon: 'el-icon-video-play',
title: '继续处理'
},
on: {
click: () => this.jumpToUpload(params)
}
}
// '文件拆分处理'
)
: ''
])
}
}
]
}
}
}
</script>
<style scoped lang="scss">
.ellipsis-title {
// 最多展示一行 省略号展示
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
word-break: break-all;
white-space: nowrap;
//width: 50%;
&:hover {
color: #409eff;
}
}
.drawer-content {
padding: 20px;
display: flex;
flex-direction: column;
}
.file-header {
display: flex;
align-items: center;
margin-bottom: 20px;
.file-icon {
font-size: 36px;
margin-right: 15px;
color: #409eff;
}
.file-info {
h3 {
margin: 0 0 10px 0;
font-size: 18px;
font-weight: bold;
}
}
}
.segment-info {
margin-top: 20px;
padding: 15px;
background: #f5f7fa;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
.segment-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
h4 {
margin: 0;
font-size: 18px;
color: #333;
}
}
.rule-card {
//background: #fff;
//border-radius: 4px;
//padding: 15px;
//box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
.rule-item {
margin-bottom: 12px;
h5 {
display: inline-block;
margin: 0 8px 0 0;
font-weight: bold;
color: #666;
}
&:last-child {
margin-bottom: 0;
}
}
.rule-detail-item {
margin-top: 8px;
padding: 10px;
background: #f9f9f9;
border-radius: 8px;
border: 1px solid #e0e0e0;
margin-right: 10px;
p {
margin: 5px 0;
}
.rule-label {
font-size: 14px;
color: #606266;
margin-bottom: 10px;
}
.rule-value {
font-size: 14px;
color: #606266;
font-weight: 500;
margin-left: 15px;
}
}
}
}
.el-collapse-item__header {
font-weight: bold;
//color: #409eff;
}
.mr10 {
margin-right: 10px;
}
.el-descriptions {
margin-bottom: 20px;
}
.collapse-title {
font-weight: bold;
color: #333;
font-size: 16px;
}
.collapse-content {
margin-top: 10px;
color: #666;
}
.tags {
margin-top: 10px;
}
.el-tag {
margin-right: 5px;
}
.segment-content-container {
margin-top: 20px;
}
.segment-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
h4 {
margin: 0;
font-size: 16px;
}
.segment-summary {
color: #606266;
}
}
.segment-split-view {
display: flex;
}
.segment-list {
//flex: 1;
width: 45%;
margin-right: 10px;
}
.segment-list-item {
cursor: pointer;
margin-bottom: 10px;
padding: 15px 0 10px 15px;
border-radius: 15px;
transition: background-color 0.3s;
font-size: 14px;
&:hover {
background: #f3f5f7;
}
&.active {
background: #f3f5f7;
}
p {
margin: 10px 0;
}
.segment-number {
color: #0a84ff;
}
.segment-keywords {
color: #606266;
}
}
.segment-detail {
width: 55%;
}
.segment-content {
background: #f6f8fa;
border-radius: 4px;
font-family: PingFangSC;
font-size: 12px;
color: #4e5969;
line-height: 17px;
text-align: left;
font-style: normal;
margin-left: 10px;
padding: 2px 5px;
}
.segment-empty {
padding: 10px;
text-align: center;
}
.clearfix {
padding-bottom: 20px;
position: relative;
&:after {
position: absolute;
bottom: 0;
left: 0;
content: '';
width: 100%;
border-bottom: 1px solid #979797;
// 宽度缩小
transform: scaleY(0.2);
}
& .header {
font-family: PingFangSC, PingFang SC;
font-weight: 600;
font-size: 16px;
color: #000000;
text-align: left;
font-style: normal;
}
}
.hover-back {
padding: 10px 5px;
&:hover {
background: rgba(87, 104, 161, 0.2);
border-radius: 5px;
}
}
.knowledgeDesc {
display: -webkit-box; // 支持多行省略
-webkit-line-clamp: 1; // 限制为两行
-webkit-box-orient: vertical; // 垂直方向排列
overflow: hidden; // 隐藏超出部分
text-overflow: ellipsis; // 显示省略号
max-width: 80%; // 限制宽度
}
</style>