Merge branch 'dev' into poc/bpic

This commit is contained in:
陈昱达
2025-05-26 13:29:49 +08:00
48 changed files with 1853 additions and 711 deletions

1
.env
View File

@@ -4,3 +4,4 @@ NODE_ENV = 'dev' // 如果是生产环境请记得切换为production
# flag
VUE_APP_FLAG='dev'
VUE_APP_ADMIN='http://39.104.123.254:7195'
VUE_APP_DOCS='http://39.104.123.254:7546/'

View File

@@ -265,8 +265,9 @@ export function uploadFileByTemplate(data) {
export function datasetQuerySegments(data) {
return request({
url: getUrl(`/datasetDocumentEx/querySegments`),
method: 'get',
params: data
method: 'post',
data,
noLoading: true
})
}
@@ -421,6 +422,7 @@ export function uploadImage(data) {
})
}
// 分段编辑
export function segmentUpdate(data) {
return request({
url: getUrl(`/datasetDocumentEx/segment/update`),
@@ -428,3 +430,63 @@ export function segmentUpdate(data) {
data
})
}
// 分段删除
export function segmentDelete(data) {
return request({
url: getUrl(`/datasetDocumentEx/segment/delete`),
method: 'get',
params: data
})
}
// 新增分段
export function segmentCreate(data) {
return request({
url: getUrl(`/datasetDocumentEx/segment/create`),
method: 'post',
data
})
}
// 分段模板
export function segmentTemplate(model) {
// model 分别时 qa general
return getUrl(`/template/download/${model}`)
}
// 导入分段
export function importSegment(data) {
return request({
url: getUrl(`/datasetDocumentEx/segment/batchImport`),
method: 'post',
data
})
}
// 导出分段
export function exportSegment(data) {
return getUrl(
`/datasetDocumentEx/segment/export?documentId=${data.documentId}`
)
}
// 分段启用禁用
export function switchStatus(data) {
return request({
url: getUrl(`/datasetDocumentEx/segment/switchStatus`),
method: 'post',
data,
noLoading: true
})
}
// 知识库关联智能体
export function relatedApps(data) {
return request({
url: getUrl(`/datasetsEx/relatedApps`),
method: 'get',
params: data
})
}
// 下载知识原文件
export function downloadOriginalFile(data) {
return getUrl(`/datasetDocumentEx/download/original_file?documentId=` + data)
}

View File

@@ -85,7 +85,8 @@ const agentEdit = data => {
const getAccessToken = data => {
return request({
url: getUrl('/third/access_token'),
method: 'get'
method: 'get',
noLoading: true
})
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 906 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 915 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 827 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 727 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -0,0 +1,22 @@
.el-dropdown-menu__item:focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
background-color: $--color-primary;
color: #fff;
}
.el-dropdown-link {
cursor: pointer;
color: $--color-primary;
}
//
//.el-button {
// &.el-dropdown__caret-button {
// padding-left: 5px;
// padding-right: 5px;
// }
//}
.el-dropdown {
& .el-dropdown__caret-button {
padding-left: 5px;
padding-right: 5px;
}
}

View File

@@ -346,3 +346,19 @@
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;
}
.el-select-dropdown.is-multiple .el-select-dropdown__item.selected {
color: $--color-primary;
}

View File

@@ -72,3 +72,34 @@
//
//}
//.el-pagination.is-background .el-pager li:not(.disabled):hover
.tableBtn {
padding: 5px;
&.is-plain {
&:hover {
background: $--color-primary;
color: #fff;
}
}
&.el-button--danger {
&.is-plain:focus,
&.is-plain:hover {
color: #fff;
background: #ff1a1a;
border-color: #ff1a1a;
}
}
}
.el-pagination__sizes {
& .el-select {
& .el-input {
&.el-input--mini {
& .el-input__inner {
height: 22px;
line-height: 22px;
}
}
}
}
}

View File

@@ -10,6 +10,7 @@
@import 'renderSass/message';
@import 'renderSass/upload';
@import 'renderSass/slider';
@import 'renderSass/drop';
html,
body,

View File

@@ -9,6 +9,9 @@
:closeOnClickModal="closeOnClickModal"
:close-on-press-escape="closeOnPressEscape"
>
<div slot="title">
<slot name="title">{{ title }}</slot>
</div>
<div class="render-dialog-body">
<slot name="default"></slot>
</div>

View File

@@ -6,6 +6,7 @@
icon-class="pdf"
style="width: 20px;height: 20px"
class-name="mr10"
v-if='showPdf'
/>
{{ fileName }}
</div>
@@ -52,7 +53,7 @@
element-loading-text="读取文档中..."
>
<iframe
v-if="isShowPdf"
v-if="showPdf"
id="iframe"
ref="iframe"
:src="
@@ -69,11 +70,35 @@
element-loading-text="正在识别中..."
>
<div class="el-card ebiz-pdf" style="height: 100%;overflow: hidden">
<div style="height: 47px;width: 100%;text-align: center;">
<div class="flex align-items-c justify-content-c miner-navbar fs14">
<el-icon
class="el-icon-caret-left cursor-pointer"
@click.native="changePageDown('down')"
>
</el-icon>
<input
style="width: 50px;text-align: center"
v-model="copyValue"
:min="0"
class="fs13 miner-input el-input"
@blur="blursChange"
></input>
<span class="mh5">/ </span>
<span>{{ mdJsons.length }}</span>
<el-icon
class="el-icon-caret-right cursor-pointer"
@click.native="changePageUp('up')"
>
></el-icon
>
</div>
</div>
<div ref="scrollView" v-show="tab === '0'">
<div
class="view-body"
id="viewBody"
style="height:calc(100vh - 180px);overflow-y: scroll;overflow-x:hidden"
style="height:calc(100vh - 227px);overflow-y: scroll;overflow-x:hidden"
v-html="markdownHtml"
></div>
</div>
@@ -122,9 +147,11 @@ export default {
name: 'index',
data() {
return {
copyValue: 1,
mdJsons: {},
finishenEnd: false,
fileName: '',
showPdf:true,
recordId: '', //pdf 记录的id
endEmit: false,
tab: '0',
@@ -278,8 +305,6 @@ export default {
page: {
handler(newVal, oldVal) {
if (newVal) {
// this.changePage(newVal, this.tab)
this.getPDFDetailMarkDown()
}
}
@@ -314,19 +339,77 @@ export default {
},
//changePage
// 分页发生改变时
changePage(page) {
let documentId = document.getElementById(`view-code-${page - 1}`)
let viewBody = document.getElementById('viewBody')
if (this.tab === '1') {
documentId = document.getElementById(`ebiz-code-${page - 1}`)
viewBody = document.getElementById('md-editor')
changePage(type) {
// if (this.page <= 0 || this.page >= this.mdJsons.length) {
// return false
// }
//
// switch (type) {
// case 'down':
// this.page -= 1
// break
// case 'up':
// this.page += 1
// break
// },
//
// let documentId = document.getElementById(`view-code-${page - 1}`)
// let viewBody = document.getElementById('viewBody')
// if (this.tab === '1') {
// documentId = document.getElementById(`ebiz-code-${page - 1}`)
// viewBody = document.getElementById('md-editor')
// }
// if (documentId) {
// viewBody.scrollTo({
// top: documentId.offsetTop - 130,
// behavior: 'smooth'
// })
// }
},
changePageDown(type) {
if (this.page <= 0) {
return false
}
if (documentId) {
viewBody.scrollTo({
top: documentId.offsetTop - 130,
behavior: 'smooth'
})
switch (type) {
case 'down':
this.page -= 1
break
case 'up':
this.page += 1
break
}
this.copyValue = this.page
},
blursChange(e) {
if (!e.target.value.trim()) {
this.copyValue = this.page
}
if (e.target.value < 0) {
this.copyValue = this.page
}
if (e.target.value > this.mdJsons.length) {
this.copyValue = this.page
}
this.page = this.copyValue
},
changePageUp(type) {
if (this.page >= this.mdJsons.length) {
return false
}
switch (type) {
case 'down':
this.page -= 1
break
case 'up':
this.page += 1
break
}
this.copyValue = this.page
},
//重新识别表格
retryMinerImage(chooseItem, loading, tableElement) {
@@ -580,7 +663,9 @@ export default {
JSON.parse(JSON.stringify(res.content.content.pdf_info))
)
// this.$refs.iframe 重新刷新iframe
if( this.$refs.iframe){
this.$refs.iframe.contentWindow.location.reload()
}
this.getPDFDetailMarkDown()
})
@@ -727,6 +812,11 @@ export default {
if (res) {
this.mdPges = res.content.content.pages
this.fileName = res.content.content.name
// 隐藏非pdf文件
this.showPdf = this.fileName.indexOf('.pdf') > 0;
this.recordId = res.content.content.recordId
switch (res.content.content.status) {
case 0:
@@ -841,6 +931,21 @@ export default {
}
</script>
<style scoped lang="scss">
.miner-navbar {
margin-top: 11px;
border-bottom: 1px solid #ebeef5;
padding-bottom: 10px;
gap: 5px;
& .miner-input {
height: 20px ;
line-height: 20px;
border: 1px solid #BBBBBC;
outline:none;
border-radius: 2px;
}
}
.lineHeight25 {
line-height: 25px;
}

View File

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

View File

@@ -58,13 +58,13 @@
class="normal-button"
:disabled="disabled"
icon="el-icon-delete"
@click="del(scope.row, scope.$index)"
@click.stop="del(scope.row, scope.$index)"
></el-button>
<el-button
class="normal-button"
icon="el-icon-edit-outline"
:disabled="disabled"
@click="edit(scope.row, scope.$index)"
@click.stop="edit(scope.row, scope.$index)"
></el-button>
</div>
<!--如果渲染的是selfBtn-->
@@ -75,10 +75,11 @@
:disabled="
item.disabled !== undefined ? item.disabled : disabled
"
:class="item.class"
:type="btn.type ? btn.type : 'primary'"
:size="btn.size ? btn.size : 'mini'"
:key="btnIndex"
@click="handlerMethods(scope.row, scope.$index, btn)"
@click.stop="handlerMethods(scope.row, scope.$index, btn)"
:label="btn.name"
>
{{ btn.name }}
@@ -133,7 +134,7 @@
<el-button
type="primary"
plain
@click="
@click.stop="
e => {
addRow(scope.row, scope.$index)
}
@@ -147,7 +148,7 @@
<el-button
type="danger"
plain
@click="
@click.stop="
e => {
delRow(scope.row, scope.$index)
}

View File

@@ -1,6 +1,11 @@
<script>
import { DIFY_URL } from '@/config/base-url'
import { agentAdd, agentDetail, agentEdit } from '@/api/intelligent-agent/list'
import {
agentAdd,
agentDetail,
agentEdit,
getAccessToken
} from '@/api/intelligent-agent/list'
export default {
name: 'workflow',
props: {
@@ -11,6 +16,7 @@ export default {
},
data() {
return {
timer: null,
agentDetail: {},
resetName: '',
@@ -76,6 +82,15 @@ export default {
this.worker.instance.postMessage('init-worker', [
this.messageChannel.instance.port2
])
// 每 5 分钟执行一次
// ✅ 先清理已有 timer防止重复创建
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
this.timer = setInterval(() => {
this.initTokenRefresh()
}, 1000 * 60 * 5)
},
destroyed() {
// 组件销毁时清除 web worker 和 messageChannel
@@ -83,6 +98,10 @@ export default {
this.worker.instance = void 0
this.messageChannel.instance.port1.close()
this.messageChannel.instance = void 0
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
},
watch: {
'$route.query': {
@@ -96,8 +115,6 @@ export default {
// 当路由参数 id 变化时,更新 dify 的 src
if (thirdAppId) {
console.log(`current params:`, this.agent.params)
this.agent.src = `${DIFY_URL}/app/${thirdAppId}/workflow?${
this.params
}`
@@ -110,6 +127,21 @@ export default {
}
},
methods: {
async initTokenRefresh() {
try {
const thirdContent = await getAccessToken()
const { refreshToken, accessToken } = thirdContent.content.content
// if (localStorage.getItem('refresh_token') !== refreshToken) {
localStorage.setItem('refresh_token', refreshToken)
// }
// if (localStorage.getItem('console_token') !== accessToken) {
localStorage.setItem('console_token', accessToken)
// }
} catch (error) {
console.error('Token refresh failed:', error)
}
},
handleAgentLoad() {
// 把 messageChannel port1 的所有权传递给 iframe
this.$refs.agent.contentWindow.postMessage('init-iframe', '*', [

View File

@@ -6,7 +6,9 @@
<span class="title">你好{{ userInfo.realName }}</span>
<div class="subtitle">欢迎使用{{ title }}</div>
<div class="role-info">
当前角色{{ userInfo.roleNames[0] || '未指定' }}
当前角色{{
userInfo.roleNames ? userInfo.roleNames[0] : '未指定'
}}
</div>
</div>
<div class="right-container">
@@ -29,7 +31,7 @@
<span class="card-title">智能体</span>
<div class="card-content">智能体管理列表</div>
</div>
<div class="card-item">
<div class="card-item" @click="openTag">
<span class="card-title">操作手册</span>
<div class="card-content">智能管理平台使用手册</div>
</div>
@@ -426,6 +428,9 @@ export default {
this.$router.push({
path: url
})
},
openTag() {
window.open(process.env.VUE_APP_DOCS)
}
},
computed: {

View File

@@ -1,11 +1,15 @@
<template>
<div class="navbar">
<hamburger :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<hamburger
:is-active="sidebar.opened"
class="hamburger-container"
@toggleClick="toggleSideBar"
/>
<breadcrumb class="breadcrumb-container" />
<div class="right-menu" v-if="$store.state.settings.sidebarLogo">
<el-dropdown class="avatar-container" trigger="click">
<el-dropdown class="avatar-container" trigger="click" size="medium">
<div class="avatar-wrapper">
<el-avatar :size="size" :src="circleUrl" class="user-avatar" />
<i class="el-icon-caret-bottom" />

View File

@@ -4,6 +4,7 @@
<el-dropdown
class="avatar-container"
trigger="click"
size="medium"
placement="top-start"
>
<div class="avatar-wrapper">

View File

@@ -10,6 +10,7 @@
v-for="item in options"
:value="item.value"
:label="item.label"
:key="item.value"
></el-option>
</el-select>
<div

View File

@@ -20,9 +20,24 @@ export default {
}
}
},
watch: {
'dialog.agent': {
handler(val) {
this.image = val.image
this.imageType = val.imageType
this.background = val.backgroundColor
},
immediate: true,
deep: true
}
},
data() {
return {
isImage: '0',
image: '',
imageType: '',
popover: false,
background: '',
chooseBack: [
@@ -79,8 +94,17 @@ export default {
},
chooseGround(colors) {
this.$set(this.dialog.agent, 'backgroundColor', colors)
this.background = colors
},
saveEmoji() {
this.dialog.agent.image = this.image
this.dialog.agent.imageType = this.imageType
this.$set(this.dialog.agent, 'backgroundColor', this.background)
this.popover = false
},
/**
* 提交表单
*/
@@ -104,8 +128,9 @@ export default {
},
// 选择emoji
selectEmoji(e) {
this.dialog.agent.image = e.data
this.dialog.agent.imageType = 'emoji'
this.image = e.data
this.imageType = 'emoji'
// this.dialog.agent.imageType = 'emoji'
},
// 选择是否是上传图片
changeUploadImage(e) {
@@ -129,11 +154,8 @@ export default {
label-position="top"
label-width="80px"
>
<div class="flex ">
<el-form-item label="名称与图标" prop="appName" style="flex:1">
<el-input v-model="dialog.agent.appName" size="medium" />
</el-form-item>
<el-form-item label="" prop="imageId" class="mt30 ml20">
<div class="flex align-items-c ">
<el-form-item label="名称与图标" prop="imageId" class="mt30">
<el-popover trigger="click" v-model="popover">
<div style="width: 28vw">
<el-radio-group
@@ -150,22 +172,34 @@ export default {
<div class="mt10" v-if="isImage === '0'">
<VEmojiPicker @select="selectEmoji" class="emoji " />
<div
v-if="dialog.agent.image"
v-if="image"
class="flex mt10 back-content justify-content-b"
>
<template
v-for="item in chooseBack"
v-if="dialog.agent.imageType === 'emoji'"
v-if="imageType === 'emoji'"
>
<div
class="emoji-background"
:style="`background:${item}`"
@click="chooseGround(item)"
:class="item === background ? 'activeBack' : ''"
>
{{ dialog.agent.image }}
{{ image }}
</div>
</template>
</div>
<div class="flex align-items-c justify-content-b">
<!-- <el-button size="medium" style="flex:1">取消</el-button>-->
<el-button
@click="saveEmoji"
size="medium"
type="primary"
style="flex:1"
>确认
</el-button>
</div>
</div>
<!-- 图片切割-->
@@ -201,12 +235,27 @@ export default {
<div v-if="dialog.agent.imageType === 'emoji'">
{{ dialog.agent.image }}
</div>
<div v-else style="width: 100%">
<img :src="dialog.agent.image" alt="" style="width: 100%;" />
<div
v-else-if="dialog.agent.imageType === 'image'"
style="width: 100%;height: 100%;overflow: hidden;border-radius: 8px"
>
<img
v-if="dialog.agent.image"
:src="dialog.agent.image"
alt=""
style="width: 100%;height: 100%"
/>
</div>
<div v-else style="font-size: 30px" class=" fw600 white">
{{ dialog.agent.appName[0] }}
</div>
</div>
</el-popover>
</el-form-item>
<el-form-item label="" prop="appName" style="flex:1" class="mt60">
<el-input v-model="dialog.agent.appName" size="medium" />
</el-form-item>
</div>
<el-form-item label="选择应用类型" required prop="appType">
@@ -272,6 +321,28 @@ export default {
</el-form>
</div>
</template>
<style>
.emoji {
width: 100% !important;
height: 100%;
& #Categories {
& .category {
&.active {
border-bottom: 3px solid var(--color-primary);
}
}
}
& #InputSearch {
& .container-search {
& input {
outline: none;
border: 1px solid var(--color-primary);
background: #fff;
}
}
}
}
</style>
<style lang="scss" scoped>
@import '@/assets/sass/renderSass/theme.scss';
@@ -302,27 +373,12 @@ export default {
text-align: center;
//margin-left: 10px;
margin-bottom: 10px;
}
.emoji {
width: 100% !important;
height: 100%;
& #Categories {
& .category {
&.active {
border-bottom: 3px solid var(--color-primary);
}
}
}
& #InputSearch {
& .container-search {
& input {
outline: none;
border: 1px solid var(--color-primary);
background: #fff;
}
}
&.activeBack {
border: 1px dashed #676f83;
}
}
.card-container {
display: flex;
flex-wrap: wrap;

View File

@@ -16,6 +16,7 @@ export default {
},
data() {
return {
agentType,
agentConfig: {
title: '',
component: 'agent',
@@ -26,12 +27,17 @@ export default {
visibleRange: 0
}
},
searchAppType: '',
searchOption: {
nameLike: '',
appType: '',
handleSearch: async () => {
this.page = 1
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"
>
<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>
</el-input>
</el-col>
@@ -247,7 +276,10 @@ export default {
}`
"
>
<div v-if="listItem.imageType === 'image'">
<div
v-if="listItem.imageType === 'image'"
style="width: 100%;height: 100%"
>
<img
:src="listItem.image"
alt=""
@@ -315,10 +347,10 @@ export default {
<el-button
class="line-button"
size="medium"
icon="el-icon-odometer"
icon="el-icon-more"
@click.stop="jumpToLogs(listItem.id)"
type="primary"
>日志与监测
>更多
</el-button>
</div>
</el-card>
@@ -528,10 +560,10 @@ export default {
text-align: left;
font-style: normal;
height: 35px;
height: 18px;
margin: 0;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;

View File

@@ -263,6 +263,14 @@ export default {
{{ index + keyword }}
#{{ keyword }}
</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>
<!-- score -->
<span class="score">

View File

@@ -36,7 +36,7 @@
<template slot="title">
<div class="flex align-items-c">
<img
src="@/assets/images/konwledge/knowledge-5.png"
:src="`${static}/loadImage/knowledge-5.png`"
alt=""
style="height: 20px"
/>
@@ -79,6 +79,7 @@ export default {
props: {},
data() {
return {
static: process.env.VUE_APP_STATIC,
// 0-向量检索 1-全文检索 2-混合检索
searchMethod: 0,
embeddingItem: {},

View File

@@ -22,7 +22,7 @@
@click="chooseFixSearch(item)"
>
<img
src="@/assets/images/konwledge/knowledge-2.png"
:src="`${static}/loadImage/knowledge-2.png`"
alt=""
style="height: 30px"
/>
@@ -162,6 +162,7 @@ export default {
},
data() {
return {
static: process.env.VUE_APP_STATIC,
form: {
// 是否启用rerank模型
rerankingEnable: true,

View File

@@ -1,10 +1,10 @@
<!-- src/views/knowledge/detail/components/DocumentDrawer.vue -->
<template>
<el-drawer
:visible.sync="visible"
:visible.sync="drawerVisible"
size="80%"
:wrapperClosable="false"
@close="$emit('update:visible', false)"
@close="drawerVisible = false"
>
<!-- drawer title -->
<template #title>
@@ -54,24 +54,22 @@
<script>
import knowledgeInfo from '@/views/track/views/knowledge-info/Index.vue'
import TextModel from './TextModel.vue'
import QAModel from './QAModel.vue'
import {
documentSourceOptions,
segmentedModeOptionsMap
} from '@/assets/js/utils/utilOptions'
import RenderFile from '@/components/RenderFile/Index.vue'
import knowledgePng_2 from '@/assets/images/konwledge/knowledge-2.png'
import MetadataOperator from '@/views/knowledge/detail/components/metaData/MetadataOperator.vue'
export default {
components: {
MetadataOperator,
TextModel,
QAModel,
RenderFile,
knowledgeInfo
},
emit: ['update:visible'],
props: {
visible: Boolean,
descriptions: {
@@ -89,12 +87,21 @@ export default {
},
data() {
return {
knowledgePng_2,
localActiveSegment: this.activeSegment,
documentSourceOptions,
metadataDialogVisible: false
}
},
computed: {
drawerVisible: {
get() {
return this.visible
},
set(value) {
this.$emit('update:visible', value)
}
}
},
watch: {
activeSegment(newVal) {
this.localActiveSegment = newVal

View File

@@ -19,35 +19,53 @@
</div>
<div
class="actions ml10 flex align-items-c mr10"
style="gap: 10px"
class="actions flex align-items-c mr10 "
style="gap: 10px;margin-top: -2px"
v-if="!noEdit"
>
<!-- 删除 编辑-->
<!-- <el-icon class="el-icon-edit" @click.native.stop=""></el-icon>-->
<!-- <el-icon-->
<!-- class="el-icon-delete"-->
<!-- @click.native.stop="deleteSegment"-->
<!-- ></el-icon>-->
<!-- <el-switch-->
<!-- size="mini"-->
<!-- @click.native.stop=""-->
<!-- v-model="segment.enabled"-->
<!-- ></el-switch>-->
<el-icon
class="el-icon-edit mr10"
@click.native.stop="handleSegmentClick(index)"
></el-icon>
<el-icon
class="el-icon-delete mr10"
@click.native.stop="deleteSegment(segment, index)"
></el-icon>
<el-switch
size="mini"
@click.native.stop=""
@change="$event => changeEnable($event, segment.id)"
v-model="segment.enabled"
></el-switch>
</div>
</div>
<!-- {{ descriptions.doc_form }}-->
<!-- 普通分段-->
<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">
<p>Q {{ segment.content }}</p>
<p>A {{ segment.answer }}</p>
</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>
@@ -70,53 +88,58 @@
"
>
<div class="segment-content">
<!-- Q & A-->
<div>
<div>
<p>QUESTION</p>
<p contenteditable class="resetHtml" ref="content">
<p class="" v-if="descriptions.doc_form !== 'text_model'">
QUESTION
</p>
<p contenteditable class="resetHtml fs13" ref="content">
{{ descriptions.data[activeSegment].content }}
</p>
</div>
<div>
<p>ANSWER</p>
<p contenteditable class="resetHtml" ref="answer">
<div v-if="descriptions.doc_form !== 'text_model'">
<p class="">ANSWER</p>
<p contenteditable class="resetHtml fs13" ref="answer">
{{ descriptions.data[activeSegment].answer }}
</p>
</div>
</div>
<div
class="flex align-items-c mt20"
class="flex align-items-c mt20 fs13"
v-if="
descriptions.data[activeSegment].keywords &&
descriptions.data[activeSegment].keywords.length
"
style="flex-wrap: wrap"
>
<span>关键词 </span>
<span class="mb10">关键词 </span>
<el-tag
v-for="(item, index) in descriptions.data[activeSegment].keywords"
:key="index"
class="mr10"
class="mr10 mb10"
size="medium"
type="info"
:closable="!noEdit"
@close="tagClose(item)"
:closable="
descriptions.data[activeSegment].keywords.length > 1 && !noEdit
"
@close="tagClose(item, index)"
>
{{ item }}
</el-tag>
<el-input
class="input-new-tag"
class="input-new-tag mb5"
v-if="createdTag"
v-model="inputValue"
ref="saveTagInput"
size="small"
size="mini"
@keyup.enter.native="handleInputConfirm"
/>
<el-button
v-if="!noEdit && !createdTag"
size="medium"
@click="showInput"
class="fs12"
class="fs12 mb10"
style="padding: 7px;border-radius: 4px;font-size: 12px"
>添加标签</el-button
>
@@ -133,7 +156,7 @@
</div>
</template>
<script>
import { segmentUpdate } from '@/api/generatedApi'
import { segmentUpdate, segmentDelete } from '@/api/generatedApi'
export default {
name: 'QAModel',
@@ -152,6 +175,7 @@ export default {
default: () => ({})
}
},
watch: {},
data() {
return {
createdTag: false,
@@ -160,10 +184,23 @@ export default {
dialogVisible: false
}
},
inject: ['changeEnable'],
mounted() {},
methods: {
deleteSegment() {
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',
'提示'
@@ -173,7 +210,7 @@ export default {
let params = {
keywords: this.descriptions.data[this.activeSegment].keywords,
content: this.$refs.content.innerHTML,
answer: this.$refs.answer.innerHTML
answer: this.$refs.answer ? this.$refs.answer.innerHTML : undefined
}
this.saveSegment(params, () => {
this.$message.success('保存成功')
@@ -183,20 +220,15 @@ export default {
showInput() {
this.createdTag = true
},
tagClose() {
this.descriptions.data[this.activeSegment].keywords.splice(
this.descriptions.data[this.activeSegment].keywords.indexOf(
this.inputValue
),
1
)
tagClose(item, index) {
this.descriptions.data[this.activeSegment].keywords.splice(index, 1)
// console.log(this.descriptions.data[this.activeSegment].keywords)
let params = {
keywords: this.descriptions.data[this.activeSegment].keywords,
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)
},
handleInputConfirm() {
@@ -206,7 +238,7 @@ export default {
this.inputValue
],
content: this.$refs.content.innerHTML,
answer: this.$refs.answer.innerHTML
answer: this.$refs.answer ? this.$refs.answer.innerHTML : undefined
}
this.saveSegment(params)
},
@@ -252,6 +284,7 @@ export default {
width: 100%;
//height: 400px;
overflow-y: auto;
overflow-x: hidden;
}
.context {
color: #3a3f4f;
@@ -265,6 +298,21 @@ export default {
border-radius: 2px;
transition: background-color 0.3s;
border-bottom: 1px solid #f3f5f7;
position: relative;
.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;
}

View File

@@ -1,317 +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" @click.native.stop=""></el-icon>-->
<!-- <el-icon-->
<!-- class="el-icon-delete"-->
<!-- @click.native.stop="deleteSegment"-->
<!-- ></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" 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="index"
class="mr10 ellipsis"
size="medium"
:closable="!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 { 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: []
}
},
methods: {
deleteSegment() {
this.$messageBox(
() => {},
'是否删除当前分段,删除后不可恢复',
'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(
this.descriptions.data[this.activeSegment].keywords.indexOf(
this.inputValue
),
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].content =
// res.content.content.content
// 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;
//margin-bottom: 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

@@ -0,0 +1,216 @@
<template>
<div class="add-segment-container">
<div class="add-segment-content">
<div class="add-segment-text fs13">
<!-- Q&A-->
<div v-if="descriptions.doc_form === 'qa_model'">
<div class="mv10">Question</div>
<el-input
type="textarea"
class="text-input-q-a"
v-model="question"
placeholder="请填写分段内容"
></el-input>
<div class="mv10">Answer</div>
<el-input
type="textarea"
class="text-input-q-a"
v-model="answer"
placeholder="请填写分段内容"
></el-input>
</div>
<!-- model-->
<div v-else>
<el-input
type="textarea"
class="text-input"
v-model="question"
placeholder="请填写分段内容"
></el-input>
</div>
</div>
</div>
<div class="add-segment-bottom ">
<div class=" mt5 fs13 mb5" style="flex:none">关键词</div>
<div
class="add-segment-tags mb10 fs14 flex align-items-s justify-content-s "
>
<div
class="flex align-items-s mb10"
style="max-height: 80px;overflow: auto;gap: 10px;flex-wrap: wrap;align-items: self-start"
>
<el-tag
v-for="(item, index) in keywords"
size="medium"
:closable="keywords.length > 1"
type="info"
:key="index"
@close="keywords.splice(index, 1)"
>{{ item }}</el-tag
>
<el-input
class="input-new-tag"
v-if="createdTag"
v-model="inputValue"
ref="saveTagInput"
size="small"
@keyup.enter.native="pushKeyWords"
/>
<el-button
v-if="!createdTag"
size="small"
@click="showInput"
class="fs12"
style="padding: 7px;border-radius: 4px;font-size: 12px"
>添加标签</el-button
>
</div>
</div>
<div class="flex align-items-c justify-content-b">
<el-checkbox v-model="continueAdd">连续新增</el-checkbox>
<div>
<el-button size="medium" @click="close">取消</el-button>
<el-button
class="line-button"
size="medium"
@click="handleInputConfirm"
>保存</el-button
>
</div>
</div>
</div>
</div>
</template>
<script>
import { segmentCreate } from '@/api/generatedApi'
export default {
name: 'addSegment',
data() {
return {
continueAdd: false,
question: '',
answer: '',
createdTag: false,
inputValue: '',
keywords: []
}
},
props: {
descriptions: {
type: Object,
default: () => ({ data: [] })
},
documentId: {
type: String,
default: () => ''
}
},
methods: {
pushKeyWords() {
this.keywords.push(this.inputValue)
this.createdTag = false
},
showInput() {
this.createdTag = true
},
handleInputConfirm() {
let params = {
documentId: this.documentId,
segment: {
keywords: this.keywords,
content: this.question,
answer: this.answer
}
}
segmentCreate(params).then(res => {
if (res) {
this.$message.success('新增分段成功')
this.descriptions.data.push({
...res.content.content
})
if (!this.continueAdd) {
this.close()
}
}
})
// this.saveSegment(params)
},
close() {
this.$emit('close', true)
},
addSegment() {
this.$emit('addSegment', this.text)
this.text = ''
}
}
}
</script>
<style scoped lang="scss">
::v-deep .text-input {
height: 100%;
.el-textarea__inner {
padding: 5px 8px;
height: calc(100vh - 310px);
//border: unset;
//resize: unset;
//height: 100%;
}
}
::v-deep .text-input-q-a {
height: 100%;
.el-textarea__inner {
padding: 5px 8px;
height: calc(100vh - 700px);
//border: unset;
//resize: unset;
//height: 100%;
}
}
.add-segment-container {
height: 100%;
overflow-y: auto;
overflow-x: hidden;
&::-webkit-scrollbar {
width: 0;
}
& .add-segment-content {
//background: rebeccapurple;
//overflow-y: hidden;
overflow-x: hidden;
& .add-segment-text {
height: calc(100vh - 310px);
//height: 100%;
//height: 999px;
//background: blue;
}
}
& .add-segment-bottom {
width: 100%;
padding: 10px 0 0 0;
position: sticky;
background: #fff;
bottom: 0;
& .add-segment-tags {
height: 100px;
//background: red;
}
}
}
.input-new-tag {
width: 100px;
}
</style>

View File

@@ -0,0 +1,160 @@
<template>
<div class="container batchAdd-container">
<!-- 文件上传区域 -->
<div
@click.stop="createFile"
@dragover.prevent="handleDragOver"
@dragleave.prevent="handleDragLeave"
@drop.prevent="handleDrop"
class="upload-container"
:class="{ 'drag-over': isDragOver }"
>
<el-empty v-if="fileList.length === 0">
<template #image>
<img :src="uploadPng" alt="" style="width: 50px;height: 50px;" />
</template>
<template #description>
<div class="flex flex-direction-c">
<span class="upload-tip">点击或将文件拖拽到这里上传</span>
</div>
</template>
</el-empty>
<div v-else @click.stop="">
<div
class="file-preview-container align-items-c justify-content-b mh20"
v-for="item in fileList"
>
<div class="flex align-items-c fs12">
<img
:src="uploadPng"
alt="CSV Icon"
style="width: 25px;height: 30px"
/>
<span class="file-name fs12 ml20 fw500">{{ item.name }}</span>
</div>
<div>
<el-button type="primary" size="medium" @click.stop="removeFile(0)"
>移除</el-button
>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import uploadPng from '@/assets/images/konwledge/upload.png'
export default {
name: 'batchAddSegment',
data() {
return {
uploadPng,
fileList: [], // 文件列表
isDragOver: false
}
},
watch: {
fileList: {
handler(newValue, oldValue) {
if (newValue.length > 0) {
this.$emit('getFileList', newValue)
} else {
this.$emit('getFileList', [])
}
}
}
},
methods: {
// 生成filed
createFile() {
let input = document.createElement('input')
input.type = 'file'
input.accept = '.xlsx,.doc,.docx,.pdf,.txt'
input.onchange = e => {
const newFiles = Array.from(e.target.files)
if (this.fileList.length + newFiles.length > 1) {
this.$message.error('最多只能上传1个文件')
return
}
this.fileList = [...this.fileList, ...newFiles]
}
input.click()
},
// 新增:移除文件
removeFile(index) {
this.fileList.splice(index, 1)
},
handleDragOver() {
this.isDragOver = true
},
handleDragLeave() {
this.isDragOver = false
},
handleDrop(event) {
this.isDragOver = false
const files = event.dataTransfer.files
if (this.fileList.length + files.length > 1) {
this.$message.error('最多只能上传1个文件')
return
}
this.fileList = [...this.fileList, ...Array.from(files)]
}
}
}
</script>
<style scoped lang="scss">
.file-preview-container {
display: flex;
align-items: center;
//background-color: #f5f5f5; // 灰色背景
padding: 15px 10px;
border-radius: 4px;
}
.file-name {
margin-left: 10px;
color: #333;
}
.upload-container {
border-radius: 6px;
border-style: dashed;
border-width: 1px;
border-color: #d9d9d9;
text-align: center;
&:hover {
border-color: var(--color-primary);
}
&.drag-over {
border-color: var(--color-primary);
background-color: #ecf5ff;
}
}
.upload-tip {
font-family: PingFangSC, PingFang SC;
font-weight: 500;
font-size: 14px;
color: #5f5e68;
font-style: normal;
}
.upload-tip-field {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 13px;
color: #b1b5c7;
font-style: normal;
}
</style>

View File

@@ -54,10 +54,10 @@
>
<div class="metadata-form">
<div class="form-item">
<label class="form-label">元数据类型</label>
<label class="form-label">元数据</label>
<el-select
v-model="dialog.metadataId"
placeholder="请选择元数据类型"
placeholder="请选择元数据"
filterable
class="full-width"
size="medium"
@@ -143,10 +143,10 @@
>
<div class="metadata-form">
<div class="form-item">
<label class="form-label">元数据类型</label>
<label class="form-label">元数据</label>
<el-input
v-model="editDialog.metadataKey"
placeholder="元数据类型"
placeholder="元数据"
class="full-width"
size="medium"
disabled
@@ -210,8 +210,6 @@ import {
updateMetaDataDoc
} from '@/api/generatedApi'
import MeteData from '@/views/knowledge/detail/components/metaData/Index.vue'
import { data } from 'autoprefixer'
export default {
name: 'MetadataOperator',
components: { MeteData },
@@ -239,7 +237,6 @@ export default {
},
created() {},
methods: {
data,
init(id) {
this.documentId = id
this.getDocList()
@@ -314,7 +311,7 @@ export default {
// 这里可以添加保存逻辑
this.dialog.visible = false
} else {
this.$message.warning('请选择元数据类型并输入值')
this.$message.warning('请选择元数据并输入值')
}
},
toMetaData() {

View File

@@ -1,49 +1,102 @@
<template>
<div id="preprocessing-container " class="mt20">
<el-form label-width="180px" :model="form" ref="processForm">
<el-form-item label="数据来源:" required prop="radio" position="top" class="el-form--label-top">
<el-radio-group v-model="form.radio" size="medium" @change="getFileType">
<el-radio-button label="1" size="medium">使用本地文件</el-radio-button>
<el-radio-button label="2" size="medium">使用通用知识文件模板</el-radio-button>
<el-form-item
label="数据来源:"
required
prop="radio"
position="top"
class="el-form--label-top"
>
<el-radio-group
v-model="form.radio"
size="medium"
@change="getFileType"
>
<el-radio-button label="1" size="medium"
>使用本地文件</el-radio-button
>
<el-radio-button label="2" size="medium"
>使用通用知识文件模板</el-radio-button
>
</el-radio-group>
<div class="mt10" v-if="form.radio === '2'">
<el-button type="primary" size="medium" class="fs14" @click="downloadTemplate">下载知识文件模板</el-button>
<el-button
type="primary"
size="medium"
class="fs14"
@click="downloadTemplate"
>下载知识文件模板</el-button
>
</div>
</el-form-item>
<!-- 文件上传-->
<el-form-item label="" required prop="file" label-width="0" class="el-form--label-top">
<el-form-item
label=""
required
prop="file"
label-width="0"
class="el-form--label-top"
>
<div
@click="createFiled"
@click.stop="createFiled"
@dragover.prevent="handleDragOver"
@dragleave.prevent="handleDragLeave"
@drop.prevent="handleDrop"
class="upload-demo"
:class="{ 'drag-over': isDragOver }"
>
<el-empty v-if="!filed">
<el-empty v-if="fileList.length === 0">
<template #image>
<img :src="uloadPng" alt="" style='width: 50px;height: 50px;'></img>
<img :src="uloadPng" alt="" style="width: 50px;height: 50px;" />
</template>
<template #description>
<div class='flex flex-direction-c'>
<span class='upload-tip'>点击或将文件拖拽到这里上传</span>
<span class='upload-tip-field'>支持扩展名.xlsx.doc.pdf.txt.docx</span>
<div class="flex flex-direction-c">
<span class="upload-tip">点击或将文件拖拽到这里上传</span>
<span class="upload-tip-field"
>支持扩展名.xlsx.doc.pdf.txt.docx</span
>
</div>
</template>
</el-empty>
<div v-else>
<el-result
v-else
v-if="fileList.length === 1"
icon="success"
title="文件上传成功"
:sub-title="`已上传文件:${filed.name}`"
:sub-title="`已上传文件:${fileList[0].name}`"
style="height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center;"
>
<template slot="extra">
<el-button type="primary" size="medium" @click.stop="createFiled">重新上传</el-button>
<el-button
type="primary"
size="medium"
@click.stop="removeFile(0)"
>移除</el-button
>
</template>
</el-result>
<el-result v-else class="more-files">
<template slot="icon">
<div class="text-left">
<el-button class="line-button" size="medium">
文件上传</el-button
>
</div>
</template>
<template slot="extra" style="width: 100%;">
<r-table
:data="fileList"
:columns="columns"
style="width: 100%;"
@removeFiled="removeFiled"
:deletion="false"
></r-table>
</template>
</el-result>
</div>
</div>
</el-form-item>
@@ -52,21 +105,40 @@
<el-form-item label="是否进行预处理:" required prop="beMinerU">
<template slot="label">
是否进行预处理
<el-tooltip class="item" effect="dark" content="通过整合最先进的文档解析模型来提高内容提取质量" placement="top">
<el-tooltip
class="item"
effect="dark"
content="通过整合最先进的文档解析模型来提高内容提取质量"
placement="top"
>
<i class="el-icon-info ml5" style="color: #909399;"></i>
</el-tooltip>
</template>
<el-switch v-model="form.beMinerU" size="medium">
<el-switch
v-model="form.beMinerU"
size="medium"
:disabled="fileList.length > 1"
>
<!-- <el-radio :label="true" size="medium"></el-radio>-->
<!-- <el-radio :label="false" size="medium"></el-radio>-->
</el-switch>
</el-form-item>
<el-form-item label="是否ocr协助处理" v-if="form.beMinerU" required prop="beOcr">
<el-form-item
label="是否ocr协助处理"
v-if="form.beMinerU"
required
prop="beOcr"
>
<template slot="label">
是否ocr协助处理
<el-tooltip class="item" effect="dark" content="能更好的协助处理图片、表格类数据" placement="top">
<el-tooltip
class="item"
effect="dark"
content="能更好的协助处理图片、表格类数据"
placement="top"
>
<i class="el-icon-info ml5" style="color: #909399;"></i>
</el-tooltip>
@@ -87,16 +159,18 @@
<!-- </el-drawer>-->
</div>
</template>
<script>
import { uploadFileByCustom, uploadFileByTemplate } from '@/api/generatedApi'
import { downloadKnowledgeTemplate } from '@/api/knowledge/task-page'
import uloadPng from '@/assets/images/konwledge/upload.png'
export default {
name: 'preprocessing',
data() {
return {
uloadPng,
filed: null,
fileList: [], // 文件列表
uploadLoading: false,
headers: {},
fieldList: [],
@@ -106,31 +180,84 @@ export default {
beOcr: false,
datasetId: this.$route.query.datasetId
},
previewDialogVisible: false, // 添加对话框显示控制变量
previewDialogVisible: false,
documentId: '',
isDragOver: false
}
},
props: {},
watch: {
'form.beMinerU': {
handler(value) {
// 如果不进行预处理不进行ocr
if (!value) {
this.form.beOcr = false
fileList: {
handler(val) {
if (val.length > 1) {
this.form.beMinerU = false
}
},
deep: true
}
},
components: {},
filters: {},
computed: {
columns: vm => {
return [
{
type: 'index',
title: '序号'
},
{
title: '文件名称',
prop: 'name'
},
{
title: '文件大小',
prop: 'size',
render: (h, params) => {
return h('span', vm.formatFileSize(params.row.size))
}
},
{
title: '文件类型',
prop: 'type',
render: (h, params) => {
// 根据文件类型展示 是什么类型文件
switch (params.row.type) {
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
return h('span', 'Excel')
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
return h('span', 'Word')
case 'application/pdf':
return h('span', 'PDF')
case 'text/plain':
return h('span', 'TXT')
}
return h('span', params.row.type)
}
},
{
title: '操作',
selfBtn: [
{
name: '移除',
size: 'mini',
type: 'primary',
method: 'removeFiled',
class: 'tableBtn'
}
]
}
]
}
},
methods: {
removeFiled(item) {
this.removeFile(item.index)
},
formatFileSize(size) {
return (size / 1024 / 1024).toFixed(2) + 'MB'
},
getFileType() {
this.$emit('getFileType', this.form.radio)
},
downloadTemplate() {
console.log(`下载模板的链接是:${downloadKnowledgeTemplate().url}`)
window.open(downloadKnowledgeTemplate().url, '_blank')
},
@@ -138,18 +265,40 @@ export default {
createFiled() {
let input = document.createElement('input')
input.type = 'file'
input.multiple = true // 支持多文件选择
// 对文件的类型做出限制
input.accept = '.xlsx,.doc,.docx,.pdf,.txt'
input.onchange = e => {
this.filed = e.target.files[0]
const newFiles = Array.from(e.target.files)
if (this.fileList.length + newFiles.length > 10) {
this.$message.error('最多只能上传10个文件')
return
}
// 校验 最新上传的文件是否之前上传过
for (let i = 0; i < newFiles.length; i++) {
for (let j = 0; j < this.fileList.length; j++) {
if (newFiles[i].name === this.fileList[j].name) {
this.$message.error('请勿重复上传文件,请检查文件')
return
}
}
}
this.fileList = [...this.fileList, ...newFiles] // 将新选择的文件添加到文件列表
}
input.click()
},
// 新增:移除文件
removeFile(index) {
this.fileList.splice(index, 1)
},
uploadFiled(back) {
if (!this.filed) {
if (this.fileList.length === 0) {
this.$message({
type: 'error',
message: '请上传文件'
@@ -159,9 +308,12 @@ export default {
// 0否 1是
let formData = new FormData()
formData.append('file', this.filed)
this.fileList.forEach(file => {
formData.append('file', file)
})
formData.append('datasetId', this.form.datasetId)
let api = this.form.radio === '1' ? uploadFileByCustom : uploadFileByTemplate
let api =
this.form.radio === '1' ? uploadFileByCustom : uploadFileByTemplate
if (this.form.radio === '1') {
formData.append('useMinerU', this.form.beMinerU ? '1' : '0')
formData.append('useOcr', this.form.beOcr ? '1' : '0')
@@ -183,10 +335,9 @@ export default {
}
}
}
})
},
handleDragOver() {
this.isDragOver = true
},
@@ -198,8 +349,13 @@ export default {
handleDrop(event) {
this.isDragOver = false
const files = event.dataTransfer.files
this.filed = files[0]
if (this.fileList.length + files.length > 10) {
this.$message.error('最多只能上传10个文件')
return
}
this.fileList = [...this.fileList, ...Array.from(files)] // 将拖拽的文件添加到文件列表
},
handleUploadSuccess(response, file) {
this.fieldList = [file]
this.uploadLoading = false
@@ -236,12 +392,17 @@ export default {
// 返回 true 继续上传,返回 false 停止上传
return true
}
},
created() {},
mounted() {},
computed: {}
}
}
</script>
<style>
.more-files > .el-result__extra,
.more-files > .el-result__icon {
width: 100%;
}
</style>
<style scoped lang="scss">
@import '@/assets/sass/renderSass/theme.scss';
.flex-direction-c {
@@ -252,14 +413,14 @@ export default {
font-family: PingFangSC, PingFang SC;
font-weight: 500;
font-size: 14px;
color: #5F5E68;
color: #5f5e68;
font-style: normal;
}
.upload-tip-field {
font-family: PingFangSC, PingFang SC;
font-weight: 400;
font-size: 13px;
color: #B1B5C7;
color: #b1b5c7;
font-style: normal;
}
.upload-demo {

View File

@@ -283,7 +283,6 @@ export default {
},
async getRuleList() {
let res = await getRulesList({ ruleType: 1 })
console.log(res)
this.ruleList = res.content.content
},
// 获取详情
@@ -311,7 +310,6 @@ export default {
})
},
queryExistingRules(val) {
console.log(val)
if (val) {
this.getDetail(val)
}

View File

@@ -63,6 +63,7 @@
<!-- 添加预览组件 -->
<split-preview
:documentList="documentList"
:documentId="documentId"
:visible.sync="previewVisible"
:preview-data="previewData"
@@ -71,6 +72,7 @@
@confirm="handlePreviewConfirm"
@handleReUpload="handleReUpload"
@handleClose="handleClose"
@changePage="changePage"
/>
</div>
</template>
@@ -88,6 +90,7 @@ export default {
},
data() {
return {
splitIndex: 0,
dialogVisible: false,
activeIndex: 0,
previewVisible: false, // 控制预览组件显示
@@ -112,6 +115,10 @@ export default {
}
},
props: {
documentList: {
type: Array,
default: () => []
},
documentId: {
type: String,
default: ''
@@ -120,6 +127,10 @@ export default {
watch: {},
filters: {},
methods: {
async changePage(index) {
this.previewData = await this.getPreviewOperation(this.documentId, index)
},
handleClick(index) {
this.activeIndex = index
if (index === 1) {
@@ -131,10 +142,15 @@ export default {
// 构建API请求参数
buildExecSplitParams(documentId) {
const params = {
let params = {
documentId: documentId,
beAuto: this.activeIndex === 0
}
let splitDocId = documentId.split(',')
if (splitDocId.length > 1) {
params.documentId = undefined
params.documentIds = splitDocId
}
if (!params.beAuto && this.$refs.customTable.ruleId) {
params.rulesId = this.$refs.customTable.ruleId
@@ -161,7 +177,11 @@ export default {
},
// 获取拆分预览
async getPreviewOperation(documentId) {
async getPreviewOperation(documentId, index) {
let splitDocId = documentId.split(',')
if (splitDocId.length > 1) {
documentId = splitDocId[index ? index : 0]
}
const res = await splitResultPreview({ documentId })
if (res.content.result !== '0') {
const errorMsg =

View File

@@ -6,6 +6,26 @@
width="60%"
:before-close="handleClose"
>
<template slot="title">
<div class="flex align-items-c" style="gap:10px">
<span>拆分结果预览</span>
<el-dropdown @command="command" trigger="click" size="medium">
<span class="el-dropdown-link">
{{ documentList[activeIndex].name
}}<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
v-for="(item, index) in documentList"
:command="index"
:key="item.id"
>{{ item.name }}</el-dropdown-item
>
</el-dropdown-menu>
</el-dropdown>
</div>
</template>
<div v-loading="loading" class="preview-content">
<template v-if="!loading && previewData">
<el-tree
@@ -23,7 +43,13 @@
</div>
<span slot="footer" class="dialog-footer">
<el-button size="medium" @click="handleReUpload">重新上传</el-button>
<el-button size="medium" type="primary" @click="handleConfirm"
<!-- <el-button size="medium" @click="upFiled">前一个</el-button>-->
<!-- <el-button size="medium" @click="downFiled">后一个</el-button>-->
<el-button
v-if="documentList.length === 1"
size="medium"
type="primary"
@click="handleConfirm"
>下一步</el-button
>
<el-button
@@ -44,6 +70,10 @@ import { embedding } from '@/api/generatedApi'
export default {
name: 'SplitPreview',
props: {
documentList: {
type: Array,
default: () => []
},
documentId: {
type: String,
default: ''
@@ -66,6 +96,7 @@ export default {
},
data() {
return {
activeIndex: 0,
defaultProps: {
children: 'children',
label: 'paragraphTitle'
@@ -73,6 +104,19 @@ export default {
}
},
methods: {
upFiled() {
this.$emit('changePage', 'up')
},
downFiled() {
this.$emit('changePage', 'down')
},
command(index) {
if (index === this.activeIndex) {
return false
}
this.activeIndex = index
this.$emit('changePage', this.activeIndex)
},
emitKnowledgeDataset() {
this.$router.push({
path: '/knowledge/reviewKnowledge',
@@ -105,7 +149,8 @@ export default {
handleConfirm() {
this.$emit('confirm')
}
}
},
watch: {}
}
</script>

View File

@@ -76,7 +76,7 @@
:key="index"
>
<div class="section-title">
拆分规则 {{ index + 1 }}
题词规则 {{ index + 1 }}
<div>
<el-button
v-if="!isPreview && form.ruleList.length > 1"

View File

@@ -67,6 +67,7 @@
:extract-results="extractResults"
:is-auto-extract="activeIndex === 0"
:loading="previewLoading"
:documentList="documentList"
@confirm="handlePreviewConfirm"
/>
</div>
@@ -113,7 +114,12 @@ export default {
documentId: ''
}
},
props: {},
props: {
documentList: {
type: Array,
default: () => []
}
},
watch: {},
filters: {},
methods: {
@@ -128,10 +134,15 @@ export default {
// 构建执行题词的参数
buildExecExtractParams(documentId) {
const params = {
let params = {
documentId: documentId,
beAuto: this.activeIndex === 0
}
let splitDocIds = documentId.split(',')
if (splitDocIds.length > 1) {
params.documentId = undefined
params.documentIds = splitDocIds
}
if (!params.beAuto && this.$refs.customTable.ruleId) {
params.rulesId = this.$refs.customTable.ruleId

View File

@@ -51,6 +51,7 @@
class="mt10"
v-else
:documentId="documentId"
:documentList="this.documentList"
@saveMarkDown="saveMarkDown"
></r-miner-u>
</div>
@@ -58,11 +59,16 @@
<step-split-config
ref="splitConfig"
v-if="active === 1"
:documentList="this.documentList"
@previewConfirmed="handlePreviewConfirm"
@handleReUpload="handleReUpload"
:documentId="documentId"
></step-split-config>
<step-words ref="words" v-if="active === 2"></step-words>
<step-words
ref="words"
v-if="active === 2"
:documentList="this.documentList"
></step-words>
</transition>
</div>
</div>
@@ -120,7 +126,7 @@ import StepPreprocessing from './components/preprocessing.vue'
import SplitConfig from '@/views/knowledge/detail/components/split/Index.vue'
import Words from '@/views/knowledge/detail/components/words/Index.vue'
import magic from '@/assets/images/konwledge/magic.png'
import { directEmbedding } from '@/api/generatedApi'
import { datasetDocumentEx, directEmbedding } from '@/api/generatedApi'
// import StepC
export default {
name: 'create',
@@ -130,7 +136,8 @@ export default {
visible: false,
active: 0,
documentId: '1365038001244180480',
isMd: false
isMd: false,
documentList: []
}
},
props: {},
@@ -185,9 +192,32 @@ export default {
}
})
},
getDocumentId(id) {
async getDocumentId(id) {
this.documentId = id
let splitDocIds = this.documentId.split(',')
for (let i = 0; i < splitDocIds.length; i++) {
let content = await this.getFileDetail(splitDocIds[i])
this.documentList.push({
name: content.knowledgeName,
id: content.id
})
}
},
// 获取文件详情 主要是名字与ID
async getFileDetail(documentId) {
const res = await datasetDocumentEx({ documentId })
if (res.content.result !== '0') {
const errorMsg =
'获取文件详情失败: ' + (res.content.resultMessage || '未知错误')
this.$message.error(errorMsg)
throw new Error(errorMsg)
}
return res.content.content
},
async nextStep() {
if (this.active === 0) {
this.$refs.stepPreProcessing.uploadFiled()
@@ -211,13 +241,17 @@ export default {
} else {
this.active = 0
}
this.documentList = []
}
},
created() {},
mounted() {
async mounted() {
let { documentId, datasetId, activeLevel, isMd } = this.$route.query
if (documentId) {
// 继续处理时 调用接口查询文件信息
this.documentId = documentId
await this.getDocumentId(documentId)
}
if (activeLevel !== undefined) {
this.active = Number(activeLevel)

View File

@@ -1,7 +1,7 @@
<template>
<div class=" render-container">
<div class="clearfix flex align-items-c justify-content-b ">
<div class=" flex align-items-c j">
<div class=" flex align-items-c justify-content-b" style="flex:1">
<div class="hover-back mr5">
<el-icon
class="el-icon-arrow-left"
@@ -10,11 +10,8 @@
></el-icon>
</div>
<img
:src="knowledgeImage ? knowledgeImage : knowledgePng_1"
class="header-icon"
/>
<div class="ml20" style="flex:1">
<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 : '' }}
@@ -32,32 +29,48 @@
v-if="!editKnowledge"
/>
<div v-else>
<el-button
type="primary"
size="medium"
class="render-button"
@click="saveKnowledgeName"
<el-button type="primary" size="medium" @click="saveKnowledgeName"
>保存</el-button
>
<el-button
size="medium"
class="render-button"
@click="cancelKnowledgeName"
<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>
<p class="mt10 fs14" style="line-height: 20px">
<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="flex:1" class="text-right">
<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"
@@ -252,7 +265,9 @@ import {
datasetQuerySegments,
datasetsExPages,
datasetUpdate,
getDatasetById
downloadOriginalFile,
getDatasetById,
relatedApps
} from '@/api/generatedApi'
import { getUserList } from '@/api/generatedApi/system'
import {
@@ -261,7 +276,6 @@ import {
} from '@/assets/js/utils/utilOptions'
import DocumentDrawer from './components/documentDetail/DocumentDrawer.vue'
import knowledgeForm from '@/views/knowledge/detail/components/knowledgeForm.vue'
import knowledgePng_1 from '@/assets/images/konwledge/knowledge-1.png'
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'
@@ -269,8 +283,16 @@ import SearchSetting from '@/views/knowledge/detail/components/SearchSetting/Ind
import { displayStatus } from '@/assets/js/utils/utilOptions'
export default {
name: 'index',
// 父子组件共享
provide() {
return {
viewDocumentDetail: row => this.viewDocumentDetail(row)
}
},
data() {
return {
relatedAppList: [],
displayStatus,
hitTestConfig: {
title: '命中测试',
@@ -284,7 +306,6 @@ export default {
title: '标注元数据',
visible: false
},
knowledgePng_1,
activeName: -1,
drawer: false,
drawerForm: false,
@@ -377,13 +398,6 @@ export default {
// 跳转到知识库编辑
jumpEditKnowledge() {
this.drawerForm = true
// let { datasetId } = this.$route.query
// this.$router.push({
// path: '/knowledge/knowledge-create',
// query: {
// datasetId: datasetId
// }
// })
},
/**
* 检索设置
@@ -456,24 +470,16 @@ export default {
},
// 查看文档详情
viewDocumentDetail(row) {
// 调用查询分段信息接口
datasetQuerySegments({ documentId: row.id }).then(res => {
if (res) {
this.descriptions = {
...row,
dataset: {
knowledgeName: this.knowledgeName,
segmentedMode: this.segmentedMode,
knowledgeImage: this.knowledgeImage
},
...row,
...res.content.content
}
}
this.drawer = true
// 调用datasetDocumentEx接口获取分词规则和词频规则
this.getDocumentExInfo(row.id)
}
})
},
jumpToUpload(params) {
@@ -531,6 +537,11 @@ export default {
console.error('获取文档详情失败', err)
})
},
// 下载原文件
handleDownload(row) {
let documentId = row.id
window.open(downloadOriginalFile(documentId))
},
// 提取规则文本处理可能是JSON字符串的情况
extractRuleText(rule) {
@@ -584,6 +595,17 @@ export default {
},
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: {
@@ -611,14 +633,16 @@ export default {
this.getKnowledgeFiledList()
// 获取用户下拉列表
this.getUserData()
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)
}
// 获取关联智能体
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() {
@@ -646,17 +670,28 @@ export default {
class: 'flex align-items-c'
},
[
h('svg-icon', {
h(
'span',
{
props: {
iconClass: params.row.useMineru === 1 ? 'miner' : 'none'
// iconClass: params.row.useMineru === 1 ? 'miner' : 'none'
},
class: 'mr5',
style: {
// display: params.row.useMineru === 1 ? '' : 'none',
width: '15px',
fontSize: '15px'
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)
]
)
@@ -762,6 +797,22 @@ export default {
}
// '标注元数据'
),
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',
@@ -1003,7 +1054,7 @@ export default {
line-height: 17px;
text-align: left;
font-style: normal;
margin-left: 20px;
margin-left: 10px;
padding: 2px 5px;
}
@@ -1044,4 +1095,13 @@ export default {
border-radius: 5px;
}
}
.knowledgeDesc {
display: -webkit-box; // 支持多行省略
-webkit-line-clamp: 1; // 限制为两行
-webkit-box-orient: vertical; // 垂直方向排列
overflow: hidden; // 隐藏超出部分
text-overflow: ellipsis; // 显示省略号
max-width: 80%; // 限制宽度
}
</style>

View File

@@ -68,7 +68,7 @@
placement="top"
width="400"
trigger="hover"
content="设置分段之间的重叠长度可以保留分段之间的语义关系提升召回效果。建议设置为最大分段长度的1096-25%。"
content="设置分段之间的重叠长度可以保留分段之间的语义关系提升召回效果。建议设置为最大分段长度的10%-25%。"
>
<el-icon
class="el-icon-question"
@@ -141,9 +141,24 @@
style="width: 49.5%;height: calc(100vh - 110px)"
class="el-card review"
>
<div class="title">
{{ documentDetail.knowledgeName }}
<div class="title flex align-items-c" style="gap: 10px">
<el-dropdown @command="command" trigger="click" size="medium">
<span class="el-dropdown-link">
{{
documentDetail.length > 0
? documentDetail[activeIndex].name
: ''
}}<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
v-for="(item, index) in documentDetail"
:command="index"
:key="item.id"
>{{ item.name }}</el-dropdown-item
>
</el-dropdown-menu>
</el-dropdown>
<span class="block" v-if="viewsDetail.total_segments">
预估块{{ viewsDetail.total_segments }}
</span>
@@ -256,7 +271,8 @@ export default {
chunkOverlap: 400
},
datasetDetail: {},
documentDetail: {},
activeIndex: 0,
documentDetail: [],
viewsDetail: {}
}
},
@@ -265,12 +281,24 @@ export default {
components: {},
filters: {},
methods: {
command(index) {
if (index === this.activeIndex) {
return
}
this.activeIndex = index
this.review_logs()
},
resetForm() {
this.form = JSON.parse(JSON.stringify(this.reset))
},
review_logs() {
let { documentId, active } = this.$route.query
let splitDocIds = documentId.split(',')
let api = segmentEstimate
switch (active) {
case '0':
@@ -287,7 +315,7 @@ export default {
break
}
api({
documentId,
documentId: splitDocIds[this.activeIndex],
segmentConfig: {
...this.form,
removeExtraSpaces: this.form.removeExtraSpaces ? 1 : 0,
@@ -299,13 +327,18 @@ export default {
},
// 获取文档详情
getDocumentDetail() {
async getDocumentDetail() {
let { documentId } = this.$route.query
datasetDocumentEx({ documentId }).then(res => {
if (res) {
this.documentDetail = res.content.content
}
let splitDocIds = documentId.split(',')
for (let i = 0; i < splitDocIds.length; i++) {
let content = await datasetDocumentEx({ documentId: splitDocIds[i] })
this.documentDetail.push({
name: content.content.content.knowledgeName,
id: content.content.content.id
})
}
},
// 获取知识库详情
@@ -320,6 +353,8 @@ export default {
// 保存 处理
saveUs() {
let { documentId, active } = this.$route.query
let splitDocIds = documentId.split(',')
let api = directEmbedding
switch (active) {
case '0':
@@ -336,14 +371,21 @@ export default {
break
}
api({
let params = {
documentId,
segmentConfig: {
...this.form,
removeExtraSpaces: this.form.removeExtraSpaces ? 1 : 0,
removeUrlsEmails: this.form.removeUrlsEmails ? 1 : 0
}
}).then(res => {
}
if (splitDocIds.length > 1) {
params.documentIds = splitDocIds
params.documentId = undefined
}
api(params).then(res => {
if (res) {
this.$router.push({
path: '/knowledge/detail',
@@ -403,7 +445,7 @@ export default {
& .review {
& .title {
height: 40px;
line-height: 40px;
//line-height: 40px;
border-bottom: 1px solid #ebeef5;
font-weight: 600;
font-size: 15px;

View File

@@ -49,7 +49,7 @@
</div>
<div class="card-body">
<div class="ledge-list mt20">
<div class="ledge-list mt10 ">
<el-card
v-for="(item, index) in datasetList"
class="item datasetList"
@@ -57,13 +57,9 @@
:key="index"
@click.native.stop="knowLedgeDetail(item)"
>
<div class="dataset-header">
<div class="dataset-header mb10">
<div class="folder-content">
<img
:src="item.image ? item.image : getKnowledgeImage(index)"
alt=""
class="folder"
/>
<img :src="item.image" alt="" class="folder" />
</div>
<div class="dataset-body" style="flex:1">
<h4 class="dataset-title">{{ item.name }}</h4>
@@ -71,10 +67,11 @@
</div>
</div>
<div class="flex align-items-c justify-content-b mh20 mv10">
<div class="flex align-items-c justify-content-b mh20 mv5">
<div class="theme-primary-desc-text-drank fs12">
{{ item.segmentedMode | filterSegmentedMode }}
</div>
<div class="opacity-button">
<el-button
class="default "
@@ -109,25 +106,11 @@
<script>
import { docManageDataset, datasetDelete } from '@/api/generatedApi/index.js'
import knowledgeForm from '@/views/knowledge/detail/components/knowledgeForm.vue'
import knowledgePng_1 from '@/assets/images/konwledge/knowledge-1.png'
import knowledgePng_2 from '@/assets/images/konwledge/knowledge-2.png'
import knowledgePng_3 from '@/assets/images/konwledge/knowledge-3.png'
import knowledgePng_4 from '@/assets/images/konwledge/knowledge-4.png'
import knowledgePng_5 from '@/assets/images/konwledge/knowledge-5.png'
import knowledgePng_6 from '@/assets/images/konwledge/knowledge-6.png'
import knowledgePng_7 from '@/assets/images/konwledge/knowledge-7.png'
export default {
name: 'index',
data() {
return {
datasetId: '',
knowledgePng_1,
knowledgePng_2,
knowledgePng_3,
knowledgePng_4,
knowledgePng_5,
knowledgePng_6,
knowledgePng_7,
datasetList: [],
nameLike: '',
drawer: false,
@@ -139,7 +122,8 @@ export default {
knowledgeForm: {
name: 'knowledgeForm',
component: knowledgeForm
}
},
relatedAppList: []
}
},
props: {},
@@ -159,6 +143,7 @@ export default {
this.datasetList = res.content.content
})
},
createdKnowLedge(item) {
this.datasetId = item ? item.id : ''
this.drawer = true
@@ -202,18 +187,6 @@ export default {
// }).then(() => {
//
// })
},
getKnowledgeImage(index) {
const images = [
knowledgePng_1,
knowledgePng_2,
knowledgePng_3,
knowledgePng_4,
knowledgePng_5,
knowledgePng_6,
knowledgePng_7
]
return images[index % images.length]
}
},
filters: {
@@ -390,10 +363,10 @@ export default {
text-align: left;
font-style: normal;
height: 35px;
height: 18px;
margin: 0;
display: -webkit-box; // 支持多行省略
-webkit-line-clamp: 2; // 限制为两行
-webkit-line-clamp: 1; // 限制为两行
-webkit-box-orient: vertical; // 垂直方向排列
overflow: hidden; // 隐藏超出部分
text-overflow: ellipsis; // 显示省略号

View File

@@ -118,6 +118,10 @@ export default {
type: String,
default: '添加用户'
},
roleListData: {
type: Array,
default: () => []
},
visible: {
type: Boolean,
default: false
@@ -145,14 +149,20 @@ export default {
email: '',
sysUserRoleDTOs: []
},
roleListData: [],
selectedRoleIds: [], // 新增用于绑定选中的角色ID数组
rules: {
realName: [
{ required: true, message: '请输入真实姓名', trigger: 'blur' }
realName: [{ required: true, message: '请输入真实姓名' }],
userName: [{ required: true, message: '请输入用户名称' }],
email: [
{ required: true, message: '请输入邮箱' },
// 邮箱格式校验 正则
{
type: 'email',
message: '请输入正确的邮箱地址'
}
],
userName: [
{ required: true, message: '请输入用户名称', trigger: 'blur' }
sysUserRoleDTOs: [
{ required: true, message: '请选择用户角色', trigger: 'blur' }
]
},
roleOptions: [
@@ -167,31 +177,18 @@ export default {
handler(val) {
if (val && Object.keys(val).length > 0) {
this.form = { ...val }
this.selectedRoleIds = this.form.sysUserRoleDTOs
this.selectedRoleIds = this.form.sysUserRoleDTOs.map(item => {
return item.roleId
})
}
},
immediate: true
}
},
created() {
this.getRoleListData()
// this.getRoleListData()
},
methods: {
// 获取角色列表
getRoleListData() {
this.loading = true
getRoleList({})
.then(response => {
this.roleListData = response.content.content || []
})
.catch(error => {
this.$message.error('获取角色列表出错', error)
})
.finally(() => {
this.loading = false
})
},
handleClose() {
this.$refs.form.resetFields()
this.$emit('update:visible', false)
@@ -205,7 +202,7 @@ export default {
this.$refs.form.validate(valid => {
if (valid) {
this.loading = true
// this.loading = true
const submitData = { ...this.form }
// 根据是否是编辑模式调用不同的API
@@ -213,24 +210,18 @@ export default {
? updateUser(submitData)
: addUser(submitData)
apiRequest
.then(() => {
apiRequest.then(() => {
this.$message.success(this.isEdit ? '修改成功' : '添加成功')
this.$emit('submit', submitData)
this.handleClose()
})
.catch(() => {
this.$message.error(this.isEdit ? '修改用户出错' : '添加用户出错')
})
.finally(() => {
this.loading = false
})
}
})
},
// 处理角色选择变化
handleRolesChange(roleIds) {
this.form.sysUserRoleDTOs = roleIds.map(id => ({ roleId: id }))
this.$refs.form.validateField('sysUserRoleDTOs')
},
clearForm() {
this.form = {}

View File

@@ -67,6 +67,7 @@
:title="dialogTitle"
:is-edit="isEdit"
:is-view="isView"
:roleListData="roleListData"
:user-data="currentUserData"
@submit="handleSubmit"
/>
@@ -90,6 +91,7 @@ import {
changeUserStatus,
resetPassword
} from '@/api/generatedApi/system'
import { getRoleList } from '@/api/system/role'
export default {
name: 'UserManage',
@@ -99,6 +101,7 @@ export default {
},
data() {
return {
roleListData: [],
loading: false,
dialogVisible: false,
resetPasswordVisible: false,
@@ -127,7 +130,35 @@ export default {
{ prop: 'realName', key: '真实姓名' },
{ prop: 'mobile', key: '手机号' },
{ prop: 'email', key: '邮箱' },
{ prop: 'userRoles', key: '用户角色' },
{
prop: 'userRoles',
key: '用户角色',
render: (h, p) => {
let text = []
p.row.sysUserRoleDTOs.map(item => {
this.roleListData.find(role => {
console.log(role, item)
if (role.id === item.roleId) {
text.push(role.roleName)
}
})
})
return h('div', [
text.map(item => {
return h(
'el-tag',
{
props: {
size: 'mini'
},
class: 'mr5'
},
item
)
})
])
}
},
{
prop: 'status',
key: '状态',
@@ -222,9 +253,20 @@ export default {
}
},
created() {
this.getRoleListData()
this.getUserList()
},
methods: {
// 获取角色列表
getRoleListData() {
this.loading = true
getRoleList({}).then(response => {
if (response) {
this.roleListData = response.content.content || []
}
})
},
// 获取用户列表
getUserList() {
this.loading = true

View File

@@ -5,7 +5,8 @@ import {
} from '@/api/generatedApi'
import KnowledgeInfo from '@/views/track/views/knowledge-info/Index.vue'
import { getUserList } from '@/api/generatedApi/system'
import knowledgePng_2 from '@/assets/images/konwledge/knowledge-2.png'
import knowledgePng_2 from '../../../public/loadImage/knowledge-2.png'
export default {
components: {
KnowledgeInfo

View File

@@ -1,20 +1,52 @@
<script>
import { datasetQuerySegments, getPdfUrl, queryTask } from '@/api/generatedApi'
import TextModel from '@/views/knowledge/detail/components/documentDetail/TextModel.vue'
import {
datasetQuerySegments,
importSegment,
queryTask,
segmentTemplate,
exportSegment,
switchStatus
} from '@/api/generatedApi'
import QAModel from '@/views/knowledge/detail/components/documentDetail/QAModel.vue'
import RenderFile from '@/components/RenderFile/Index.vue'
import MetadataOperator from '@/views/knowledge/detail/components/metaData/MetadataOperator.vue'
import AddSegment from '@/views/knowledge/detail/components/documentDetail/addSegment.vue'
import BatchAddSegment from '@/views/knowledge/detail/components/documentDetail/batchAddSegment.vue'
import { Form } from 'element-ui'
export default {
name: 'index',
components: { MetadataOperator, QAModel, TextModel, RenderFile },
components: {
MetadataOperator,
QAModel,
RenderFile,
AddSegment,
BatchAddSegment
},
data() {
return {
segmentPage: 1,
segmentLimit: 10,
segmentTotal: 101,
searchText: '',
keyWords: '',
batchAddSegmentDialog: false,
iframeSrc: window.location.origin,
newForm: {},
descriptions: null
descriptions: null,
addSegmentDialog: false,
batchSegmentList: []
}
},
provide() {
return {
changeEnable: this.changeEnable
}
},
inject: ['viewDocumentDetail'],
props: {
noEdit: {
type: Boolean,
@@ -75,18 +107,98 @@ export default {
}
},
methods: {
toSplit() {
this.$router.push({
path: '/knowledge/detail/create',
query: {
changeEnable(state, id) {
let params = {
documentId: this.form.id,
datasetId: this.form.datasetId,
activeLevel: 1
segmentIds: [id],
status: state ? 0 : 1 //枚举值0-启用 1-禁用
}
switchStatus(params)
},
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() {
let form = new FormData()
form.append('documentId', this.form.id)
if (this.batchSegmentList.length === 0) {
this.$message.error('请先选择文件')
return
}
this.batchSegmentList.map(item => {
form.append('file', item)
})
importSegment(form).then(res => {
if (res) {
this.$message.success('导入成功')
this.batchAddSegmentDialog = false
this.batchSegmentList = []
this._getSplitResultPreview()
}
})
},
getPdfUrl,
getFileList(list) {
this.batchSegmentList = list
},
downLoadSegmentTemplate() {
let model = null
switch (this.newForm.segmentedMode) {
case 0:
model = 'general'
break
case 1:
model = 'qa'
break
default:
model = 'general'
return ''
}
window.open(segmentTemplate(model))
},
command(type) {
switch (type) {
case 'newAdd':
// 批量新增
this.batchAddSegmentDialog = true
break
case 'exportSeg':
// 导出分段
window.open(exportSegment({ documentId: this.form.id }))
break
}
},
// 新增分段
addSegment() {
if (this.newForm.optStatus !== 4) {
return false
}
// this._getSplitResultPreview()
this.addSegmentDialog = true
},
_getTaskDetail() {
queryTask({ id: this.form.id }).then(res => {
const { content } = res.content
@@ -94,8 +206,19 @@ export default {
})
},
_getSplitResultPreview() {
datasetQuerySegments({ documentId: this.form.id }).then(res => {
if (this.descriptions && this.descriptions.data) {
this.descriptions.data = []
}
let params = {
documentId: this.form.id,
page: this.segmentPage,
pageSize: this.segmentLimit,
keyword: this.keyWords
// keyword: this.searchText
}
datasetQuerySegments(params).then(res => {
this.descriptions = res.content.content
this.segmentTotal = res.content.content.total
})
},
openMetaDrawer() {
@@ -126,8 +249,8 @@ export default {
class="upload-info-container"
:class="!fullscreen ? 'render-container' : ''"
>
<div class="form-container">
<el-descriptions class="" :column="4">
<div class="form-container ph10">
<el-descriptions class="mt10" :column="4">
<el-descriptions-item label="知识库">
{{ newForm.datasetName }}
</el-descriptions-item>
@@ -163,24 +286,92 @@ export default {
</div>
<div class="card-body">
<el-row :gutter="20">
<div class="flex align-items-c mt10" style="gap:20px">
<el-dropdown
trigger="click"
v-if="!noEdit"
split-button
size="medium"
type="primary"
@click="addSegment"
class="line-button"
:class="newForm.optStatus !== 4 ? 'is-disabled' : ''"
@command="command"
>
新增分段
<el-dropdown-menu slot="dropdown" :disabled="newForm.optStatus !== 4">
<el-dropdown-item
command="newAdd"
:disabled="newForm.optStatus !== 4"
>批量新增</el-dropdown-item
>
<el-dropdown-item
command="exportSeg"
:disabled="newForm.optStatus !== 4"
>分段导出</el-dropdown-item
>
</el-dropdown-menu>
</el-dropdown>
<el-input
style="width: 50vh;"
size="small"
placeholder="按下回车搜索分段内容"
clearable
v-model="searchText"
@input="inputSegment"
@keydown.enter.native="searchSegment"
></el-input>
</div>
<el-row :gutter="20" class="mt10">
<!-- 左侧表单内容和知识内容 -->
<el-col :span="12">
<el-col
:span="
newForm.knowledgeName && newForm.knowledgeName.indexOf('.pdf') >= 0
? 12
: 24
"
>
<!-- 知识内容 -->
<div class="content-card el-card mt20">
<div class="knowledge-content" v-if="descriptions">
<text-model
:noEdit="noEdit"
v-if="descriptions.doc_form === 'text_model'"
:descriptions="descriptions"
:parentForm="form"
/>
<div class="content-card el-card ">
<div
v-if="descriptions"
class="knowledge-content"
v-loading="!descriptions.data"
>
<q-a-model
:class="segmentTotal > 0 ? '' : ''"
:noEdit="noEdit"
v-else-if="descriptions.doc_form === 'qa_model'"
:descriptions="descriptions"
:parentForm="form"
:style="{
height: 'calc(100% - 40px)',
overflowX: 'hidden',
overflowY: '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
@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 v-else class="p20 flex align-items-c">
<div class="mr10 fs14">暂无知识内容</div>
@@ -196,8 +387,13 @@ export default {
</el-col>
<!-- 右侧原文内容 -->
<el-col :span="12">
<div class="content-card el-card full-height mt20">
<el-col
:span="12"
v-if="
newForm.knowledgeName && newForm.knowledgeName.indexOf('.pdf') >= 0
"
>
<div class="content-card el-card full-height ">
<!-- <metadata-operator-->
<!-- @openMetaDrawer="openMetaDrawer"-->
<!-- ></metadata-operator>-->
@@ -206,10 +402,66 @@ export default {
</el-col>
</el-row>
</div>
<el-drawer
title="新增分段"
:visible.sync="addSegmentDialog"
:wrapperClosable="false"
width="50%"
append-to-body
:modal="false"
>
<add-segment
:descriptions="descriptions"
:document-id="form.id"
@close="addSegmentDialog = false"
></add-segment>
</el-drawer>
<r-dialog
title="批量新增"
:visible.sync="batchAddSegmentDialog"
append-to-body
width="40%"
destroy-on-close
>
<template slot="title">
<div class="flex align-items-c">
<div>批量新增</div>
<el-button
size="mini"
type="text"
class="ml20"
icon="el-icon-download"
@click="downLoadSegmentTemplate"
>下载模板</el-button
>
</div>
</template>
<batch-add-segment
:document-id="form.id"
@close="batchAddSegmentDialog = false"
@getFileList="getFileList"
></batch-add-segment>
<div slot="footer">
<el-button @click="batchAddSegmentDialog = false" size="medium"
>取消</el-button
>
<el-button
size="medium"
type="primary"
:disabled="batchSegmentList.length < 1"
@click="importSegmentToKnowledge"
>导入</el-button
>
</div>
</r-dialog>
</div>
</template>
<style lang="scss" scoped>
@import '@/assets/sass/renderSass/theme.scss';
.form-container {
background: #fff;
padding: 5px;
@@ -218,12 +470,13 @@ export default {
}
.content-card {
margin-bottom: 20px;
//margin-bottom: 20px;
border-radius: 8px;
overflow: hidden;
.knowledge-content {
height: calc(100vh - 240px);
position: relative;
height: calc(100vh - 290px);
overflow-y: auto;
//&::-webkit-scrollbar {
// width: 4px;
@@ -232,7 +485,7 @@ export default {
}
.full-height {
height: calc(100vh - 240px);
height: calc(100vh - 290px);
padding-bottom: 20px;
}
@@ -249,4 +502,20 @@ export default {
.clearfix:after {
clear: both;
}
::v-deep .line-button {
background: $--color-primary-button-gradient;
overflow: hidden;
border-radius: 8px;
&.is-disabled {
& .el-button {
cursor: no-drop;
}
}
& .el-button-group {
& .el-button {
background: unset;
border: unset;
}
}
}
</style>