mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 01:33:05 +00:00
This commit is contained in:
parent
1dfff30daf
commit
b16353fbe8
|
|
@ -0,0 +1,15 @@
|
|||
import { Result } from '@/request/Result'
|
||||
import { get, post, del, put } from '@/request/index'
|
||||
|
||||
const prefix = '/image'
|
||||
/**
|
||||
* 上传图片
|
||||
* @param 参数 file:file
|
||||
*/
|
||||
const postImage: (data: any) => Promise<Result<any>> = (data) => {
|
||||
return post(`${prefix}`, data)
|
||||
}
|
||||
|
||||
export default {
|
||||
postImage
|
||||
}
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
import { Result } from '@/request/Result'
|
||||
import { get, post, del, put } from '@/request/index'
|
||||
import type { pageRequest } from '@/api/type/common'
|
||||
import type { Ref } from 'vue'
|
||||
const prefix = '/dataset'
|
||||
|
||||
/**
|
||||
* 段落列表
|
||||
* @param 参数 dataset_id document_id
|
||||
* page {
|
||||
"current_page": "string",
|
||||
"page_size": "string",
|
||||
}
|
||||
* param {
|
||||
"title": "string",
|
||||
"content": "string",
|
||||
}
|
||||
*/
|
||||
const getParagraph: (
|
||||
dataset_id: string,
|
||||
document_id: string,
|
||||
page: pageRequest,
|
||||
param: any,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<any>> = (dataset_id, document_id, page, param, loading) => {
|
||||
return get(
|
||||
`${prefix}/${dataset_id}/document/${document_id}/paragraph/${page.current_page}/${page.page_size}`,
|
||||
param,
|
||||
loading
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除段落
|
||||
* @param 参数 dataset_id, document_id, paragraph_id
|
||||
*/
|
||||
const delParagraph: (
|
||||
dataset_id: string,
|
||||
document_id: string,
|
||||
paragraph_id: string,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<boolean>> = (dataset_id, document_id, paragraph_id, loading) => {
|
||||
return del(
|
||||
`${prefix}/${dataset_id}/document/${document_id}/paragraph/${paragraph_id}`,
|
||||
undefined,
|
||||
{},
|
||||
loading
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除段落
|
||||
* @param 参数 dataset_id, document_id
|
||||
*/
|
||||
const delMulParagraph: (
|
||||
dataset_id: string,
|
||||
document_id: string,
|
||||
data: any,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<boolean>> = (dataset_id, document_id, data, loading) => {
|
||||
return del(
|
||||
`${prefix}/${dataset_id}/document/${document_id}/paragraph/_batch`,
|
||||
undefined,
|
||||
{ id_list: data },
|
||||
loading
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建段落
|
||||
* @param 参数
|
||||
* dataset_id, document_id
|
||||
* {
|
||||
"content": "string",
|
||||
"title": "string",
|
||||
"is_active": true,
|
||||
"problem_list": [
|
||||
{
|
||||
"id": "string",
|
||||
"content": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
const postParagraph: (
|
||||
dataset_id: string,
|
||||
document_id: string,
|
||||
data: any,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<any>> = (dataset_id, document_id, data, loading) => {
|
||||
return post(`${prefix}/${dataset_id}/document/${document_id}/paragraph`, data, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改段落
|
||||
* @param 参数
|
||||
* dataset_id, document_id, paragraph_id
|
||||
* {
|
||||
"content": "string",
|
||||
"title": "string",
|
||||
"is_active": true,
|
||||
"problem_list": [
|
||||
{
|
||||
"id": "string",
|
||||
"content": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
*/
|
||||
const putParagraph: (
|
||||
dataset_id: string,
|
||||
document_id: string,
|
||||
paragraph_id: string,
|
||||
data: any,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<any>> = (dataset_id, document_id, paragraph_id, data, loading) => {
|
||||
return put(
|
||||
`${prefix}/${dataset_id}/document/${document_id}/paragraph/${paragraph_id}`,
|
||||
data,
|
||||
undefined,
|
||||
loading
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量迁移段落
|
||||
* @param 参数 dataset_id,target_dataset_id,
|
||||
*/
|
||||
const putMigrateMulParagraph: (
|
||||
dataset_id: string,
|
||||
document_id: string,
|
||||
target_dataset_id: string,
|
||||
target_document_id: string,
|
||||
data: any,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<boolean>> = (
|
||||
dataset_id,
|
||||
document_id,
|
||||
target_dataset_id,
|
||||
target_document_id,
|
||||
data,
|
||||
loading
|
||||
) => {
|
||||
return put(
|
||||
`${prefix}/${dataset_id}/document/${document_id}/paragraph/migrate/dataset/${target_dataset_id}/document/${target_document_id}`,
|
||||
data,
|
||||
undefined,
|
||||
loading
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 问题列表
|
||||
* @param 参数 dataset_id,document_id,paragraph_id
|
||||
*/
|
||||
const getProblem: (
|
||||
dataset_id: string,
|
||||
document_id: string,
|
||||
paragraph_id: string
|
||||
) => Promise<Result<any>> = (dataset_id, document_id, paragraph_id: string) => {
|
||||
return get(`${prefix}/${dataset_id}/document/${document_id}/paragraph/${paragraph_id}/problem`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建问题
|
||||
* @param 参数
|
||||
* dataset_id, document_id, paragraph_id
|
||||
* {
|
||||
"id": "string",
|
||||
content": "string"
|
||||
}
|
||||
*/
|
||||
const postProblem: (
|
||||
dataset_id: string,
|
||||
document_id: string,
|
||||
paragraph_id: string,
|
||||
data: any,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<any>> = (dataset_id, document_id, paragraph_id, data: any, loading) => {
|
||||
return post(
|
||||
`${prefix}/${dataset_id}/document/${document_id}/paragraph/${paragraph_id}/problem`,
|
||||
data,
|
||||
{},
|
||||
loading
|
||||
)
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param dataset_id 数据集id
|
||||
* @param document_id 文档id
|
||||
* @param paragraph_id 段落id
|
||||
* @param problem_id 问题id
|
||||
* @param loading 加载器
|
||||
* @returns
|
||||
*/
|
||||
const associationProblem: (
|
||||
dataset_id: string,
|
||||
document_id: string,
|
||||
paragraph_id: string,
|
||||
problem_id: string,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<any>> = (dataset_id, document_id, paragraph_id, problem_id, loading) => {
|
||||
return put(
|
||||
`${prefix}/${dataset_id}/document/${document_id}/paragraph/${paragraph_id}/problem/${problem_id}/association`,
|
||||
{},
|
||||
{},
|
||||
loading
|
||||
)
|
||||
}
|
||||
/**
|
||||
* 解除关联问题
|
||||
* @param 参数 dataset_id, document_id, paragraph_id,problem_id
|
||||
*/
|
||||
const disassociationProblem: (
|
||||
dataset_id: string,
|
||||
document_id: string,
|
||||
paragraph_id: string,
|
||||
problem_id: string,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<boolean>> = (dataset_id, document_id, paragraph_id, problem_id, loading) => {
|
||||
return put(
|
||||
`${prefix}/${dataset_id}/document/${document_id}/paragraph/${paragraph_id}/problem/${problem_id}/un_association`,
|
||||
{},
|
||||
{},
|
||||
loading
|
||||
)
|
||||
}
|
||||
|
||||
const batchGenerateRelated: (
|
||||
dataset_id: string,
|
||||
document_id: string,
|
||||
data: any,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<boolean>> = (dataset_id, document_id, data, loading) => {
|
||||
return put(
|
||||
`${prefix}/${dataset_id}/document/${document_id}/paragraph/batch_generate_related`,
|
||||
data,
|
||||
undefined,
|
||||
loading
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
getParagraph,
|
||||
delParagraph,
|
||||
putParagraph,
|
||||
postParagraph,
|
||||
getProblem,
|
||||
postProblem,
|
||||
disassociationProblem,
|
||||
associationProblem,
|
||||
delMulParagraph,
|
||||
putMigrateMulParagraph,
|
||||
batchGenerateRelated
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
import { Result } from '@/request/Result'
|
||||
import { get, post, del, put } from '@/request/index'
|
||||
import type { Ref } from 'vue'
|
||||
import type { KeyValue } from '@/api/type/common'
|
||||
import type { pageRequest } from '@/api/type/common'
|
||||
const prefix = '/dataset'
|
||||
|
||||
/**
|
||||
* 文档分页列表
|
||||
* @param 参数 dataset_id,
|
||||
* page {
|
||||
"current_page": "string",
|
||||
"page_size": "string",
|
||||
}
|
||||
* query {
|
||||
"content": "string",
|
||||
}
|
||||
*/
|
||||
|
||||
const getProblems: (
|
||||
dataset_id: string,
|
||||
page: pageRequest,
|
||||
param: any,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<any>> = (dataset_id, page, param, loading) => {
|
||||
return get(
|
||||
`${prefix}/${dataset_id}/problem/${page.current_page}/${page.page_size}`,
|
||||
param,
|
||||
loading
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建问题
|
||||
* @param 参数 dataset_id
|
||||
* data: array[string]
|
||||
*/
|
||||
const postProblems: (
|
||||
dataset_id: string,
|
||||
data: any,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<any>> = (dataset_id, data, loading) => {
|
||||
return post(`${prefix}/${dataset_id}/problem`, data, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除问题
|
||||
* @param 参数 dataset_id, problem_id,
|
||||
*/
|
||||
const delProblems: (
|
||||
dataset_id: string,
|
||||
problem_id: string,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<boolean>> = (dataset_id, problem_id, loading) => {
|
||||
return del(`${prefix}/${dataset_id}/problem/${problem_id}`, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除问题
|
||||
* @param 参数 dataset_id,
|
||||
*/
|
||||
const delMulProblem: (
|
||||
dataset_id: string,
|
||||
data: any,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<boolean>> = (dataset_id, data, loading) => {
|
||||
return del(`${prefix}/${dataset_id}/problem/_batch`, undefined, data, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改问题
|
||||
* @param 参数
|
||||
* dataset_id, problem_id,
|
||||
* {
|
||||
"content": "string",
|
||||
}
|
||||
*/
|
||||
const putProblems: (
|
||||
dataset_id: string,
|
||||
problem_id: string,
|
||||
data: any,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<any>> = (dataset_id, problem_id, data: any, loading) => {
|
||||
return put(`${prefix}/${dataset_id}/problem/${problem_id}`, data, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 问题详情
|
||||
* @param 参数
|
||||
* dataset_id, problem_id,
|
||||
*/
|
||||
const getDetailProblems: (
|
||||
dataset_id: string,
|
||||
problem_id: string,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<any>> = (dataset_id, problem_id, loading) => {
|
||||
return get(`${prefix}/${dataset_id}/problem/${problem_id}/paragraph`, undefined, loading)
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量关联段落
|
||||
* @param 参数 dataset_id,
|
||||
* {
|
||||
"problem_id_list": "Array",
|
||||
"paragraph_list": "Array",
|
||||
}
|
||||
*/
|
||||
const postMulAssociationProblem: (
|
||||
dataset_id: string,
|
||||
data: any,
|
||||
loading?: Ref<boolean>
|
||||
) => Promise<Result<boolean>> = (dataset_id, data, loading) => {
|
||||
return post(`${prefix}/${dataset_id}/problem/_batch`, data, undefined, loading)
|
||||
}
|
||||
|
||||
export default {
|
||||
getProblems,
|
||||
postProblems,
|
||||
delProblems,
|
||||
putProblems,
|
||||
getDetailProblems,
|
||||
delMulProblem,
|
||||
postMulAssociationProblem
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
|
|
@ -54,4 +54,67 @@ export default {
|
|||
])
|
||||
},
|
||||
},
|
||||
'app-problems': {
|
||||
iconReader: () => {
|
||||
return h('i', [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
style: { height: '100%', width: '100%' },
|
||||
viewBox: '0 0 1024 1024',
|
||||
version: '1.1',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
[
|
||||
h('path', {
|
||||
d: 'M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768z m0 85.333333C252.8 981.333333 42.666667 771.2 42.666667 512S252.8 42.666667 512 42.666667s469.333333 210.133333 469.333333 469.333333-210.133333 469.333333-469.333333 469.333333z m-21.333333-298.666666h42.666666a21.333333 21.333333 0 0 1 21.333334 21.333333v42.666667a21.333333 21.333333 0 0 1-21.333334 21.333333h-42.666666a21.333333 21.333333 0 0 1-21.333334-21.333333v-42.666667a21.333333 21.333333 0 0 1 21.333334-21.333333zM343.466667 396.032c0.554667-4.778667 1.109333-8.746667 1.664-11.946667 8.32-46.293333 29.397333-80.341333 63.189333-102.144 26.453333-17.28 59.008-25.941333 97.621333-25.941333 50.730667 0 92.842667 12.288 126.378667 36.864 33.578667 24.533333 50.346667 60.928 50.346667 109.141333 0 29.568-7.253333 54.485333-21.888 74.752-8.533333 12.245333-24.917333 27.946667-49.152 47.061334l-23.893334 18.773333c-13.013333 10.24-21.632 22.186667-25.898666 35.84-1.152 3.712-2.176 10.624-3.072 20.736a21.333333 21.333333 0 0 1-21.248 19.498667h-47.786667a21.333333 21.333333 0 0 1-21.248-23.296c2.773333-29.696 5.717333-48.469333 8.832-56.362667 5.845333-14.677333 20.906667-31.573333 45.141333-50.688l24.533334-19.413333c8.106667-6.144 49.749333-35.456 49.749333-61.44 0-25.941333-4.522667-35.498667-17.578667-49.749334-13.013333-14.208-42.368-18.773333-68.864-18.773333-26.026667 0-48.256 6.869333-59.136 24.405333-5.034667 8.106667-9.173333 16.768-12.117333 25.6a89.472 89.472 0 0 0-3.114667 13.098667 21.333333 21.333333 0 0 1-21.034666 17.706667H364.672a21.333333 21.333333 0 0 1-21.205333-23.722667z',
|
||||
fill: 'currentColor'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
},
|
||||
'app-hit-test': {
|
||||
iconReader: () => {
|
||||
return h('i', [
|
||||
h(
|
||||
'svg',
|
||||
{
|
||||
style: { height: '100%', width: '100%' },
|
||||
viewBox: '0 0 20 20',
|
||||
version: '1.1',
|
||||
xmlns: 'http://www.w3.org/2000/svg'
|
||||
},
|
||||
|
||||
[
|
||||
h('path', {
|
||||
d: 'M1.6665 9.99986C1.6665 5.3975 5.39748 1.66653 9.99984 1.66653H10.8332V3.3332H9.99984C6.31795 3.3332 3.33317 6.31797 3.33317 9.99986C3.33317 13.6818 6.31795 16.6665 9.99984 16.6665C13.6817 16.6665 16.6665 13.6818 16.6665 9.99986V9.16653H18.3332V9.99986C18.3332 14.6022 14.6022 18.3332 9.99984 18.3332C5.39748 18.3332 1.6665 14.6022 1.6665 9.99986Z',
|
||||
fill: 'currentColor',
|
||||
fillRule: 'evenodd',
|
||||
clipRule: 'evenodd'
|
||||
}),
|
||||
h('path', {
|
||||
d: 'M5.4165 9.99986C5.4165 7.46854 7.46852 5.41653 9.99984 5.41653H10.8332V7.0832H9.99984C8.38899 7.0832 7.08317 8.38902 7.08317 9.99986C7.08317 11.6107 8.38899 12.9165 9.99984 12.9165C11.6107 12.9165 12.9165 11.6107 12.9165 9.99986V9.16653H14.5832V9.99986C14.5832 12.5312 12.5312 14.5832 9.99984 14.5832C7.46852 14.5832 5.4165 12.5312 5.4165 9.99986Z',
|
||||
fill: 'currentColor',
|
||||
fillRule: 'evenodd',
|
||||
clipRule: 'evenodd'
|
||||
}),
|
||||
h('path', {
|
||||
d: 'M13.2138 6.78296C13.5394 7.10825 13.5397 7.63588 13.2144 7.96147L10.5894 10.5889C10.2641 10.9145 9.73644 10.9147 9.41085 10.5894C9.08527 10.2641 9.08502 9.73651 9.41031 9.41092L12.0353 6.7835C12.3606 6.45792 12.8882 6.45767 13.2138 6.78296Z',
|
||||
fill: 'currentColor',
|
||||
fillRule: 'evenodd',
|
||||
clipRule: 'evenodd'
|
||||
}),
|
||||
h('path', {
|
||||
d: 'M15.1942 1.72962C15.506 1.8584 15.7095 2.16249 15.7095 2.49986V4.29161H17.4998C17.8365 4.29161 18.1401 4.49423 18.2693 4.80516C18.3985 5.11608 18.3279 5.47421 18.0904 5.71284L15.8508 7.96276C15.6944 8.11987 15.4819 8.2082 15.2602 8.2082H12.6248C12.1645 8.2082 11.7914 7.8351 11.7914 7.37486V4.76086C11.7914 4.54046 11.8787 4.32904 12.0342 4.17287L14.2856 1.91186C14.5237 1.6728 14.8824 1.60085 15.1942 1.72962ZM13.4581 5.105V6.54153H14.9139L15.4945 5.95828H14.8761C14.4159 5.95828 14.0428 5.58518 14.0428 5.12495V4.51779L13.4581 5.105Z',
|
||||
fill: 'currentColor',
|
||||
fillRule: 'evenodd',
|
||||
clipRule: 'evenodd'
|
||||
})
|
||||
]
|
||||
)
|
||||
])
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import system from './system'
|
|||
import userManage from './user-manage'
|
||||
import resourceAuthorization from './resource-authorization'
|
||||
import application from './application'
|
||||
import problem from './problem'
|
||||
// import notFound from './404'
|
||||
|
||||
// import applicationOverview from './application-overview'
|
||||
|
|
@ -15,7 +16,7 @@ import application from './application'
|
|||
// import team from './team'
|
||||
|
||||
// import paragraph from './paragraph'
|
||||
// import problem from './problem'
|
||||
|
||||
// import log from './log'
|
||||
// import applicationWorkflow from './application-workflow'
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ export default {
|
|||
userManage,
|
||||
resourceAuthorization,
|
||||
application,
|
||||
problem,
|
||||
// notFound,
|
||||
|
||||
// applicationOverview,
|
||||
|
|
@ -37,7 +39,7 @@ export default {
|
|||
// user,
|
||||
// team,
|
||||
// paragraph,
|
||||
// problem,
|
||||
|
||||
// log,
|
||||
// applicationWorkflow,
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
export default {
|
||||
title: '问题',
|
||||
createProblem: '创建问题',
|
||||
detailProblem: '问题详情',
|
||||
quickCreateProblem: '快速创建问题',
|
||||
quickCreateName: '问题',
|
||||
tip: {
|
||||
placeholder: '请输入问题,支持输入多个,一行一个。',
|
||||
errorMessage: '问题不能为空!',
|
||||
requiredMessage: '请输入问题',
|
||||
relatedSuccess:'批量关联分段成功'
|
||||
},
|
||||
|
||||
setting: {
|
||||
batchDelete: '批量删除',
|
||||
cancelRelated: '取消关联'
|
||||
},
|
||||
searchBar: {
|
||||
placeholder: '按名称搜索'
|
||||
},
|
||||
table: {
|
||||
paragraph_count: '关联分段数',
|
||||
updateTime: '更新时间'
|
||||
},
|
||||
delete: {
|
||||
confirmTitle: '是否删除问题:',
|
||||
confirmMessage1: '删除问题关联的',
|
||||
confirmMessage2: '个分段会被取消关联,请谨慎操作。'
|
||||
},
|
||||
relateParagraph: {
|
||||
title: '关联分段',
|
||||
selectDocument: '选择文档',
|
||||
placeholder: '按 文档名称 搜索',
|
||||
selectedParagraph: '已选分段',
|
||||
count: '个'
|
||||
},
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
const ModelRouter = {
|
||||
path: '/knowledge/:id',
|
||||
name: 'DatasetDetail',
|
||||
name: 'KnowledgeDetail',
|
||||
meta: { title: 'common.fileUpload.document', activeMenu: '/knowledge', breadcrumb: true },
|
||||
component: () => import('@/layout/layout-template/MainLayout.vue'),
|
||||
hidden: true,
|
||||
|
|
@ -14,35 +14,35 @@ const ModelRouter = {
|
|||
title: 'common.fileUpload.document',
|
||||
active: 'document',
|
||||
parentPath: '/knowledge/:id',
|
||||
parentName: 'DatasetDetail',
|
||||
parentName: 'KnowledgeDetail',
|
||||
},
|
||||
component: () => import('@/views/document/index.vue'),
|
||||
},
|
||||
// {
|
||||
// path: 'problem',
|
||||
// name: 'Problem',
|
||||
// meta: {
|
||||
// icon: 'app-problems',
|
||||
// iconActive: 'QuestionFilled',
|
||||
// title: 'views.problem.title',
|
||||
// active: 'problem',
|
||||
// parentPath: '/dataset/:id',
|
||||
// parentName: 'DatasetDetail'
|
||||
// },
|
||||
// component: () => import('@/views/problem/index.vue')
|
||||
// },
|
||||
// {
|
||||
// path: 'hit-test',
|
||||
// name: 'DatasetHitTest',
|
||||
// meta: {
|
||||
// icon: 'app-hit-test',
|
||||
// title: 'views.application.hitTest.title',
|
||||
// active: 'hit-test',
|
||||
// parentPath: '/dataset/:id',
|
||||
// parentName: 'DatasetDetail'
|
||||
// },
|
||||
// component: () => import('@/views/hit-test/index.vue')
|
||||
// },
|
||||
{
|
||||
path: 'problem',
|
||||
name: 'Problem',
|
||||
meta: {
|
||||
icon: 'app-problems',
|
||||
iconActive: 'QuestionFilled',
|
||||
title: 'views.problem.title',
|
||||
active: 'problem',
|
||||
parentPath: '/knowledge/:id',
|
||||
parentName: 'KnowledgeDetail'
|
||||
},
|
||||
component: () => import('@/views/problem/index.vue')
|
||||
},
|
||||
{
|
||||
path: 'hit-test',
|
||||
name: 'DatasetHitTest',
|
||||
meta: {
|
||||
icon: 'app-hit-test',
|
||||
title: 'views.application.hitTest.title',
|
||||
active: 'hit-test',
|
||||
parentPath: '/knowledge/:id',
|
||||
parentName: 'KnowledgeDetail'
|
||||
},
|
||||
component: () => import('@/views/hit-test/index.vue')
|
||||
},
|
||||
// {
|
||||
// path: 'setting',
|
||||
// name: 'DatasetSetting',
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
// 排序
|
||||
export function arraySort(list: Array<any>, property: any, desc?: boolean) {
|
||||
return list.sort((a: any, b: any) => {
|
||||
return desc ? b[property] - a[property] : a[property] - b[property]
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 拆分数组 每n个拆分为一个数组
|
||||
* @param sourceDataList 资源数据
|
||||
|
|
|
|||
|
|
@ -0,0 +1,440 @@
|
|||
<template>
|
||||
<div class="hit-test">
|
||||
<LayoutContainer>
|
||||
<template #header>
|
||||
<h4>
|
||||
{{ $t('views.application.hitTest.title') }}
|
||||
<el-text type="info" class="ml-4"> {{ $t('views.application.hitTest.text') }}</el-text>
|
||||
</h4>
|
||||
</template>
|
||||
<div class="hit-test__main p-16" v-loading="loading">
|
||||
<div class="question-title" :style="{ visibility: questionTitle ? 'visible' : 'hidden' }">
|
||||
<div class="avatar">
|
||||
<AppAvatar>
|
||||
<img src="@/assets/user-icon.svg" style="width: 54%" alt="" />
|
||||
</AppAvatar>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h4 class="text break-all">{{ questionTitle }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
<el-scrollbar>
|
||||
<div class="hit-test-height">
|
||||
<el-empty
|
||||
v-if="first"
|
||||
:image="emptyImg"
|
||||
:description="$t('views.application.hitTest.emptyMessage1')"
|
||||
style="padding-top: 160px"
|
||||
:image-size="125"
|
||||
/>
|
||||
<el-empty
|
||||
v-else-if="paragraphDetail.length == 0"
|
||||
:description="$t('views.application.hitTest.emptyMessage2')"
|
||||
style="padding-top: 160px"
|
||||
:image-size="125"
|
||||
/>
|
||||
<el-row v-else>
|
||||
<el-col
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="12"
|
||||
:lg="8"
|
||||
:xl="6"
|
||||
v-for="(item, index) in paragraphDetail"
|
||||
:key="index"
|
||||
class="p-8"
|
||||
>
|
||||
<CardBox
|
||||
shadow="hover"
|
||||
:title="item.title || '-'"
|
||||
:description="item.content"
|
||||
class="document-card layout-bg layout-bg cursor"
|
||||
:class="item.is_active ? '' : 'disabled'"
|
||||
:showIcon="false"
|
||||
@click="editParagraph(item)"
|
||||
>
|
||||
<template #icon>
|
||||
<AppAvatar class="mr-12 avatar-light" :size="22">
|
||||
{{ index + 1 + '' }}</AppAvatar
|
||||
>
|
||||
</template>
|
||||
<div class="active-button primary">{{ item.similarity?.toFixed(3) }}</div>
|
||||
<template #footer>
|
||||
<div class="footer-content flex-between">
|
||||
<el-text>
|
||||
<el-icon>
|
||||
<Document />
|
||||
</el-icon>
|
||||
{{ item?.document_name }}
|
||||
</el-text>
|
||||
<div v-if="item.trample_num || item.star_num">
|
||||
<span v-if="item.star_num">
|
||||
<AppIcon iconName="app-like-color"></AppIcon>
|
||||
{{ item.star_num }}
|
||||
</span>
|
||||
<span v-if="item.trample_num" class="ml-4">
|
||||
<AppIcon iconName="app-oppose-color"></AppIcon>
|
||||
{{ item.trample_num }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</CardBox>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
|
||||
<ParagraphDialog ref="ParagraphDialogRef" :title="title" @refresh="refresh" />
|
||||
</LayoutContainer>
|
||||
<div class="hit-test__operate p-24 pt-0">
|
||||
<el-popover :visible="popoverVisible" placement="right-end" :width="500" trigger="click">
|
||||
<template #reference>
|
||||
<el-button icon="Setting" class="mb-8" @click="settingChange('open')">{{
|
||||
$t('common.paramSetting')
|
||||
}}</el-button>
|
||||
</template>
|
||||
<div class="mb-16">
|
||||
<div class="title mb-8">
|
||||
{{ $t('views.application.applicationForm.dialog.selectSearchMode') }}
|
||||
</div>
|
||||
<el-radio-group
|
||||
v-model="cloneForm.search_mode"
|
||||
class="card__radio"
|
||||
@change="changeHandle"
|
||||
>
|
||||
<el-card
|
||||
shadow="never"
|
||||
class="mb-16"
|
||||
:class="cloneForm.search_mode === 'embedding' ? 'active' : ''"
|
||||
>
|
||||
<el-radio value="embedding" size="large">
|
||||
<p class="mb-4">
|
||||
{{ $t('views.application.applicationForm.dialog.vectorSearch') }}
|
||||
</p>
|
||||
<el-text type="info">{{
|
||||
$t('views.application.applicationForm.dialog.vectorSearchTooltip')
|
||||
}}</el-text>
|
||||
</el-radio>
|
||||
</el-card>
|
||||
<el-card
|
||||
shadow="never"
|
||||
class="mb-16"
|
||||
:class="cloneForm.search_mode === 'keywords' ? 'active' : ''"
|
||||
>
|
||||
<el-radio value="keywords" size="large">
|
||||
<p class="mb-4">
|
||||
{{ $t('views.application.applicationForm.dialog.fullTextSearch') }}
|
||||
</p>
|
||||
<el-text type="info">{{
|
||||
$t('views.application.applicationForm.dialog.fullTextSearchTooltip')
|
||||
}}</el-text>
|
||||
</el-radio>
|
||||
</el-card>
|
||||
<el-card
|
||||
shadow="never"
|
||||
class="mb-16"
|
||||
:class="cloneForm.search_mode === 'blend' ? 'active' : ''"
|
||||
>
|
||||
<el-radio value="blend" size="large">
|
||||
<p class="mb-4">
|
||||
{{ $t('views.application.applicationForm.dialog.hybridSearch') }}
|
||||
</p>
|
||||
<el-text type="info">{{
|
||||
$t('views.application.applicationForm.dialog.hybridSearchTooltip')
|
||||
}}</el-text>
|
||||
</el-radio>
|
||||
</el-card>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<div class="mb-16">
|
||||
<div class="title mb-8">
|
||||
{{ $t('views.application.applicationForm.dialog.similarityThreshold') }}
|
||||
</div>
|
||||
<el-input-number
|
||||
v-model="cloneForm.similarity"
|
||||
:min="0"
|
||||
:max="cloneForm.search_mode === 'blend' ? 2 : 1"
|
||||
:precision="3"
|
||||
:step="0.1"
|
||||
:value-on-clear="0"
|
||||
controls-position="right"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="mb-16">
|
||||
<div class="title mb-8">
|
||||
{{ $t('views.application.applicationForm.dialog.topReferences') }}
|
||||
</div>
|
||||
<el-input-number
|
||||
v-model="cloneForm.top_number"
|
||||
:min="1"
|
||||
:max="10000"
|
||||
controls-position="right"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="text-right">
|
||||
<el-button @click="popoverVisible = false">{{ $t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="settingChange('close')">{{
|
||||
$t('common.confirm')
|
||||
}}</el-button>
|
||||
</div>
|
||||
</el-popover>
|
||||
<div class="operate-textarea flex">
|
||||
<el-input
|
||||
ref="quickInputRef"
|
||||
v-model="inputValue"
|
||||
type="textarea"
|
||||
:placeholder="$t('common.inputPlaceholder')"
|
||||
:autosize="{ minRows: 1, maxRows: 8 }"
|
||||
@keydown.enter="sendChatHandle($event)"
|
||||
/>
|
||||
<div class="operate">
|
||||
<el-button
|
||||
text
|
||||
class="sent-button"
|
||||
:disabled="isDisabledChart || loading"
|
||||
@click="sendChatHandle"
|
||||
>
|
||||
<img v-show="isDisabledChart || loading" src="@/assets/icon_send.svg" alt="" />
|
||||
<img
|
||||
v-show="!isDisabledChart && !loading"
|
||||
src="@/assets/icon_send_colorful.svg"
|
||||
alt=""
|
||||
/>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref, onMounted, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import KnowledgeApi from '@/api/knowledge/knowledge'
|
||||
// import applicationApi from '@/api/application'
|
||||
import ParagraphDialog from '@/views/paragraph/component/ParagraphDialog.vue'
|
||||
import { arraySort } from '@/utils/common'
|
||||
import emptyImg from '@/assets/hit-test-empty.png'
|
||||
import { t } from '@/locales'
|
||||
const route = useRoute()
|
||||
const {
|
||||
meta: { activeMenu },
|
||||
params: { id }
|
||||
} = route as any
|
||||
|
||||
const quickInputRef = ref()
|
||||
const ParagraphDialogRef = ref()
|
||||
const loading = ref(false)
|
||||
const paragraphDetail = ref<any[]>([])
|
||||
const title = ref('')
|
||||
const inputValue = ref('')
|
||||
const formInline = ref({
|
||||
similarity: 0.6,
|
||||
top_number: 5,
|
||||
search_mode: 'embedding'
|
||||
})
|
||||
|
||||
// 第一次加载
|
||||
const first = ref(true)
|
||||
|
||||
const cloneForm = ref<any>({})
|
||||
|
||||
const popoverVisible = ref(false)
|
||||
const questionTitle = ref('')
|
||||
|
||||
const isDisabledChart = computed(() => !inputValue.value)
|
||||
|
||||
const isApplication = computed(() => {
|
||||
return activeMenu.includes('application')
|
||||
})
|
||||
const isDataset = computed(() => {
|
||||
return activeMenu.includes('dataset')
|
||||
})
|
||||
|
||||
function changeHandle(val: string) {
|
||||
if (val === 'keywords') {
|
||||
cloneForm.value.similarity = 0
|
||||
} else {
|
||||
cloneForm.value.similarity = 0.6
|
||||
}
|
||||
}
|
||||
|
||||
function settingChange(val: string) {
|
||||
if (val === 'open') {
|
||||
popoverVisible.value = true
|
||||
cloneForm.value = cloneDeep(formInline.value)
|
||||
} else if (val === 'close') {
|
||||
popoverVisible.value = false
|
||||
formInline.value = cloneDeep(cloneForm.value)
|
||||
}
|
||||
}
|
||||
|
||||
function editParagraph(row: any) {
|
||||
title.value = t('views.paragraph.paragraphDetail')
|
||||
ParagraphDialogRef.value.open(row)
|
||||
}
|
||||
|
||||
function sendChatHandle(event: any) {
|
||||
if (!event?.ctrlKey && !event?.shiftKey && !event?.altKey && !event?.metaKey) {
|
||||
// 如果没有按下组合键,则会阻止默认事件
|
||||
event.preventDefault()
|
||||
if (!isDisabledChart.value && !loading.value) {
|
||||
getHitTestList()
|
||||
}
|
||||
} else {
|
||||
// 如果同时按下ctrl/shift/cmd/opt +enter,则会换行
|
||||
insertNewlineAtCursor(event)
|
||||
}
|
||||
}
|
||||
const insertNewlineAtCursor = (event?: any) => {
|
||||
const textarea = quickInputRef.value.$el.querySelector(
|
||||
'.el-textarea__inner'
|
||||
) as HTMLTextAreaElement
|
||||
const startPos = textarea.selectionStart
|
||||
const endPos = textarea.selectionEnd
|
||||
// 阻止默认行为(避免额外的换行符)
|
||||
event.preventDefault()
|
||||
// 在光标处插入换行符
|
||||
inputValue.value = inputValue.value.slice(0, startPos) + '\n' + inputValue.value.slice(endPos)
|
||||
nextTick(() => {
|
||||
textarea.setSelectionRange(startPos + 1, startPos + 1) // 光标定位到换行后位置
|
||||
})
|
||||
}
|
||||
|
||||
function getHitTestList() {
|
||||
const obj = {
|
||||
query_text: inputValue.value,
|
||||
...formInline.value
|
||||
}
|
||||
if (isDataset.value) {
|
||||
datasetApi.getDatasetHitTest(id, obj, loading).then((res) => {
|
||||
paragraphDetail.value = res.data && arraySort(res.data, 'comprehensive_score', true)
|
||||
questionTitle.value = inputValue.value
|
||||
inputValue.value = ''
|
||||
first.value = false
|
||||
})
|
||||
} else if (isApplication.value) {
|
||||
applicationApi.getApplicationHitTest(id, obj, loading).then((res) => {
|
||||
paragraphDetail.value = res.data && arraySort(res.data, 'comprehensive_score', true)
|
||||
questionTitle.value = inputValue.value
|
||||
inputValue.value = ''
|
||||
first.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function refresh(data: any) {
|
||||
if (data) {
|
||||
const obj = paragraphDetail.value.filter((v) => v.id === data.id)[0]
|
||||
obj.content = data.content
|
||||
obj.title = data.title
|
||||
} else {
|
||||
paragraphDetail.value = []
|
||||
getHitTestList()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.hit-test {
|
||||
.question-title {
|
||||
.avatar {
|
||||
float: left;
|
||||
}
|
||||
.content {
|
||||
padding-left: 40px;
|
||||
.text {
|
||||
padding: 6px 0;
|
||||
height: 34px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__operate {
|
||||
.operate-textarea {
|
||||
box-shadow: 0px 6px 24px 0px rgba(31, 35, 41, 0.08);
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ffffff;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:has(.el-textarea__inner:focus) {
|
||||
border: 1px solid var(--el-color-primary);
|
||||
}
|
||||
|
||||
:deep(.el-textarea__inner) {
|
||||
border-radius: 8px !important;
|
||||
box-shadow: none;
|
||||
resize: none;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
.operate {
|
||||
padding: 6px 10px;
|
||||
.sent-button {
|
||||
max-height: none;
|
||||
.el-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
:deep(.el-loading-spinner) {
|
||||
margin-top: -15px;
|
||||
.circular {
|
||||
width: 31px;
|
||||
height: 31px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.hit-test {
|
||||
&__header {
|
||||
position: absolute;
|
||||
right: calc(var(--app-base-px) * 3);
|
||||
}
|
||||
|
||||
.hit-test-height {
|
||||
height: calc(var(--app-main-height) - 170px);
|
||||
}
|
||||
.document-card {
|
||||
height: 210px;
|
||||
border: 1px solid var(--app-layout-bg-color);
|
||||
&:hover {
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--el-border-color);
|
||||
}
|
||||
&.disabled {
|
||||
background: var(--app-layout-bg-color);
|
||||
border: 1px solid var(--app-layout-bg-color);
|
||||
:deep(.description) {
|
||||
color: var(--app-border-color-dark);
|
||||
}
|
||||
:deep(.title) {
|
||||
color: var(--app-border-color-dark);
|
||||
}
|
||||
}
|
||||
:deep(.description) {
|
||||
-webkit-line-clamp: 5 !important;
|
||||
height: 110px;
|
||||
}
|
||||
.active-button {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="title"
|
||||
v-model="dialogVisible"
|
||||
width="80%"
|
||||
class="paragraph-dialog"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<el-row v-loading="loading">
|
||||
<el-col :span="18">
|
||||
<el-scrollbar height="500" wrap-class="paragraph-scrollbar">
|
||||
<div class="p-24" style="padding-bottom: 8px">
|
||||
<div style="position: absolute; right: 20px; top: 20px; ">
|
||||
<el-button text @click="isEdit = true" v-if="problemId && !isEdit">
|
||||
<el-icon><EditPen /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<ParagraphForm ref="paragraphFormRef" :data="detail" :isEdit="isEdit" />
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<div class="text-right p-24 pt-0" v-if="problemId && isEdit">
|
||||
<el-button @click.prevent="cancelEdit"> {{$t('common.cancel')}} </el-button>
|
||||
<el-button type="primary" :disabled="loading" @click="handleDebounceClick">
|
||||
{{$t('common.save')}}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6" class="border-l" style="width: 300px">
|
||||
<!-- 关联问题 -->
|
||||
<ProblemComponent
|
||||
:problemId="problemId"
|
||||
:docId="document_id"
|
||||
:datasetId="dataset_id"
|
||||
ref="ProblemRef"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer v-if="!problemId">
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> {{$t('common.cancel')}} </el-button>
|
||||
<el-button :disabled="loading" type="primary" @click="handleDebounceClick">
|
||||
{{$t('common.submit')}}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, nextTick } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { cloneDeep, debounce } from 'lodash'
|
||||
|
||||
import ParagraphForm from '@/views/paragraph/component/ParagraphForm.vue'
|
||||
import ProblemComponent from '@/views/paragraph/component/ProblemComponent.vue'
|
||||
import paragraphApi from '@/api/knowledge/paragraph'
|
||||
import useStore from '@/stores'
|
||||
|
||||
const props = defineProps({
|
||||
title: String
|
||||
})
|
||||
|
||||
const { paragraph } = useStore()
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id, documentId }
|
||||
} = route as any
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const ProblemRef = ref()
|
||||
const paragraphFormRef = ref<any>()
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
||||
const loading = ref(false)
|
||||
const problemId = ref('')
|
||||
const detail = ref<any>({})
|
||||
const isEdit = ref(false)
|
||||
const document_id = ref('')
|
||||
const dataset_id = ref('')
|
||||
const cloneData = ref(null)
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
problemId.value = ''
|
||||
detail.value = {}
|
||||
isEdit.value = false
|
||||
document_id.value = ''
|
||||
dataset_id.value = ''
|
||||
cloneData.value = null
|
||||
}
|
||||
})
|
||||
|
||||
const cancelEdit = () => {
|
||||
isEdit.value = false
|
||||
detail.value = cloneDeep(cloneData.value)
|
||||
}
|
||||
|
||||
const open = (data: any) => {
|
||||
if (data) {
|
||||
detail.value.title = data.title
|
||||
detail.value.content = data.content
|
||||
cloneData.value = cloneDeep(detail.value)
|
||||
problemId.value = data.id
|
||||
document_id.value = data.document_id
|
||||
dataset_id.value = data.dataset_id || id
|
||||
} else {
|
||||
isEdit.value = true
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
const submitHandle = async () => {
|
||||
if (await paragraphFormRef.value?.validate()) {
|
||||
loading.value = true
|
||||
if (problemId.value) {
|
||||
paragraph
|
||||
.asyncPutParagraph(
|
||||
dataset_id.value,
|
||||
documentId || document_id.value,
|
||||
problemId.value,
|
||||
paragraphFormRef.value?.form,
|
||||
loading
|
||||
)
|
||||
.then((res: any) => {
|
||||
isEdit.value = false
|
||||
emit('refresh', res.data)
|
||||
})
|
||||
} else {
|
||||
const obj =
|
||||
ProblemRef.value.problemList.length > 0
|
||||
? {
|
||||
problem_list: ProblemRef.value.problemList,
|
||||
...paragraphFormRef.value?.form
|
||||
}
|
||||
: paragraphFormRef.value?.form
|
||||
paragraphApi.postParagraph(id, documentId, obj, loading).then((res) => {
|
||||
dialogVisible.value = false
|
||||
emit('refresh')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
const handleDebounceClick = debounce(() => {
|
||||
submitHandle()
|
||||
}, 200)
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
<template>
|
||||
<el-form
|
||||
ref="paragraphFormRef"
|
||||
:model="form"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
:rules="rules"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item :label="$t('views.paragraph.form.paragraphTitle.label')">
|
||||
<el-input
|
||||
v-if="isEdit"
|
||||
v-model="form.title"
|
||||
:placeholder="$t('views.paragraph.form.paragraphTitle.placeholder')"
|
||||
maxlength="256"
|
||||
show-word-limit
|
||||
>
|
||||
</el-input>
|
||||
<span class="lighter" v-else>{{ form.title || '-' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('views.paragraph.form.content.label')" prop="content">
|
||||
<MdEditor
|
||||
v-if="isEdit"
|
||||
v-model="form.content"
|
||||
:placeholder="$t('views.paragraph.form.content.placeholder')"
|
||||
:maxLength="100000"
|
||||
:preview="false"
|
||||
:toolbars="toolbars"
|
||||
style="height: 300px"
|
||||
@onUploadImg="onUploadImg"
|
||||
:footers="footers"
|
||||
>
|
||||
<template #defFooters>
|
||||
<span style="margin-left: -6px">/ 100000</span>
|
||||
</template>
|
||||
</MdEditor>
|
||||
<MdPreview
|
||||
v-else
|
||||
ref="editorRef"
|
||||
editorId="preview-only"
|
||||
:modelValue="form.content"
|
||||
class="maxkb-md"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onUnmounted, watch } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import imageApi from '@/api/image'
|
||||
import { t } from '@/locales'
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
isEdit: Boolean
|
||||
})
|
||||
|
||||
const toolbars = [
|
||||
'bold',
|
||||
'underline',
|
||||
'italic',
|
||||
'-',
|
||||
'title',
|
||||
'strikeThrough',
|
||||
'sub',
|
||||
'sup',
|
||||
'quote',
|
||||
'unorderedList',
|
||||
'orderedList',
|
||||
'task',
|
||||
'-',
|
||||
'codeRow',
|
||||
'code',
|
||||
'link',
|
||||
'image',
|
||||
'table',
|
||||
'mermaid',
|
||||
'katex',
|
||||
'-',
|
||||
'revoke',
|
||||
'next',
|
||||
'=',
|
||||
'pageFullscreen',
|
||||
'preview',
|
||||
'htmlPreview'
|
||||
] as any[]
|
||||
|
||||
const footers = ['markdownTotal', 0, '=', 1, 'scrollSwitch']
|
||||
|
||||
const editorRef = ref()
|
||||
|
||||
const form = ref<any>({
|
||||
title: '',
|
||||
content: ''
|
||||
})
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
content: [
|
||||
{ required: true, message: t('views.paragraph.form.content.requiredMessage1'), trigger: 'blur' },
|
||||
{ max: 100000, message: t('views.paragraph.form.content.requiredMessage2'), trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
const paragraphFormRef = ref<FormInstance>()
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(value) => {
|
||||
if (value && JSON.stringify(value) !== '{}') {
|
||||
form.value.title = value.title
|
||||
form.value.content = value.content
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => props.isEdit,
|
||||
(value) => {
|
||||
if (!value) {
|
||||
paragraphFormRef.value?.clearValidate()
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
/*
|
||||
表单校验
|
||||
*/
|
||||
function validate() {
|
||||
if (!paragraphFormRef.value) return
|
||||
return paragraphFormRef.value.validate((valid: any) => {
|
||||
return valid
|
||||
})
|
||||
}
|
||||
|
||||
const onUploadImg = async (files: any, callback: any) => {
|
||||
const res = await Promise.all(
|
||||
files.map((file: any) => {
|
||||
return new Promise((rev, rej) => {
|
||||
const fd = new FormData()
|
||||
fd.append('file', file)
|
||||
|
||||
imageApi
|
||||
.postImage(fd)
|
||||
.then((res: any) => {
|
||||
rev(res)
|
||||
})
|
||||
.catch((error) => rej(error))
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
callback(res.map((item) => item.data))
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
form.value = {
|
||||
title: '',
|
||||
content: ''
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
validate,
|
||||
form
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
<template>
|
||||
<p class="bold title p-24" style="padding-bottom: 0">
|
||||
<span class="flex align-center">
|
||||
<span>{{ $t('views.paragraph.relatedProblem.title') }}</span>
|
||||
<el-divider direction="vertical" class="mr-4" />
|
||||
<el-button text @click="addProblem">
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-button>
|
||||
</span>
|
||||
</p>
|
||||
<div v-loading="loading">
|
||||
<el-scrollbar height="500px">
|
||||
<div class="p-24" style="padding-top: 16px">
|
||||
<el-select
|
||||
v-if="isAddProblem"
|
||||
v-model="problemValue"
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
:reserve-keyword="false"
|
||||
:placeholder="$t('views.paragraph.relatedProblem.placeholder')"
|
||||
remote
|
||||
:remote-method="remoteMethod"
|
||||
:loading="optionLoading"
|
||||
@change="addProblemHandle"
|
||||
@blur="isAddProblem = false"
|
||||
class="mb-16"
|
||||
popper-class="select-popper"
|
||||
:popper-append-to-body="false"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in problemOptions"
|
||||
:key="item.id"
|
||||
:label="item.content"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.content }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
<template v-for="(item, index) in problemList" :key="index">
|
||||
<TagEllipsis
|
||||
@close="delProblemHandle(item, index)"
|
||||
class="question-tag"
|
||||
type="info"
|
||||
effect="plain"
|
||||
closable
|
||||
>
|
||||
<auto-tooltip :content="item.content">
|
||||
{{ item.content }}
|
||||
</auto-tooltip>
|
||||
</TagEllipsis>
|
||||
</template>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import paragraphApi from '@/api/knowledge/paragraph'
|
||||
import useStore from '@/stores'
|
||||
|
||||
const props = defineProps({
|
||||
problemId: String,
|
||||
docId: String,
|
||||
datasetId: String
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id, documentId } // id为datasetId
|
||||
} = route as any
|
||||
|
||||
const { problem } = useStore()
|
||||
const inputRef = ref()
|
||||
const loading = ref(false)
|
||||
const isAddProblem = ref(false)
|
||||
|
||||
const problemValue = ref('')
|
||||
const problemList = ref<any[]>([])
|
||||
|
||||
const problemOptions = ref<any[]>([])
|
||||
const optionLoading = ref(false)
|
||||
|
||||
watch(
|
||||
() => props.problemId,
|
||||
(value) => {
|
||||
if (value) {
|
||||
getProblemList()
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
function delProblemHandle(item: any, index: number) {
|
||||
if (item.id) {
|
||||
problem
|
||||
.asyncDisassociationProblem(
|
||||
props.datasetId || id,
|
||||
documentId || props.docId,
|
||||
props.problemId || '',
|
||||
item.id,
|
||||
loading
|
||||
)
|
||||
.then((res: any) => {
|
||||
getProblemList()
|
||||
})
|
||||
} else {
|
||||
problemList.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
function getProblemList() {
|
||||
loading.value = true
|
||||
paragraphApi
|
||||
.getProblem(props.datasetId || id, documentId || props.docId, props.problemId || '')
|
||||
.then((res) => {
|
||||
problemList.value = res.data
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function addProblem() {
|
||||
isAddProblem.value = true
|
||||
nextTick(() => {
|
||||
inputRef.value?.focus()
|
||||
})
|
||||
}
|
||||
function addProblemHandle(val: string) {
|
||||
if (props.problemId) {
|
||||
const api = problemOptions.value.some((option) => option.id === val)
|
||||
? problem.asyncAssociationProblem(
|
||||
props.datasetId || id,
|
||||
documentId || props.docId,
|
||||
props.problemId,
|
||||
val,
|
||||
loading
|
||||
)
|
||||
: paragraphApi.postProblem(
|
||||
props.datasetId || id,
|
||||
documentId || props.docId,
|
||||
props.problemId,
|
||||
{
|
||||
content: val
|
||||
},
|
||||
loading
|
||||
)
|
||||
api.then(() => {
|
||||
getProblemList()
|
||||
problemValue.value = ''
|
||||
isAddProblem.value = false
|
||||
})
|
||||
} else {
|
||||
const problem = problemOptions.value.find((option) => option.id === val)
|
||||
const content = problem ? problem.content : val
|
||||
if (!problemList.value.some((item) => item.content === content)) {
|
||||
problemList.value.push({ content: content })
|
||||
}
|
||||
|
||||
problemValue.value = ''
|
||||
isAddProblem.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const remoteMethod = (query: string) => {
|
||||
getProblemOption(query)
|
||||
}
|
||||
|
||||
function getProblemOption(filterText?: string) {
|
||||
return problem
|
||||
.asyncGetProblem(
|
||||
props.datasetId || (id as string),
|
||||
{ current_page: 1, page_size: 100 },
|
||||
filterText && { content: filterText },
|
||||
optionLoading
|
||||
)
|
||||
.then((res: any) => {
|
||||
problemOptions.value = res.data.records
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getProblemOption()
|
||||
})
|
||||
onUnmounted(() => {
|
||||
problemList.value = []
|
||||
problemValue.value = ''
|
||||
isAddProblem.value = false
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
problemList
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.question-tag {
|
||||
// width: 217px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="`${$t('views.log.selectDataset')}/${$t('common.fileUpload.document')}`"
|
||||
v-model="dialogVisible"
|
||||
width="500"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
:rules="rules"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item :label="$t('views.log.selectDataset')" prop="dataset_id">
|
||||
<el-select
|
||||
v-model="form.dataset_id"
|
||||
filterable
|
||||
:placeholder="$t('views.log.selectDatasetPlaceholder')"
|
||||
:loading="optionLoading"
|
||||
@change="changeDataset"
|
||||
>
|
||||
<el-option v-for="item in datasetList" :key="item.id" :label="item.name" :value="item.id">
|
||||
<span class="flex align-center">
|
||||
<AppAvatar
|
||||
v-if="!item.dataset_id && item.type === '1'"
|
||||
class="mr-12 avatar-purple"
|
||||
shape="square"
|
||||
:size="24"
|
||||
>
|
||||
<img src="@/assets/icon_web.svg" style="width: 58%" alt="" />
|
||||
</AppAvatar>
|
||||
<AppAvatar
|
||||
v-else-if="!item.dataset_id && item.type === '2'"
|
||||
class="mr-12 avatar-purple"
|
||||
shape="square"
|
||||
:size="24"
|
||||
style="background: none"
|
||||
>
|
||||
<img src="@/assets/logo_lark.svg" style="width: 100%" alt="" />
|
||||
</AppAvatar>
|
||||
<AppAvatar
|
||||
v-else-if="!item.dataset_id && item.type === '0'"
|
||||
class="mr-12 avatar-blue"
|
||||
shape="square"
|
||||
:size="24"
|
||||
>
|
||||
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
|
||||
</AppAvatar>
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('views.log.saveToDocument')" prop="document_id">
|
||||
<el-select
|
||||
v-model="form.document_id"
|
||||
filterable
|
||||
:placeholder="$t('views.log.documentPlaceholder')"
|
||||
:loading="optionLoading"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in documentList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
|
||||
<el-button type="primary" @click="submitForm(formRef)" :loading="loading">
|
||||
{{ $t('views.document.setting.migration') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, reactive } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import paragraphApi from '@/api/knowledge/paragraph'
|
||||
import useStore from '@/stores'
|
||||
import { t } from '@/locales'
|
||||
const { dataset, document } = useStore()
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id, documentId }
|
||||
} = route as any
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
const formRef = ref()
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const loading = ref(false)
|
||||
|
||||
const form = ref<any>({
|
||||
dataset_id: '',
|
||||
document_id: ''
|
||||
})
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
dataset_id: [
|
||||
{ required: true, message: t('views.log.selectDatasetPlaceholder'), trigger: 'change' }
|
||||
],
|
||||
document_id: [{ required: true, message: t('views.log.documentPlaceholder'), trigger: 'change' }]
|
||||
})
|
||||
|
||||
const datasetList = ref<any[]>([])
|
||||
const documentList = ref<any[]>([])
|
||||
const optionLoading = ref(false)
|
||||
const paragraphList = ref<string[]>([])
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
form.value = {
|
||||
dataset_id: '',
|
||||
document_id: ''
|
||||
}
|
||||
datasetList.value = []
|
||||
documentList.value = []
|
||||
paragraphList.value = []
|
||||
formRef.value?.clearValidate()
|
||||
}
|
||||
})
|
||||
|
||||
function changeDataset(id: string) {
|
||||
form.value.document_id = ''
|
||||
getDocument(id)
|
||||
}
|
||||
|
||||
function getDocument(id: string) {
|
||||
document.asyncGetAllDocument(id, loading).then((res: any) => {
|
||||
documentList.value = res.data?.filter((v: any) => v.id !== documentId)
|
||||
})
|
||||
}
|
||||
|
||||
function getDataset() {
|
||||
dataset.asyncGetAllDataset(loading).then((res: any) => {
|
||||
datasetList.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
const open = (list: any) => {
|
||||
paragraphList.value = list
|
||||
getDataset()
|
||||
formRef.value?.clearValidate()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
const submitForm = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
paragraphApi
|
||||
.putMigrateMulParagraph(
|
||||
id,
|
||||
documentId,
|
||||
form.value.dataset_id,
|
||||
form.value.document_id,
|
||||
paragraphList.value,
|
||||
loading
|
||||
)
|
||||
.then(() => {
|
||||
emit('refresh')
|
||||
dialogVisible.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,438 @@
|
|||
<template>
|
||||
<LayoutContainer back-to="-1" class="document-detail">
|
||||
<template #header>
|
||||
<div style="width: 78%">
|
||||
<h3 style="display: inline-block">{{ documentDetail?.name }}</h3>
|
||||
<el-text type="info" v-if="documentDetail?.type === '1'"
|
||||
>({{ $t('views.document.form.source_url.label') }}:<el-link
|
||||
:href="documentDetail?.meta?.source_url"
|
||||
target="_blank"
|
||||
>
|
||||
<span class="break-all">{{ documentDetail?.meta?.source_url }} </span></el-link
|
||||
>)
|
||||
</el-text>
|
||||
</div>
|
||||
<div class="header-button">
|
||||
<el-button @click="batchSelectedHandle(true)" v-if="isBatch === false">
|
||||
{{ $t('views.paragraph.setting.batchSelected') }}
|
||||
</el-button>
|
||||
<el-button @click="batchSelectedHandle(false)" v-if="isBatch === true">
|
||||
{{ $t('views.paragraph.setting.cancelSelected') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
@click="addParagraph"
|
||||
type="primary"
|
||||
:disabled="loading"
|
||||
v-if="isBatch === false"
|
||||
>
|
||||
{{ $t('views.paragraph.addParagraph') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<div
|
||||
class="document-detail__main p-16"
|
||||
v-loading="(paginationConfig.current_page === 1 && loading) || changeStateloading"
|
||||
>
|
||||
<div class="flex-between p-8">
|
||||
<span>{{ paginationConfig.total }} {{ $t('views.paragraph.paragraph_count') }}</span>
|
||||
<el-input
|
||||
v-model="search"
|
||||
:placeholder="$t('common.search')"
|
||||
class="input-with-select"
|
||||
style="width: 260px"
|
||||
@change="searchHandle"
|
||||
clearable
|
||||
>
|
||||
<template #prepend>
|
||||
<el-select v-model="searchType" placeholder="Select" style="width: 80px">
|
||||
<el-option :label="$t('common.title')" value="title" />
|
||||
<el-option :label="$t('common.content')" value="content" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<el-scrollbar>
|
||||
<div class="document-detail-height">
|
||||
<el-empty v-if="paragraphDetail.length == 0" :description="$t('common.noData')" />
|
||||
|
||||
<InfiniteScroll
|
||||
v-else
|
||||
:size="paragraphDetail.length"
|
||||
:total="paginationConfig.total"
|
||||
:page_size="paginationConfig.page_size"
|
||||
v-model:current_page="paginationConfig.current_page"
|
||||
@load="getParagraphList"
|
||||
:loading="loading"
|
||||
>
|
||||
<el-row>
|
||||
<el-col
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="8"
|
||||
:lg="6"
|
||||
:xl="6"
|
||||
v-for="(item, index) in paragraphDetail"
|
||||
:key="index"
|
||||
class="p-8"
|
||||
>
|
||||
<!-- 批量操作card -->
|
||||
<CardBox
|
||||
v-if="isBatch === true"
|
||||
shadow="hover"
|
||||
:title="item.title || '-'"
|
||||
:description="item.content"
|
||||
class="document-card cursor"
|
||||
:class="multipleSelection.includes(item.id) ? 'selected' : ''"
|
||||
:showIcon="false"
|
||||
@click="selectHandle(item.id)"
|
||||
>
|
||||
<div class="active-button" @click.stop></div>
|
||||
|
||||
<template #footer>
|
||||
<div class="footer-content flex-between">
|
||||
<span>
|
||||
{{ numberFormat(item?.content.length) || 0 }}
|
||||
{{ $t('views.paragraph.character_count') }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</CardBox>
|
||||
<!-- 非批量操作card -->
|
||||
<CardBox
|
||||
v-else
|
||||
shadow="hover"
|
||||
:title="item.title || '-'"
|
||||
:description="item.content"
|
||||
class="document-card cursor"
|
||||
:class="item.is_active ? '' : 'disabled'"
|
||||
:showIcon="false"
|
||||
@click="editParagraph(item)"
|
||||
>
|
||||
<div class="active-button" @click.stop>
|
||||
<el-switch
|
||||
:loading="loading"
|
||||
v-model="item.is_active"
|
||||
:before-change="() => changeState(item)"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="footer-content flex-between">
|
||||
<span>
|
||||
{{ numberFormat(item?.content.length) || 0 }}
|
||||
{{ $t('views.paragraph.character_count') }}
|
||||
</span>
|
||||
|
||||
<span @click.stop>
|
||||
<el-dropdown trigger="click">
|
||||
<el-button text>
|
||||
<el-icon><MoreFilled /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="openGenerateDialog(item)">
|
||||
<el-icon><Connection /></el-icon>
|
||||
{{
|
||||
$t('views.document.generateQuestion.title')
|
||||
}}</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item @click="openSelectDocumentDialog(item)">
|
||||
<AppIcon iconName="app-migrate"></AppIcon>
|
||||
{{ $t('views.document.setting.migration') }}</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item icon="Delete" @click.stop="deleteParagraph(item)">{{
|
||||
$t('common.delete')
|
||||
}}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</CardBox>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
||||
<div class="mul-operation border-t w-full" v-if="isBatch === true">
|
||||
<el-button :disabled="multipleSelection.length === 0" @click="openGenerateDialog()">
|
||||
{{ $t('views.document.generateQuestion.title') }}
|
||||
</el-button>
|
||||
<el-button :disabled="multipleSelection.length === 0" @click="openSelectDocumentDialog()">
|
||||
{{ $t('views.document.setting.migration') }}
|
||||
</el-button>
|
||||
|
||||
<el-button :disabled="multipleSelection.length === 0" @click="deleteMulParagraph">
|
||||
{{ $t('common.delete') }}
|
||||
</el-button>
|
||||
<span class="ml-8">
|
||||
{{ $t('views.document.selected') }} {{ multipleSelection.length }}
|
||||
{{ $t('views.document.items') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<ParagraphDialog ref="ParagraphDialogRef" :title="title" @refresh="refresh" />
|
||||
<SelectDocumentDialog ref="SelectDocumentDialogRef" @refresh="refreshMigrateParagraph" />
|
||||
<GenerateRelatedDialog ref="GenerateRelatedDialogRef" @refresh="refresh" />
|
||||
</LayoutContainer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import documentApi from '@/api/document'
|
||||
import paragraphApi from '@/api/knowledge/paragraph'
|
||||
import ParagraphDialog from './component/ParagraphDialog.vue'
|
||||
import SelectDocumentDialog from './component/SelectDocumentDialog.vue'
|
||||
import GenerateRelatedDialog from '@/components/generate-related-dialog/index.vue'
|
||||
import { numberFormat } from '@/utils/utils'
|
||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||
import useStore from '@/stores'
|
||||
import { t } from '@/locales'
|
||||
const { paragraph } = useStore()
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id, documentId }
|
||||
} = route as any
|
||||
|
||||
const SelectDocumentDialogRef = ref()
|
||||
const ParagraphDialogRef = ref()
|
||||
const loading = ref(false)
|
||||
const changeStateloading = ref(false)
|
||||
const documentDetail = ref<any>({})
|
||||
const paragraphDetail = ref<any[]>([])
|
||||
const title = ref('')
|
||||
const search = ref('')
|
||||
const searchType = ref('title')
|
||||
|
||||
// 批量操作
|
||||
const isBatch = ref(false)
|
||||
const multipleSelection = ref<any[]>([])
|
||||
|
||||
const paginationConfig = reactive({
|
||||
current_page: 1,
|
||||
page_size: 30,
|
||||
total: 0
|
||||
})
|
||||
|
||||
function refreshMigrateParagraph() {
|
||||
paragraphDetail.value = paragraphDetail.value.filter(
|
||||
(v) => !multipleSelection.value.includes(v.id)
|
||||
)
|
||||
multipleSelection.value = []
|
||||
MsgSuccess(t('views.document.tip.migrationSuccess'))
|
||||
}
|
||||
|
||||
function openSelectDocumentDialog(row?: any) {
|
||||
if (row) {
|
||||
multipleSelection.value = [row.id]
|
||||
}
|
||||
SelectDocumentDialogRef.value.open(multipleSelection.value)
|
||||
}
|
||||
function deleteMulParagraph() {
|
||||
MsgConfirm(
|
||||
`${t('views.document.delete.confirmTitle1')} ${multipleSelection.value.length} ${t('views.document.delete.confirmTitle2')}`,
|
||||
t('views.paragraph.delete.confirmMessage'),
|
||||
{
|
||||
confirmButtonText: t('common.confirm'),
|
||||
confirmButtonClass: 'danger'
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
paragraphApi
|
||||
.delMulParagraph(id, documentId, multipleSelection.value, changeStateloading)
|
||||
.then(() => {
|
||||
paragraphDetail.value = paragraphDetail.value.filter(
|
||||
(v) => !multipleSelection.value.includes(v.id)
|
||||
)
|
||||
multipleSelection.value = []
|
||||
MsgSuccess(t('views.document.delete.successMessage'))
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
function batchSelectedHandle(bool: boolean) {
|
||||
isBatch.value = bool
|
||||
multipleSelection.value = []
|
||||
}
|
||||
|
||||
function selectHandle(id: string) {
|
||||
if (multipleSelection.value.includes(id)) {
|
||||
multipleSelection.value.splice(multipleSelection.value.indexOf(id), 1)
|
||||
} else {
|
||||
multipleSelection.value.push(id)
|
||||
}
|
||||
}
|
||||
|
||||
function searchHandle() {
|
||||
paginationConfig.current_page = 1
|
||||
paragraphDetail.value = []
|
||||
getParagraphList()
|
||||
}
|
||||
|
||||
function changeState(row: any) {
|
||||
const obj = {
|
||||
is_active: !row.is_active
|
||||
}
|
||||
paragraph
|
||||
.asyncPutParagraph(id, documentId, row.id, obj, changeStateloading)
|
||||
.then((res) => {
|
||||
const index = paragraphDetail.value.findIndex((v) => v.id === row.id)
|
||||
paragraphDetail.value[index].is_active = !paragraphDetail.value[index].is_active
|
||||
return true
|
||||
})
|
||||
.catch(() => {
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
function deleteParagraph(row: any) {
|
||||
MsgConfirm(
|
||||
`${t('views.paragraph.delete.confirmTitle')} ${row.title || '-'} ?`,
|
||||
t('views.paragraph.delete.confirmMessage'),
|
||||
{
|
||||
confirmButtonText: t('common.confirm'),
|
||||
confirmButtonClass: 'danger'
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
paragraph.asyncDelParagraph(id, documentId, row.id, loading).then(() => {
|
||||
const index = paragraphDetail.value.findIndex((v) => v.id === row.id)
|
||||
paragraphDetail.value.splice(index, 1)
|
||||
MsgSuccess(t('common.deleteSuccess'))
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
function addParagraph() {
|
||||
title.value = t('views.paragraph.addParagraph')
|
||||
ParagraphDialogRef.value.open()
|
||||
}
|
||||
function editParagraph(row: any) {
|
||||
title.value = t('views.paragraph.paragraphDetail')
|
||||
ParagraphDialogRef.value.open(row)
|
||||
}
|
||||
|
||||
function getDetail() {
|
||||
loading.value = true
|
||||
documentApi
|
||||
.getDocumentDetail(id, documentId)
|
||||
.then((res) => {
|
||||
documentDetail.value = res.data
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function getParagraphList() {
|
||||
paragraphApi
|
||||
.getParagraph(
|
||||
id,
|
||||
documentId,
|
||||
paginationConfig,
|
||||
search.value && { [searchType.value]: search.value },
|
||||
loading
|
||||
)
|
||||
.then((res) => {
|
||||
paragraphDetail.value = [...paragraphDetail.value, ...res.data.records]
|
||||
paginationConfig.total = res.data.total
|
||||
})
|
||||
}
|
||||
|
||||
function refresh(data: any) {
|
||||
if (data) {
|
||||
const index = paragraphDetail.value.findIndex((v) => v.id === data.id)
|
||||
paragraphDetail.value.splice(index, 1, data)
|
||||
} else {
|
||||
paginationConfig.current_page = 1
|
||||
paragraphDetail.value = []
|
||||
getParagraphList()
|
||||
}
|
||||
}
|
||||
|
||||
const GenerateRelatedDialogRef = ref()
|
||||
function openGenerateDialog(row?: any) {
|
||||
const arr: string[] = []
|
||||
if (row) {
|
||||
arr.push(row.id)
|
||||
} else {
|
||||
multipleSelection.value.map((v) => {
|
||||
if (v) {
|
||||
arr.push(v)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
GenerateRelatedDialogRef.value.open(arr, 'paragraph')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDetail()
|
||||
getParagraphList()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.document-detail {
|
||||
.header-button {
|
||||
position: absolute;
|
||||
right: calc(var(--app-base-px) * 3);
|
||||
}
|
||||
|
||||
.document-detail-height {
|
||||
height: calc(var(--app-main-height) - 75px);
|
||||
}
|
||||
.document-card {
|
||||
height: 210px;
|
||||
background: var(--app-layout-bg-color);
|
||||
border: 1px solid var(--app-layout-bg-color);
|
||||
&.selected {
|
||||
background: #ffffff;
|
||||
&:hover {
|
||||
background: #ffffff;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--el-border-color);
|
||||
}
|
||||
&.disabled {
|
||||
background: var(--app-layout-bg-color);
|
||||
border: 1px solid var(--app-layout-bg-color);
|
||||
:deep(.description) {
|
||||
color: var(--app-border-color-dark);
|
||||
}
|
||||
:deep(.title) {
|
||||
color: var(--app-border-color-dark);
|
||||
}
|
||||
}
|
||||
:deep(.content) {
|
||||
-webkit-line-clamp: 5 !important;
|
||||
height: 110px !important;
|
||||
}
|
||||
.active-button {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&__main {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
.mul-operation {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 16px 24px;
|
||||
box-sizing: border-box;
|
||||
background: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="$t('views.problem.createProblem')"
|
||||
v-model="dialogVisible"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<el-form
|
||||
label-position="top"
|
||||
ref="problemFormRef"
|
||||
:rules="rules"
|
||||
:model="form"
|
||||
require-asterisk-position="right"
|
||||
>
|
||||
<el-form-item :label="$t('views.problem.title')" prop="data">
|
||||
<el-input
|
||||
v-model="form.data"
|
||||
:placeholder="$t('views.problem.tip.placeholder')"
|
||||
:rows="10"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
|
||||
<el-button type="primary" @click="submit(problemFormRef)" :loading="loading">
|
||||
{{ $t('common.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
import useStore from '@/stores'
|
||||
import { t } from '@/locales'
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id }
|
||||
} = route as any
|
||||
const { problem } = useStore()
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
const problemFormRef = ref()
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
const form = ref<any>({
|
||||
data: ''
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
data: [{ required: true, message: t('views.problem.tip.requiredMessage'), trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
form.value = {
|
||||
data: ''
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const open = () => {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
const arr = form.value.data.split('\n').filter(function (item: string) {
|
||||
return item !== ''
|
||||
})
|
||||
problem.asyncPostProblem(id, arr, loading).then((res: any) => {
|
||||
MsgSuccess(t('common.createSuccess'))
|
||||
emit('refresh')
|
||||
dialogVisible.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
<template>
|
||||
<el-drawer v-model="visible" size="60%" @close="closeHandle">
|
||||
<template #header>
|
||||
<h4>{{ $t('views.problem.detailProblem') }}</h4>
|
||||
</template>
|
||||
<div>
|
||||
<el-scrollbar>
|
||||
<div class="p-8">
|
||||
<el-form label-position="top" v-loading="loading" @submit.prevent>
|
||||
<el-form-item :label="$t('views.problem.title')">
|
||||
<ReadWrite
|
||||
@change="editName"
|
||||
:data="currentContent"
|
||||
:showEditIcon="true"
|
||||
:maxlength="256"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('views.problem.relateParagraph.title')">
|
||||
<template v-for="(item, index) in paragraphList" :key="index">
|
||||
<CardBox
|
||||
:title="item.title || '-'"
|
||||
class="paragraph-source-card cursor mb-8"
|
||||
:showIcon="false"
|
||||
@click.stop="editParagraph(item)"
|
||||
>
|
||||
<div class="active-button">
|
||||
<span class="mr-4">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('views.problem.setting.cancelRelated')"
|
||||
placement="top"
|
||||
>
|
||||
<el-button type="primary" text @click.stop="disassociation(item)">
|
||||
<AppIcon iconName="app-quxiaoguanlian"></AppIcon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
<template #description>
|
||||
<el-scrollbar height="80">
|
||||
{{ item.content }}
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="footer-content flex-between">
|
||||
<el-text>
|
||||
<el-icon>
|
||||
<Document />
|
||||
</el-icon>
|
||||
{{ item?.document_name }}
|
||||
</el-text>
|
||||
</div>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<ParagraphDialog
|
||||
ref="ParagraphDialogRef"
|
||||
:title="$t('views.paragraph.editParagraph')"
|
||||
@refresh="refresh"
|
||||
/>
|
||||
<RelateProblemDialog ref="RelateProblemDialogRef" @refresh="refresh" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<div>
|
||||
<el-button @click="relateProblem">{{
|
||||
$t('views.problem.relateParagraph.title')
|
||||
}}</el-button>
|
||||
<el-button @click="pre" :disabled="pre_disable || loading">{{
|
||||
$t('views.log.buttons.prev')
|
||||
}}</el-button>
|
||||
<el-button @click="next" :disabled="next_disable || loading">{{
|
||||
$t('views.log.buttons.next')
|
||||
}}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import problemApi from '@/api/knowledge/problem'
|
||||
import ParagraphDialog from '@/views/paragraph/component/ParagraphDialog.vue'
|
||||
import RelateProblemDialog from './RelateProblemDialog.vue'
|
||||
import { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message'
|
||||
import useStore from '@/stores'
|
||||
import { t } from '@/locales'
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/**
|
||||
* 当前的id
|
||||
*/
|
||||
currentId: string
|
||||
currentContent: string
|
||||
/**
|
||||
* 下一条
|
||||
*/
|
||||
next: () => void
|
||||
/**
|
||||
* 上一条
|
||||
*/
|
||||
pre: () => void
|
||||
|
||||
pre_disable: boolean
|
||||
|
||||
next_disable: boolean
|
||||
}>(),
|
||||
{}
|
||||
)
|
||||
|
||||
const emit = defineEmits(['update:currentId', 'update:currentContent', 'refresh'])
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id }
|
||||
} = route
|
||||
|
||||
const { problem } = useStore()
|
||||
const RelateProblemDialogRef = ref()
|
||||
const ParagraphDialogRef = ref()
|
||||
const loading = ref(false)
|
||||
const visible = ref(false)
|
||||
const paragraphList = ref<any[]>([])
|
||||
|
||||
function disassociation(item: any) {
|
||||
problem
|
||||
.asyncDisassociationProblem(
|
||||
item.dataset_id,
|
||||
item.document_id,
|
||||
item.id,
|
||||
props.currentId,
|
||||
loading
|
||||
)
|
||||
.then(() => {
|
||||
getRecord()
|
||||
})
|
||||
}
|
||||
|
||||
function relateProblem() {
|
||||
RelateProblemDialogRef.value.open([props.currentId])
|
||||
}
|
||||
|
||||
function editParagraph(row: any) {
|
||||
ParagraphDialogRef.value.open(row)
|
||||
}
|
||||
|
||||
function editName(val: string) {
|
||||
if (val) {
|
||||
const obj = {
|
||||
content: val
|
||||
}
|
||||
problemApi.putProblems(id as string, props.currentId, obj, loading).then(() => {
|
||||
emit('update:currentContent', val)
|
||||
MsgSuccess(t('common.modifySuccess'))
|
||||
})
|
||||
} else {
|
||||
MsgError(t('views.problem.tip.errorMessage'))
|
||||
}
|
||||
}
|
||||
|
||||
function closeHandle() {
|
||||
paragraphList.value = []
|
||||
}
|
||||
|
||||
function getRecord() {
|
||||
if (props.currentId && visible.value) {
|
||||
problemApi.getDetailProblems(id as string, props.currentId, loading).then((res) => {
|
||||
paragraphList.value = res.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
getRecord()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.currentId,
|
||||
() => {
|
||||
paragraphList.value = []
|
||||
getRecord()
|
||||
}
|
||||
)
|
||||
|
||||
watch(visible, (bool) => {
|
||||
if (!bool) {
|
||||
emit('update:currentId', '')
|
||||
emit('update:currentContent', '')
|
||||
emit('refresh')
|
||||
}
|
||||
})
|
||||
|
||||
const open = () => {
|
||||
getRecord()
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
|
|
@ -0,0 +1,301 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="$t('views.problem.relateParagraph.title')"
|
||||
v-model="dialogVisible"
|
||||
width="80%"
|
||||
class="paragraph-dialog"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<el-row v-loading="loading">
|
||||
<el-col :span="6">
|
||||
<el-scrollbar height="500" wrap-class="paragraph-scrollbar">
|
||||
<div class="bold title align-center p-24 pb-0">
|
||||
{{ $t('views.problem.relateParagraph.selectDocument') }}
|
||||
</div>
|
||||
<div class="p-8" style="padding-bottom: 8px">
|
||||
<el-input
|
||||
v-model="filterDoc"
|
||||
:placeholder="$t('views.problem.relateParagraph.placeholder')"
|
||||
prefix-icon="Search"
|
||||
clearable
|
||||
/>
|
||||
<common-list
|
||||
:data="documentList"
|
||||
class="mt-8"
|
||||
@click="clickDocumentHandle"
|
||||
:default-active="currentDocument"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span class="flex lighter align-center">
|
||||
<auto-tooltip :content="row.name">
|
||||
{{ row.name }}
|
||||
</auto-tooltip>
|
||||
<el-badge
|
||||
:value="associationCount(row.id)"
|
||||
type="primary"
|
||||
v-if="associationCount(row.id)"
|
||||
class="paragraph-badge ml-4"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
</common-list>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-col>
|
||||
<el-col :span="18" class="border-l">
|
||||
<el-scrollbar height="500" wrap-class="paragraph-scrollbar">
|
||||
<div class="p-24" style="padding-bottom: 8px; padding-top: 16px">
|
||||
<div class="flex-between mb-16">
|
||||
<div class="bold title align-center">
|
||||
{{ $t('components.selectParagraph.title') }}
|
||||
<el-text>
|
||||
({{ $t('views.problem.relateParagraph.selectedParagraph') }}:{{
|
||||
associationCount(currentDocument)
|
||||
}}
|
||||
{{ $t('views.problem.relateParagraph.count') }})
|
||||
</el-text>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="search"
|
||||
:placeholder="$t('common.search')"
|
||||
class="input-with-select"
|
||||
style="width: 260px"
|
||||
@change="searchHandle"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-select v-model="searchType" placeholder="Select" style="width: 80px">
|
||||
<el-option :label="$t('common.title')" value="title" />
|
||||
<el-option :label="$t('common.content')" value="content" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<el-empty v-if="paragraphList.length == 0" :description="$t('common.noData')" />
|
||||
|
||||
<InfiniteScroll
|
||||
v-else
|
||||
:size="paragraphList.length"
|
||||
:total="paginationConfig.total"
|
||||
:page_size="paginationConfig.page_size"
|
||||
v-model:current_page="paginationConfig.current_page"
|
||||
@load="getParagraphList"
|
||||
:loading="loading"
|
||||
>
|
||||
<template v-for="(item, index) in paragraphList" :key="index">
|
||||
<CardBox
|
||||
shadow="hover"
|
||||
:title="item.title || '-'"
|
||||
:description="item.content"
|
||||
class="paragraph-card cursor mb-16"
|
||||
:class="isAssociation(item.id) ? 'selected' : ''"
|
||||
:showIcon="false"
|
||||
@click="associationClick(item)"
|
||||
>
|
||||
</CardBox>
|
||||
</template>
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer v-if="isMul">
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false"> {{ $t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="mulAssociation"> {{ $t('common.confirm') }} </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, reactive } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import problemApi from '@/api/knowledge/problem'
|
||||
import paragraphApi from '@/api/knowledge/paragraph'
|
||||
import useStore from '@/stores'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
import { t } from '@/locales'
|
||||
const { problem, document } = useStore()
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id } // datasetId
|
||||
} = route as any
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
||||
const loading = ref(false)
|
||||
const documentList = ref<any[]>([])
|
||||
const cloneDocumentList = ref<any[]>([])
|
||||
const paragraphList = ref<any[]>([])
|
||||
const currentProblemId = ref<String>('')
|
||||
const currentMulProblemId = ref<string[]>([])
|
||||
|
||||
// 回显
|
||||
const associationParagraph = ref<any[]>([])
|
||||
|
||||
const currentDocument = ref<String>('')
|
||||
const search = ref('')
|
||||
const searchType = ref('title')
|
||||
const filterDoc = ref('')
|
||||
// 批量
|
||||
const isMul = ref(false)
|
||||
|
||||
const paginationConfig = reactive({
|
||||
current_page: 1,
|
||||
page_size: 50,
|
||||
total: 0
|
||||
})
|
||||
|
||||
function mulAssociation() {
|
||||
const data = {
|
||||
problem_id_list: currentMulProblemId.value,
|
||||
paragraph_list: associationParagraph.value.map((item) => ({
|
||||
paragraph_id: item.id,
|
||||
document_id: item.document_id
|
||||
}))
|
||||
}
|
||||
problemApi.postMulAssociationProblem(id, data, loading).then(() => {
|
||||
MsgSuccess(t('views.problem.tip.relatedSuccess'))
|
||||
dialogVisible.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function associationClick(item: any) {
|
||||
if (isMul.value) {
|
||||
if (isAssociation(item.id)) {
|
||||
associationParagraph.value.splice(associationParagraph.value.indexOf(item.id), 1)
|
||||
} else {
|
||||
associationParagraph.value.push(item)
|
||||
}
|
||||
} else {
|
||||
if (isAssociation(item.id)) {
|
||||
problem
|
||||
.asyncDisassociationProblem(
|
||||
id,
|
||||
item.document_id,
|
||||
item.id,
|
||||
currentProblemId.value as string,
|
||||
loading
|
||||
)
|
||||
.then(() => {
|
||||
getRecord(currentProblemId.value)
|
||||
})
|
||||
} else {
|
||||
problem
|
||||
.asyncAssociationProblem(
|
||||
id,
|
||||
item.document_id,
|
||||
item.id,
|
||||
currentProblemId.value as string,
|
||||
loading
|
||||
)
|
||||
.then(() => {
|
||||
getRecord(currentProblemId.value)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function searchHandle() {
|
||||
paginationConfig.current_page = 1
|
||||
paragraphList.value = []
|
||||
currentDocument.value && getParagraphList(currentDocument.value)
|
||||
}
|
||||
|
||||
function clickDocumentHandle(item: any) {
|
||||
paginationConfig.current_page = 1
|
||||
paragraphList.value = []
|
||||
currentDocument.value = item.id
|
||||
getParagraphList(item.id)
|
||||
}
|
||||
|
||||
function getDocument() {
|
||||
document.asyncGetAllDocument(id, loading).then((res: any) => {
|
||||
cloneDocumentList.value = res.data
|
||||
documentList.value = res.data
|
||||
currentDocument.value = cloneDocumentList.value?.length > 0 ? cloneDocumentList.value[0].id : ''
|
||||
currentDocument.value && getParagraphList(currentDocument.value)
|
||||
})
|
||||
}
|
||||
|
||||
function getParagraphList(documentId: String) {
|
||||
paragraphApi
|
||||
.getParagraph(
|
||||
id,
|
||||
(documentId || currentDocument.value) as string,
|
||||
paginationConfig,
|
||||
search.value && { [searchType.value]: search.value },
|
||||
loading
|
||||
)
|
||||
.then((res) => {
|
||||
paragraphList.value = [...paragraphList.value, ...res.data.records]
|
||||
paginationConfig.total = res.data.total
|
||||
})
|
||||
}
|
||||
|
||||
// 已关联分段
|
||||
function getRecord(problemId: String) {
|
||||
problemApi.getDetailProblems(id as string, problemId as string, loading).then((res) => {
|
||||
associationParagraph.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
function associationCount(documentId: String) {
|
||||
return associationParagraph.value.filter((item) => item.document_id === documentId).length
|
||||
}
|
||||
function isAssociation(paragraphId: String) {
|
||||
return associationParagraph.value.some((option) => option.id === paragraphId)
|
||||
}
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
documentList.value = []
|
||||
cloneDocumentList.value = []
|
||||
paragraphList.value = []
|
||||
associationParagraph.value = []
|
||||
isMul.value = false
|
||||
|
||||
currentDocument.value = ''
|
||||
search.value = ''
|
||||
searchType.value = 'title'
|
||||
emit('refresh')
|
||||
}
|
||||
})
|
||||
|
||||
watch(filterDoc, (val) => {
|
||||
paragraphList.value = []
|
||||
documentList.value = val
|
||||
? cloneDocumentList.value.filter((item) => item.name.includes(val))
|
||||
: cloneDocumentList.value
|
||||
currentDocument.value = documentList.value?.length > 0 ? documentList.value[0].id : ''
|
||||
})
|
||||
|
||||
const open = (problemId: any) => {
|
||||
getDocument()
|
||||
if (problemId.length == 1) {
|
||||
currentProblemId.value = problemId[0]
|
||||
getRecord(problemId)
|
||||
} else if (problemId.length > 1) {
|
||||
currentMulProblemId.value = problemId
|
||||
isMul.value = true
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.paragraph-card {
|
||||
position: relative;
|
||||
}
|
||||
.paragraph-badge {
|
||||
.el-badge__content {
|
||||
height: auto;
|
||||
display: table;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,362 @@
|
|||
<template>
|
||||
<LayoutContainer :header="$t('views.problem.title')">
|
||||
<div class="main-calc-height">
|
||||
<div class="p-24">
|
||||
<div class="flex-between">
|
||||
<div>
|
||||
<el-button type="primary" @click="createProblem">{{$t('views.problem.createProblem')}}</el-button>
|
||||
<el-button @click="relateProblem()" :disabled="multipleSelection.length === 0"
|
||||
>{{$t('views.problem.relateParagraph.title')}}</el-button
|
||||
>
|
||||
<el-button @click="deleteMulDocument" :disabled="multipleSelection.length === 0"
|
||||
>{{$t('views.problem.setting.batchDelete')}}</el-button
|
||||
>
|
||||
</div>
|
||||
|
||||
<el-input
|
||||
v-model="filterText"
|
||||
:placeholder="$t('views.problem.searchBar.placeholder')"
|
||||
prefix-icon="Search"
|
||||
class="w-240"
|
||||
@change="getList"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<app-table
|
||||
ref="multipleTableRef"
|
||||
class="mt-16"
|
||||
:data="problemData"
|
||||
:pagination-config="paginationConfig"
|
||||
quick-create
|
||||
:quickCreateName="$t('views.problem.quickCreateName')"
|
||||
:quickCreatePlaceholder="$t('views.problem.quickCreateProblem')"
|
||||
:quickCreateMaxlength="256"
|
||||
@sizeChange="handleSizeChange"
|
||||
@changePage="getList"
|
||||
@cell-mouse-enter="cellMouseEnter"
|
||||
@cell-mouse-leave="cellMouseLeave"
|
||||
@creatQuick="creatQuickHandle"
|
||||
@row-click="rowClickHandle"
|
||||
@selection-change="handleSelectionChange"
|
||||
:row-class-name="setRowClass"
|
||||
v-loading="loading"
|
||||
:row-key="(row: any) => row.id"
|
||||
>
|
||||
<el-table-column type="selection" width="55" :reserve-selection="true" />
|
||||
<el-table-column prop="content" :label="$t('views.problem.title')" min-width="280">
|
||||
<template #default="{ row }">
|
||||
<ReadWrite
|
||||
@change="editName($event, row.id)"
|
||||
:data="row.content"
|
||||
:showEditIcon="row.id === currentMouseId"
|
||||
:maxlength="256"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="paragraph_count" :label="$t('views.problem.table.paragraph_count')" align="right" min-width="100">
|
||||
<template #default="{ row }">
|
||||
<el-link type="primary" @click.stop="rowClickHandle(row)" v-if="row.paragraph_count">
|
||||
{{ row.paragraph_count }}
|
||||
</el-link>
|
||||
<span v-else>
|
||||
{{ row.paragraph_count }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" :label="$t('common.createTime')" width="170">
|
||||
<template #default="{ row }">
|
||||
{{ datetimeFormat(row.create_time) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="update_time" :label="$t('views.problem.table.updateTime')" width="170">
|
||||
<template #default="{ row }">
|
||||
{{ datetimeFormat(row.update_time) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('common.operation')" align="left" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div>
|
||||
<span class="mr-4">
|
||||
<el-tooltip effect="dark" :content="$t('views.problem.relateParagraph.title')" placement="top">
|
||||
<el-button type="primary" text @click.stop="relateProblem(row)">
|
||||
<el-icon><Connection /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<span>
|
||||
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
|
||||
<el-button type="primary" text @click.stop="deleteProblem(row)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</app-table>
|
||||
</div>
|
||||
</div>
|
||||
<CreateProblemDialog ref="CreateProblemDialogRef" @refresh="refresh" />
|
||||
<DetailProblemDrawer
|
||||
:next="nextChatRecord"
|
||||
:pre="preChatRecord"
|
||||
ref="DetailProblemRef"
|
||||
v-model:currentId="currentClickId"
|
||||
v-model:currentContent="currentContent"
|
||||
:pre_disable="pre_disable"
|
||||
:next_disable="next_disable"
|
||||
@refresh="refreshRelate"
|
||||
/>
|
||||
<RelateProblemDialog ref="RelateProblemDialogRef" @refresh="refreshRelate" />
|
||||
</LayoutContainer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, reactive, onBeforeUnmount, computed } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { ElTable } from 'element-plus'
|
||||
import problemApi from '@/api/knowledge/problem'
|
||||
import CreateProblemDialog from './component/CreateProblemDialog.vue'
|
||||
import DetailProblemDrawer from './component/DetailProblemDrawer.vue'
|
||||
import RelateProblemDialog from './component/RelateProblemDialog.vue'
|
||||
import { datetimeFormat } from '@/utils/time'
|
||||
import { MsgSuccess, MsgConfirm, MsgError } from '@/utils/message'
|
||||
import type { Dict } from '@/api/type/common'
|
||||
import useStore from '@/stores'
|
||||
import { t } from '@/locales'
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id } // 知识库id
|
||||
} = route as any
|
||||
|
||||
const { problem } = useStore()
|
||||
|
||||
const RelateProblemDialogRef = ref()
|
||||
const DetailProblemRef = ref()
|
||||
const CreateProblemDialogRef = ref()
|
||||
const loading = ref(false)
|
||||
|
||||
// 当前需要修改问题的id
|
||||
const currentMouseId = ref('')
|
||||
// 当前点击打开drawer的id
|
||||
const currentClickId = ref('')
|
||||
const currentContent = ref('')
|
||||
|
||||
const paginationConfig = reactive({
|
||||
current_page: 1,
|
||||
page_size: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
const filterText = ref('')
|
||||
const problemData = ref<any[]>([])
|
||||
const problemIndexMap = computed<Dict<number>>(() => {
|
||||
return problemData.value
|
||||
.map((row, index) => ({
|
||||
[row.id]: index
|
||||
}))
|
||||
.reduce((pre, next) => ({ ...pre, ...next }), {})
|
||||
})
|
||||
|
||||
const multipleTableRef = ref<InstanceType<typeof ElTable>>()
|
||||
const multipleSelection = ref<any[]>([])
|
||||
|
||||
function relateProblem(row?: any) {
|
||||
const arr: string[] = []
|
||||
if (row) {
|
||||
arr.push(row.id)
|
||||
} else {
|
||||
multipleSelection.value.map((v) => {
|
||||
if (v) {
|
||||
arr.push(v.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RelateProblemDialogRef.value.open(arr)
|
||||
}
|
||||
|
||||
function createProblem() {
|
||||
CreateProblemDialogRef.value.open()
|
||||
}
|
||||
|
||||
const handleSelectionChange = (val: any[]) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
|
||||
/*
|
||||
快速创建空白文档
|
||||
*/
|
||||
function creatQuickHandle(val: string) {
|
||||
loading.value = true
|
||||
const obj = [val]
|
||||
problem
|
||||
.asyncPostProblem(id, obj)
|
||||
.then((res) => {
|
||||
getList()
|
||||
MsgSuccess(t('common.createSuccess'))
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function deleteMulDocument() {
|
||||
const arr: string[] = []
|
||||
multipleSelection.value.map((v) => {
|
||||
if (v) {
|
||||
arr.push(v.id)
|
||||
}
|
||||
})
|
||||
problemApi.delMulProblem(id, arr, loading).then(() => {
|
||||
MsgSuccess(t('views.document.delete.successMessage'))
|
||||
multipleTableRef.value?.clearSelection()
|
||||
getList()
|
||||
})
|
||||
}
|
||||
|
||||
function deleteProblem(row: any) {
|
||||
MsgConfirm(
|
||||
`${t('views.problem.delete.confirmTitle')} ${row.content} ?`,
|
||||
`${t('views.problem.delete.confirmMessage1')} ${row.paragraph_count} ${t('views.problem.delete.confirmMessage2')}`,
|
||||
{
|
||||
confirmButtonText: t('common.confirm'),
|
||||
confirmButtonClass: 'danger'
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
problemApi.delProblems(id, row.id, loading).then(() => {
|
||||
MsgSuccess(t('common.deleteSuccess'))
|
||||
getList()
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
function editName(val: string, problemId: string) {
|
||||
if (val) {
|
||||
const obj = {
|
||||
content: val
|
||||
}
|
||||
problemApi.putProblems(id, problemId, obj, loading).then(() => {
|
||||
getList()
|
||||
MsgSuccess(t('common.modifySuccess'))
|
||||
})
|
||||
} else {
|
||||
MsgError(t('views.problem.tip.errorMessage'))
|
||||
}
|
||||
}
|
||||
|
||||
function cellMouseEnter(row: any) {
|
||||
currentMouseId.value = row.id
|
||||
}
|
||||
function cellMouseLeave() {
|
||||
currentMouseId.value = ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 下一页
|
||||
*/
|
||||
const nextChatRecord = () => {
|
||||
let index = problemIndexMap.value[currentClickId.value] + 1
|
||||
if (index >= problemData.value.length) {
|
||||
if (
|
||||
index + (paginationConfig.current_page - 1) * paginationConfig.page_size >=
|
||||
paginationConfig.total - 1
|
||||
) {
|
||||
return
|
||||
}
|
||||
paginationConfig.current_page = paginationConfig.current_page + 1
|
||||
getList().then(() => {
|
||||
index = 0
|
||||
currentClickId.value = problemData.value[index].id
|
||||
currentContent.value = problemData.value[index].content
|
||||
})
|
||||
} else {
|
||||
currentClickId.value = problemData.value[index].id
|
||||
currentContent.value = problemData.value[index].content
|
||||
}
|
||||
}
|
||||
const pre_disable = computed(() => {
|
||||
let index = problemIndexMap.value[currentClickId.value] - 1
|
||||
return index < 0 && paginationConfig.current_page <= 1
|
||||
})
|
||||
|
||||
const next_disable = computed(() => {
|
||||
let index = problemIndexMap.value[currentClickId.value] + 1
|
||||
return (
|
||||
index >= problemData.value.length &&
|
||||
index + (paginationConfig.current_page - 1) * paginationConfig.page_size >=
|
||||
paginationConfig.total - 1
|
||||
)
|
||||
})
|
||||
/**
|
||||
* 上一页
|
||||
*/
|
||||
const preChatRecord = () => {
|
||||
let index = problemIndexMap.value[currentClickId.value] - 1
|
||||
|
||||
if (index < 0) {
|
||||
if (paginationConfig.current_page <= 1) {
|
||||
return
|
||||
}
|
||||
paginationConfig.current_page = paginationConfig.current_page - 1
|
||||
getList().then((ok) => {
|
||||
index = paginationConfig.page_size - 1
|
||||
currentClickId.value = problemData.value[index].id
|
||||
currentContent.value = problemData.value[index].content
|
||||
})
|
||||
} else {
|
||||
currentClickId.value = problemData.value[index].id
|
||||
currentContent.value = problemData.value[index].content
|
||||
}
|
||||
}
|
||||
|
||||
function rowClickHandle(row: any, column?: any) {
|
||||
if (column && column.type === 'selection') {
|
||||
return
|
||||
}
|
||||
if (row.paragraph_count) {
|
||||
currentClickId.value = row.id
|
||||
currentContent.value = row.content
|
||||
DetailProblemRef.value.open()
|
||||
}
|
||||
}
|
||||
|
||||
const setRowClass = ({ row }: any) => {
|
||||
return currentClickId.value === row?.id ? 'highlight' : ''
|
||||
}
|
||||
|
||||
function handleSizeChange() {
|
||||
paginationConfig.current_page = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
function getList() {
|
||||
return problem
|
||||
.asyncGetProblem(
|
||||
id as string,
|
||||
paginationConfig,
|
||||
filterText.value && { content: filterText.value },
|
||||
loading
|
||||
)
|
||||
.then((res: any) => {
|
||||
problemData.value = res.data.records
|
||||
paginationConfig.total = res.data.total
|
||||
})
|
||||
}
|
||||
function refreshRelate() {
|
||||
getList()
|
||||
multipleTableRef.value?.clearSelection()
|
||||
}
|
||||
function refresh() {
|
||||
paginationConfig.current_page = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
Loading…
Reference in New Issue