parent
c67f4633a1
commit
633c005905
@ -218,7 +218,7 @@ class ParagraphSerializers(ApiMixin, serializers.Serializer):
|
|||||||
def association(self, with_valid=True, with_embedding=True):
|
def association(self, with_valid=True, with_embedding=True):
|
||||||
if with_valid:
|
if with_valid:
|
||||||
self.is_valid(raise_exception=True)
|
self.is_valid(raise_exception=True)
|
||||||
problem = QuerySet(Problem).filter(id=self.data.get("problem_id"))
|
problem = QuerySet(Problem).filter(id=self.data.get("problem_id")).first()
|
||||||
problem_paragraph_mapping = ProblemParagraphMapping(id=uuid.uuid1(),
|
problem_paragraph_mapping = ProblemParagraphMapping(id=uuid.uuid1(),
|
||||||
document_id=self.data.get('document_id'),
|
document_id=self.data.get('document_id'),
|
||||||
paragraph_id=self.data.get('paragraph_id'),
|
paragraph_id=self.data.get('paragraph_id'),
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Dict
|
from typing import Dict, List
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
@ -83,6 +83,7 @@ class ProblemSerializers(ApiMixin, serializers.Serializer):
|
|||||||
**{'dataset_id': self.data.get('dataset_id')})
|
**{'dataset_id': self.data.get('dataset_id')})
|
||||||
if 'content' in self.data:
|
if 'content' in self.data:
|
||||||
query_set = query_set.filter(**{'content__contains': self.data.get('content')})
|
query_set = query_set.filter(**{'content__contains': self.data.get('content')})
|
||||||
|
query_set = query_set.order_by("-create_time")
|
||||||
return query_set
|
return query_set
|
||||||
|
|
||||||
def list(self):
|
def list(self):
|
||||||
@ -95,6 +96,22 @@ class ProblemSerializers(ApiMixin, serializers.Serializer):
|
|||||||
return native_page_search(current_page, page_size, query_set, select_string=get_file_content(
|
return native_page_search(current_page, page_size, query_set, select_string=get_file_content(
|
||||||
os.path.join(PROJECT_DIR, "apps", "dataset", 'sql', 'list_problem.sql')))
|
os.path.join(PROJECT_DIR, "apps", "dataset", 'sql', 'list_problem.sql')))
|
||||||
|
|
||||||
|
class BatchOperate(serializers.Serializer):
|
||||||
|
dataset_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("知识库id"))
|
||||||
|
|
||||||
|
def delete(self, problem_id_list: List, with_valid=True):
|
||||||
|
if with_valid:
|
||||||
|
self.is_valid(raise_exception=True)
|
||||||
|
dataset_id = self.data.get('dataset_id')
|
||||||
|
problem_paragraph_mapping_list = QuerySet(ProblemParagraphMapping).filter(
|
||||||
|
dataset_id=dataset_id,
|
||||||
|
problem_id__in=problem_id_list)
|
||||||
|
source_ids = [row.id for row in problem_paragraph_mapping_list]
|
||||||
|
problem_paragraph_mapping_list.delete()
|
||||||
|
QuerySet(Problem).filter(id__in=problem_id_list).delete()
|
||||||
|
ListenerManagement.delete_embedding_by_source_ids_signal.send(source_ids)
|
||||||
|
return True
|
||||||
|
|
||||||
class Operate(serializers.Serializer):
|
class Operate(serializers.Serializer):
|
||||||
dataset_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("知识库id"))
|
dataset_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("知识库id"))
|
||||||
|
|
||||||
@ -105,6 +122,8 @@ class ProblemSerializers(ApiMixin, serializers.Serializer):
|
|||||||
self.is_valid(raise_exception=True)
|
self.is_valid(raise_exception=True)
|
||||||
problem_paragraph_mapping = QuerySet(ProblemParagraphMapping).filter(dataset_id=self.data.get("dataset_id"),
|
problem_paragraph_mapping = QuerySet(ProblemParagraphMapping).filter(dataset_id=self.data.get("dataset_id"),
|
||||||
problem_id=self.data.get("problem_id"))
|
problem_id=self.data.get("problem_id"))
|
||||||
|
if problem_paragraph_mapping is None or len(problem_paragraph_mapping)==0:
|
||||||
|
return []
|
||||||
return native_search(
|
return native_search(
|
||||||
QuerySet(Paragraph).filter(id__in=[row.paragraph_id for row in problem_paragraph_mapping]),
|
QuerySet(Paragraph).filter(id__in=[row.paragraph_id for row in problem_paragraph_mapping]),
|
||||||
select_string=get_file_content(
|
select_string=get_file_content(
|
||||||
@ -123,6 +142,7 @@ class ProblemSerializers(ApiMixin, serializers.Serializer):
|
|||||||
dataset_id=self.data.get('dataset_id'),
|
dataset_id=self.data.get('dataset_id'),
|
||||||
problem_id=self.data.get('problem_id'))
|
problem_id=self.data.get('problem_id'))
|
||||||
source_ids = [row.id for row in problem_paragraph_mapping_list]
|
source_ids = [row.id for row in problem_paragraph_mapping_list]
|
||||||
|
problem_paragraph_mapping_list.delete()
|
||||||
QuerySet(Problem).filter(id=self.data.get('problem_id')).delete()
|
QuerySet(Problem).filter(id=self.data.get('problem_id')).delete()
|
||||||
ListenerManagement.delete_embedding_by_source_ids_signal.send(source_ids)
|
ListenerManagement.delete_embedding_by_source_ids_signal.send(source_ids)
|
||||||
return True
|
return True
|
||||||
|
|||||||
@ -36,6 +36,25 @@ class ProblemApi(ApiMixin):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class BatchOperate(ApiMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_request_params_api():
|
||||||
|
return [openapi.Parameter(name='dataset_id',
|
||||||
|
in_=openapi.IN_PATH,
|
||||||
|
type=openapi.TYPE_STRING,
|
||||||
|
required=True,
|
||||||
|
description='知识库id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_request_body_api():
|
||||||
|
return openapi.Schema(
|
||||||
|
title="问题id列表",
|
||||||
|
description="问题id列表",
|
||||||
|
type=openapi.TYPE_ARRAY,
|
||||||
|
items=openapi.Schema(type=openapi.TYPE_STRING)
|
||||||
|
)
|
||||||
|
|
||||||
class Operate(ApiMixin):
|
class Operate(ApiMixin):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_request_params_api():
|
def get_request_params_api():
|
||||||
|
|||||||
@ -36,6 +36,7 @@ urlpatterns = [
|
|||||||
'dataset/<str:dataset_id>/document/<str:document_id>/paragraph/<str:paragraph_id>/problem/<str:problem_id>/association',
|
'dataset/<str:dataset_id>/document/<str:document_id>/paragraph/<str:paragraph_id>/problem/<str:problem_id>/association',
|
||||||
views.Paragraph.Problem.Association.as_view()),
|
views.Paragraph.Problem.Association.as_view()),
|
||||||
path('dataset/<str:dataset_id>/problem', views.Problem.as_view()),
|
path('dataset/<str:dataset_id>/problem', views.Problem.as_view()),
|
||||||
|
path('dataset/<str:dataset_id>/problem/_batch', views.Problem.OperateBatch.as_view()),
|
||||||
path('dataset/<str:dataset_id>/problem/<int:current_page>/<int:page_size>', views.Problem.Page.as_view()),
|
path('dataset/<str:dataset_id>/problem/<int:current_page>/<int:page_size>', views.Problem.Page.as_view()),
|
||||||
path('dataset/<str:dataset_id>/problem/<str:problem_id>', views.Problem.Operate.as_view()),
|
path('dataset/<str:dataset_id>/problem/<str:problem_id>', views.Problem.Operate.as_view()),
|
||||||
path('dataset/<str:dataset_id>/problem/<str:problem_id>/paragraph', views.Problem.Paragraph.as_view()),
|
path('dataset/<str:dataset_id>/problem/<str:problem_id>/paragraph', views.Problem.Paragraph.as_view()),
|
||||||
|
|||||||
@ -51,7 +51,7 @@ class Problem(APIView):
|
|||||||
def post(self, request: Request, dataset_id: str):
|
def post(self, request: Request, dataset_id: str):
|
||||||
return result.success(
|
return result.success(
|
||||||
ProblemSerializers.Create(
|
ProblemSerializers.Create(
|
||||||
data={'dataset_id': dataset_id, 'problem_list': request.query_params.get('problem_list')}).save())
|
data={'dataset_id': dataset_id, 'problem_list': request.data}).batch())
|
||||||
|
|
||||||
class Paragraph(APIView):
|
class Paragraph(APIView):
|
||||||
authentication_classes = [TokenAuth]
|
authentication_classes = [TokenAuth]
|
||||||
@ -70,6 +70,24 @@ class Problem(APIView):
|
|||||||
data={**query_params_to_single_dict(request.query_params), 'dataset_id': dataset_id,
|
data={**query_params_to_single_dict(request.query_params), 'dataset_id': dataset_id,
|
||||||
'problem_id': problem_id}).list_paragraph())
|
'problem_id': problem_id}).list_paragraph())
|
||||||
|
|
||||||
|
class OperateBatch(APIView):
|
||||||
|
authentication_classes = [TokenAuth]
|
||||||
|
|
||||||
|
@action(methods=['DELETE'], detail=False)
|
||||||
|
@swagger_auto_schema(operation_summary="批量删除问题",
|
||||||
|
operation_id="批量删除问题",
|
||||||
|
request_body=
|
||||||
|
ProblemApi.BatchOperate.get_request_body_api(),
|
||||||
|
manual_parameters=ProblemApi.BatchOperate.get_request_params_api(),
|
||||||
|
responses=result.get_default_response(),
|
||||||
|
tags=["知识库/文档/段落/问题"])
|
||||||
|
@has_permissions(
|
||||||
|
lambda r, k: Permission(group=Group.DATASET, operate=Operate.MANAGE,
|
||||||
|
dynamic_tag=k.get('dataset_id')))
|
||||||
|
def delete(self, request: Request, dataset_id: str):
|
||||||
|
return result.success(
|
||||||
|
ProblemSerializers.BatchOperate(data={'dataset_id': dataset_id}).delete(request.data))
|
||||||
|
|
||||||
class Operate(APIView):
|
class Operate(APIView):
|
||||||
authentication_classes = [TokenAuth]
|
authentication_classes = [TokenAuth]
|
||||||
|
|
||||||
|
|||||||
@ -129,25 +129,55 @@ const postProblem: (
|
|||||||
dataset_id: string,
|
dataset_id: string,
|
||||||
document_id: string,
|
document_id: string,
|
||||||
paragraph_id: string,
|
paragraph_id: string,
|
||||||
data: any
|
data: any,
|
||||||
) => Promise<Result<any>> = (dataset_id, document_id, paragraph_id, data: any) => {
|
loading?: Ref<boolean>
|
||||||
|
) => Promise<Result<any>> = (dataset_id, document_id, paragraph_id, data: any, loading) => {
|
||||||
return post(
|
return post(
|
||||||
`${prefix}/${dataset_id}/document/${document_id}/paragraph/${paragraph_id}/problem`,
|
`${prefix}/${dataset_id}/document/${document_id}/paragraph/${paragraph_id}/problem`,
|
||||||
data
|
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
|
* @param 参数 dataset_id, document_id, paragraph_id,problem_id
|
||||||
*/
|
*/
|
||||||
const delProblem: (
|
const disassociationProblem: (
|
||||||
dataset_id: string,
|
dataset_id: string,
|
||||||
document_id: string,
|
document_id: string,
|
||||||
paragraph_id: string,
|
paragraph_id: string,
|
||||||
problem_id: string
|
problem_id: string,
|
||||||
) => Promise<Result<boolean>> = (dataset_id, document_id, paragraph_id, problem_id) => {
|
loading?: Ref<boolean>
|
||||||
|
) => Promise<Result<boolean>> = (dataset_id, document_id, paragraph_id, problem_id, loading) => {
|
||||||
return put(
|
return put(
|
||||||
`${prefix}/${dataset_id}/document/${document_id}/paragraph/${paragraph_id}/problem/${problem_id}/un_association`
|
`${prefix}/${dataset_id}/document/${document_id}/paragraph/${paragraph_id}/problem/${problem_id}/un_association`,
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
loading
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,5 +188,6 @@ export default {
|
|||||||
postParagraph,
|
postParagraph,
|
||||||
getProblem,
|
getProblem,
|
||||||
postProblem,
|
postProblem,
|
||||||
delProblem
|
disassociationProblem,
|
||||||
|
associationProblem
|
||||||
}
|
}
|
||||||
|
|||||||
107
ui/src/api/problem.ts
Normal file
107
ui/src/api/problem.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getProblems,
|
||||||
|
postProblems,
|
||||||
|
delProblems,
|
||||||
|
putProblems,
|
||||||
|
getDetailProblems,
|
||||||
|
delMulProblem
|
||||||
|
}
|
||||||
@ -102,13 +102,4 @@ defineExpose({ open })
|
|||||||
height: calc(100vh - 260px);
|
height: calc(100vh - 260px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.paragraph-source-card {
|
|
||||||
height: 210px;
|
|
||||||
width: 100%;
|
|
||||||
.active-button {
|
|
||||||
position: absolute;
|
|
||||||
right: 16px;
|
|
||||||
top: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -6,9 +6,11 @@
|
|||||||
<el-input
|
<el-input
|
||||||
ref="quickInputRef"
|
ref="quickInputRef"
|
||||||
v-model="inputValue"
|
v-model="inputValue"
|
||||||
placeholder="请输入文档名称"
|
:placeholder="`请输入${quickCreateName}`"
|
||||||
class="w-500 mr-12"
|
class="w-500 mr-12"
|
||||||
autofocus
|
autofocus
|
||||||
|
:maxlength="quickCreateMaxlength"
|
||||||
|
:show-word-limit="quickCreateMaxlength ? true : false"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<el-button type="primary" @click="submitHandle" :disabled="loading">创建</el-button>
|
<el-button type="primary" @click="submitHandle" :disabled="loading">创建</el-button>
|
||||||
@ -17,7 +19,7 @@
|
|||||||
<div v-else @click="quickCreateHandel" class="w-full">
|
<div v-else @click="quickCreateHandel" class="w-full">
|
||||||
<el-button type="primary" link class="quich-button">
|
<el-button type="primary" link class="quich-button">
|
||||||
<el-icon><Plus /></el-icon>
|
<el-icon><Plus /></el-icon>
|
||||||
<span class="ml-4">快速创建空白文档</span>
|
<span class="ml-4">{{ quickCreatePlaceholder }}</span>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -51,6 +53,18 @@ const props = defineProps({
|
|||||||
quickCreate: {
|
quickCreate: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
quickCreateName: {
|
||||||
|
type: String,
|
||||||
|
default: '文档名称'
|
||||||
|
},
|
||||||
|
quickCreatePlaceholder: {
|
||||||
|
type: String,
|
||||||
|
default: '快速创建空白文档'
|
||||||
|
},
|
||||||
|
quickCreateMaxlength: {
|
||||||
|
type: Number,
|
||||||
|
default: () => 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const emit = defineEmits(['changePage', 'sizeChange', 'creatQuick'])
|
const emit = defineEmits(['changePage', 'sizeChange', 'creatQuick'])
|
||||||
@ -81,7 +95,7 @@ function submitHandle() {
|
|||||||
loading.value = false
|
loading.value = false
|
||||||
}, 200)
|
}, 200)
|
||||||
} else {
|
} else {
|
||||||
MsgError('文件名称不能为空!')
|
MsgError(`${props.quickCreateName}不能为空!`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -728,5 +728,51 @@ export const iconMap: any = {
|
|||||||
)
|
)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'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: 'M565.03091564 528.45078523a588.83471385 588.83471385 0 0 1 16.90811971-15.58251253c16.81532721-14.88656875 34.70439623-28.84521246 50.33330501-44.73261462 28.33485369-28.83195638 37.04409293-63.99368709 29.02416942-101.57465094-9.23948212-43.27444672-40.20566608-71.52976398-84.66653122-81.02111147-31.27770165-8.21876458-35.38708395-7.01909007-67.9373685-4.33473551-37.94550581 6.16407344-39.35727747 6.05802485-76.22241344 22.62811474-2.48551348 1.39188755-19.28758462 10.35962019-24.5966414 15.11855-11.44661809 5.60731841-19.40026124 17.25940562-19.40026123 30.86013539a34.46578693 34.46578693 0 0 0 34.46578694 34.46578694 34.1807814 34.1807814 0 0 0 20.83854503-7.17816293c0.35128591-0.22535322 0.69594378-0.41756626 1.06711379-0.74896807 28.77230406-25.79631593 62.90668921-36.7259472 102.56885634-31.38375021 15.43006769 2.07457524 28.54032281 8.45737387 38.05818242 20.42097876 12.23535436 15.3770434 10.79707056 32.51714437 6.85338917 49.71026962-3.05552458 13.30909618-11.26103308 24.31163586-21.66704951 33.43181333-17.02079632 14.932965-34.65799999 29.27603478-52.28194758 43.60584853-19.63224249 15.97356663-28.85846852 36.7259472-31.52293898 60.18919446a257.89025081 257.89025081 0 0 0-1.49793613 30.30338037h0.04639624c-0.03976821 19.12188371 16.21880398 32.68947331 30.90653165 33.12029565 20.02329661 0.59652323 35.11533446-13.47479709 35.32743162-32.39783973-0.00662803-1.0869979-0.19884108-2.07457524-0.28500555-3.12180494-0.00662803-5.1433559-0.0927925-10.29333983 0.01988411-15.43006769 0.29826162-13.49468121 3.10854885-26.22713825 13.66038209-36.34814915zM515.93042532 643.75209862c-19.01583514-0.76222413-32.4309799 15.15169019-33.41192923 31.12525684-1.23281469 20.1691134 15.69518913 34.65799999 30.89327557 35.10870642 20.02329661 0.59652323 35.11533446-13.47479709 35.32743161-32.39783973-0.13918876-19.99015643-13.38863262-33.06064333-32.80877795-33.83612353zM96.72703555 251.52481518h120.80258323c17.31242991 0 31.34398202-14.84017249 31.34398202-33.14017976s-14.03818015-33.14017975-31.34398202-33.14017975H96.72703555c-17.31242991 0-31.34398202 14.84017249-31.34398201 33.14017975s14.03155212 33.14017975 31.34398201 33.14017976zM94.63920422 412.78492985h120.80258324c17.31242991 0 31.34398202-14.84017249 31.34398201-33.14017974s-14.03818015-33.14017975-31.34398201-33.14017976H94.63920422c-17.31242991 0-31.35061005 14.84017249-31.35061003 33.14017976s14.03818015 33.14017975 31.35061003 33.14017974zM246.78576947 542.32989251c0-18.3066353-14.03818015-33.14017975-31.34398201-33.14017975H94.63920422c-17.31242991 0-31.35061005 14.83354446-31.35061003 33.14017975 0 18.30000725 14.03818015 33.14017975 31.35061003 33.14017976h120.80258324c17.30580187 0 31.34398202-14.84017249 31.34398201-33.14017976z',
|
||||||
|
fill: 'currentColor'
|
||||||
|
}),
|
||||||
|
h('path', {
|
||||||
|
d: 'M824.35945025 44.76986174H194.99429654a35.93058289 35.93058289 0 0 0 0 71.84790971h629.36515371c19.80457142 0 35.96372307 16.13263951 35.96372307 35.93058289v718.5652615a35.99023521 35.99023521 0 0 1-35.96372307 35.93721092H230.10963102a35.95709503 35.95709503 0 0 1-35.95709503-35.93721092v-190.42347285a35.93721092 35.93721092 0 0 0-35.96372307-35.92395486 35.92395486 35.92395486 0 0 0-35.95709503 35.92395486v190.42347285c0 59.43359837 48.40454655 107.79837669 107.87791313 107.7983767h594.24981923c59.47999461 0 107.8712851-48.36477833 107.87128509-107.7983767V152.55498237c0-59.42697034-48.39129049-107.78512063-107.87128509-107.78512063z',
|
||||||
|
fill: 'currentColor'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'app-quxiaoguanlian': {
|
||||||
|
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: 'M544 298.688a32 32 0 0 1 32-32h320c41.216 0 74.688 33.408 74.688 74.624V640c0 41.216-33.472 74.688-74.688 74.688h-85.312a32 32 0 1 1 0-64H896a10.688 10.688 0 0 0 10.688-10.688V341.312A10.688 10.688 0 0 0 896 330.688H576a32 32 0 0 1-32-32zM53.312 341.312c0-41.216 33.472-74.624 74.688-74.624h106.688a32 32 0 1 1 0 64H128a10.688 10.688 0 0 0-10.688 10.624V640c0 5.888 4.8 10.688 10.688 10.688h320a32 32 0 1 1 0 64H128A74.688 74.688 0 0 1 53.312 640V341.312zM282.432 100.416a32 32 0 0 1 43.84 11.392l426.624 725.312a32 32 0 0 1-55.168 32.448L271.104 144.256a32 32 0 0 1 11.328-43.84zM650.688 490.688a32 32 0 0 1 32-32H768a32 32 0 1 1 0 64h-85.312a32 32 0 0 1-32-32zM224 490.688a32 32 0 0 1 32-32h85.312a32 32 0 1 1 0 64H256a32 32 0 0 1-32-32z',
|
||||||
|
fill: 'currentColor'
|
||||||
|
})
|
||||||
|
]
|
||||||
|
)
|
||||||
|
])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="cursor">
|
<div class="cursor w-full">
|
||||||
<slot name="read">
|
<slot name="read">
|
||||||
<div class="flex align-center" v-if="!isEdit">
|
<div class="flex align-center" v-if="!isEdit">
|
||||||
<auto-tooltip :content="data">
|
<auto-tooltip :content="data">
|
||||||
{{ data }}
|
{{ data }}
|
||||||
</auto-tooltip>
|
</auto-tooltip>
|
||||||
|
|
||||||
<el-button @click.stop="editNameHandle" text v-if="showEditIcon">
|
<el-button class="ml-4" @click.stop="editNameHandle" text v-if="showEditIcon">
|
||||||
<el-icon><Edit /></el-icon>
|
<el-icon><EditPen /></el-icon>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</slot>
|
</slot>
|
||||||
<slot>
|
<slot>
|
||||||
<div class="flex align-center" v-if="isEdit">
|
<div class="flex align-center" @click.stop v-if="isEdit">
|
||||||
<div @click.stop>
|
<div class="w-full">
|
||||||
<el-input ref="inputRef" v-model="writeValue" placeholder="请输入" autofocus></el-input>
|
<el-input
|
||||||
|
ref="inputRef"
|
||||||
|
v-model="writeValue"
|
||||||
|
placeholder="请输入"
|
||||||
|
autofocus
|
||||||
|
:maxlength="maxlength"
|
||||||
|
:show-word-limit="maxlength ? true : false"
|
||||||
|
></el-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="ml-4">
|
<span class="ml-4">
|
||||||
@ -42,6 +49,10 @@ const props = defineProps({
|
|||||||
showEditIcon: {
|
showEditIcon: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
maxlength: {
|
||||||
|
type: Number,
|
||||||
|
default: () => 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const emit = defineEmits(['change'])
|
const emit = defineEmits(['change'])
|
||||||
|
|||||||
@ -37,6 +37,18 @@ const datasetRouter = {
|
|||||||
},
|
},
|
||||||
component: () => import('@/views/document/index.vue')
|
component: () => import('@/views/document/index.vue')
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'problem',
|
||||||
|
name: 'Problem',
|
||||||
|
meta: {
|
||||||
|
icon: 'app-problems',
|
||||||
|
title: '问题',
|
||||||
|
active: 'problem',
|
||||||
|
parentPath: '/dataset/:id',
|
||||||
|
parentName: 'DatasetDetail'
|
||||||
|
},
|
||||||
|
component: () => import('@/views/problem/index.vue')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'hit-test',
|
path: 'hit-test',
|
||||||
name: 'DatasetHitTest',
|
name: 'DatasetHitTest',
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import useParagraphStore from './modules/paragraph'
|
|||||||
import useModelStore from './modules/model'
|
import useModelStore from './modules/model'
|
||||||
import useApplicationStore from './modules/application'
|
import useApplicationStore from './modules/application'
|
||||||
import useDocumentStore from './modules/document'
|
import useDocumentStore from './modules/document'
|
||||||
|
import useProblemStore from './modules/problem'
|
||||||
|
|
||||||
const useStore = () => ({
|
const useStore = () => ({
|
||||||
common: useCommonStore(),
|
common: useCommonStore(),
|
||||||
@ -16,7 +17,8 @@ const useStore = () => ({
|
|||||||
paragraph: useParagraphStore(),
|
paragraph: useParagraphStore(),
|
||||||
model: useModelStore(),
|
model: useModelStore(),
|
||||||
application: useApplicationStore(),
|
application: useApplicationStore(),
|
||||||
document: useDocumentStore()
|
document: useDocumentStore(),
|
||||||
|
problem: useProblemStore()
|
||||||
})
|
})
|
||||||
|
|
||||||
export default useStore
|
export default useStore
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import documentApi from '@/api/document'
|
|||||||
import { type Ref } from 'vue'
|
import { type Ref } from 'vue'
|
||||||
|
|
||||||
const useDocumentStore = defineStore({
|
const useDocumentStore = defineStore({
|
||||||
id: 'documents',
|
id: 'document',
|
||||||
state: () => ({}),
|
state: () => ({}),
|
||||||
actions: {
|
actions: {
|
||||||
async asyncGetAllDocument(id: string, loading?: Ref<boolean>) {
|
async asyncGetAllDocument(id: string, loading?: Ref<boolean>) {
|
||||||
@ -17,6 +17,18 @@ const useDocumentStore = defineStore({
|
|||||||
reject(error)
|
reject(error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
async asyncPostDocument(datasetId: string, data: any, loading?: Ref<boolean>) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
documentApi
|
||||||
|
.postDocument(datasetId, data, loading)
|
||||||
|
.then((data) => {
|
||||||
|
resolve(data)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
79
ui/src/stores/modules/problem.ts
Normal file
79
ui/src/stores/modules/problem.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { type Ref } from 'vue'
|
||||||
|
import problemApi from '@/api/problem'
|
||||||
|
import paragraphApi from '@/api/paragraph'
|
||||||
|
import type { pageRequest } from '@/api/type/common'
|
||||||
|
|
||||||
|
const useProblemStore = defineStore({
|
||||||
|
id: 'problem',
|
||||||
|
state: () => ({}),
|
||||||
|
actions: {
|
||||||
|
async asyncPostProblem(datasetId: string, data: any, loading?: Ref<boolean>) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
problemApi
|
||||||
|
.postProblems(datasetId, data, loading)
|
||||||
|
.then((data) => {
|
||||||
|
resolve(data)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async asyncGetProblem(
|
||||||
|
datasetId: string,
|
||||||
|
page: pageRequest,
|
||||||
|
param: any,
|
||||||
|
loading?: Ref<boolean>
|
||||||
|
) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
problemApi
|
||||||
|
.getProblems(datasetId, page, param, loading)
|
||||||
|
.then((data) => {
|
||||||
|
resolve(data)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async asyncDisassociationProblem(
|
||||||
|
datasetId: string,
|
||||||
|
documentId: string,
|
||||||
|
paragraphId: string,
|
||||||
|
problemId: string,
|
||||||
|
loading?: Ref<boolean>
|
||||||
|
) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
paragraphApi
|
||||||
|
.disassociationProblem(datasetId, documentId, paragraphId, problemId, loading)
|
||||||
|
.then((data) => {
|
||||||
|
resolve(data)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async asyncAssociationProblem(
|
||||||
|
datasetId: string,
|
||||||
|
documentId: string,
|
||||||
|
paragraphId: string,
|
||||||
|
problemId: string,
|
||||||
|
loading?: Ref<boolean>
|
||||||
|
) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
paragraphApi
|
||||||
|
.associationProblem(datasetId, documentId, paragraphId, problemId, loading)
|
||||||
|
.then((data) => {
|
||||||
|
resolve(data)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default useProblemStore
|
||||||
@ -533,3 +533,36 @@ h4 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 段落card
|
||||||
|
.paragraph-source-card {
|
||||||
|
height: 210px;
|
||||||
|
width: 100%;
|
||||||
|
.active-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
top: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分段 dialog
|
||||||
|
.paragraph-dialog {
|
||||||
|
padding: 0 !important;
|
||||||
|
.el-scrollbar {
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
.el-dialog__header {
|
||||||
|
padding: 16px 24px;
|
||||||
|
}
|
||||||
|
.el-dialog__body {
|
||||||
|
border-top: 1px solid var(--el-border-color);
|
||||||
|
}
|
||||||
|
.el-dialog__footer {
|
||||||
|
padding: 16px 24px;
|
||||||
|
border-top: 1px solid var(--el-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--app-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -58,18 +58,18 @@ import StepSecond from './step/StepSecond.vue'
|
|||||||
import ResultSuccess from './step/ResultSuccess.vue'
|
import ResultSuccess from './step/ResultSuccess.vue'
|
||||||
import datasetApi from '@/api/dataset'
|
import datasetApi from '@/api/dataset'
|
||||||
import type { datasetData } from '@/api/type/dataset'
|
import type { datasetData } from '@/api/type/dataset'
|
||||||
import documentApi from '@/api/document'
|
|
||||||
import { MsgConfirm, MsgSuccess } from '@/utils/message'
|
import { MsgConfirm, MsgSuccess } from '@/utils/message'
|
||||||
|
|
||||||
import useStore from '@/stores'
|
import useStore from '@/stores'
|
||||||
const { dataset } = useStore()
|
const { dataset, document } = useStore()
|
||||||
const baseInfo = computed(() => dataset.baseInfo)
|
const baseInfo = computed(() => dataset.baseInfo)
|
||||||
const webInfo = computed(() => dataset.webInfo)
|
const webInfo = computed(() => dataset.webInfo)
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const {
|
const {
|
||||||
params: { id, type }
|
params: { type },
|
||||||
|
query: { id } // id为datasetID,有id的是上传文档
|
||||||
} = route
|
} = route
|
||||||
const isCreate = type === 'create'
|
const isCreate = type === 'create'
|
||||||
// const steps = [
|
// const steps = [
|
||||||
@ -112,19 +112,19 @@ function clearStore() {
|
|||||||
}
|
}
|
||||||
function submit() {
|
function submit() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const documents = [] as any[]
|
const data = [] as any
|
||||||
StepSecondRef.value?.paragraphList.map((item: any) => {
|
StepSecondRef.value?.paragraphList.map((item: any) => {
|
||||||
documents.push({
|
data.push({
|
||||||
name: item.name,
|
name: item.name,
|
||||||
paragraphs: item.content
|
paragraphs: item.content
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
const obj = { ...baseInfo.value, documents } as datasetData
|
const obj = { ...baseInfo.value, data } as datasetData
|
||||||
const id = route.query.id
|
|
||||||
if (id) {
|
if (id) {
|
||||||
documentApi
|
// 上传文档
|
||||||
.postDocument(id as string, documents)
|
document
|
||||||
.then((res) => {
|
.asyncPostDocument(id as string, data)
|
||||||
|
.then(() => {
|
||||||
MsgSuccess('提交成功')
|
MsgSuccess('提交成功')
|
||||||
clearStore()
|
clearStore()
|
||||||
router.push({ path: `/dataset/${id}/document` })
|
router.push({ path: `/dataset/${id}/document` })
|
||||||
|
|||||||
@ -10,14 +10,14 @@
|
|||||||
<p class="mb-8">同步方式</p>
|
<p class="mb-8">同步方式</p>
|
||||||
<el-radio-group v-model="method" class="card__radio">
|
<el-radio-group v-model="method" class="card__radio">
|
||||||
<el-card shadow="never" class="mb-16" :class="method === 'replace' ? 'active' : ''">
|
<el-card shadow="never" class="mb-16" :class="method === 'replace' ? 'active' : ''">
|
||||||
<el-radio label="replace" size="large">
|
<el-radio value="replace" size="large">
|
||||||
<p class="mb-4">替换同步</p>
|
<p class="mb-4">替换同步</p>
|
||||||
<el-text type="info">重新获取 Web 站点文档,覆盖替换本地知识库中的文档</el-text>
|
<el-text type="info">重新获取 Web 站点文档,覆盖替换本地知识库中的文档</el-text>
|
||||||
</el-radio>
|
</el-radio>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card shadow="never" class="mb-16" :class="method === 'complete' ? 'active' : ''">
|
<el-card shadow="never" class="mb-16" :class="method === 'complete' ? 'active' : ''">
|
||||||
<el-radio label="complete" size="large">
|
<el-radio value="complete" size="large">
|
||||||
<p class="mb-4">整体同步</p>
|
<p class="mb-4">整体同步</p>
|
||||||
<el-text type="info">先删除本地知识库所有文档,重新获取 Web 站点文档</el-text>
|
<el-text type="info">先删除本地知识库所有文档,重新获取 Web 站点文档</el-text>
|
||||||
</el-radio>
|
</el-radio>
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-card shadow="never" class="mb-16" :class="form.type === '0' ? 'active' : ''">
|
<el-card shadow="never" class="mb-16" :class="form.type === '0' ? 'active' : ''">
|
||||||
<el-radio label="0" size="large">
|
<el-radio value="0" size="large">
|
||||||
<div class="flex align-center">
|
<div class="flex align-center">
|
||||||
<AppAvatar class="mr-8" shape="square" :size="32">
|
<AppAvatar class="mr-8" shape="square" :size="32">
|
||||||
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
|
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
|
||||||
@ -31,7 +31,7 @@
|
|||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-card shadow="never" class="mb-16" :class="form.type === '1' ? 'active' : ''">
|
<el-card shadow="never" class="mb-16" :class="form.type === '1' ? 'active' : ''">
|
||||||
<el-radio label="1" size="large">
|
<el-radio value="1" size="large">
|
||||||
<div class="flex align-center">
|
<div class="flex align-center">
|
||||||
<AppAvatar class="mr-8 avatar-purple" shape="square" :size="32">
|
<AppAvatar class="mr-8 avatar-purple" shape="square" :size="32">
|
||||||
<img src="@/assets/icon_web.svg" style="width: 58%" alt="" />
|
<img src="@/assets/icon_web.svg" style="width: 58%" alt="" />
|
||||||
|
|||||||
@ -8,13 +8,13 @@
|
|||||||
<div class="left-height" @click.stop>
|
<div class="left-height" @click.stop>
|
||||||
<el-radio-group v-model="radio" class="set-rules__radio">
|
<el-radio-group v-model="radio" class="set-rules__radio">
|
||||||
<el-card shadow="never" class="mb-16" :class="radio === '1' ? 'active' : ''">
|
<el-card shadow="never" class="mb-16" :class="radio === '1' ? 'active' : ''">
|
||||||
<el-radio label="1" size="large">
|
<el-radio value="1" size="large">
|
||||||
<p class="mb-4">智能分段(推荐)</p>
|
<p class="mb-4">智能分段(推荐)</p>
|
||||||
<el-text type="info">不了解如何设置分段规则推荐使用智能分段</el-text>
|
<el-text type="info">不了解如何设置分段规则推荐使用智能分段</el-text>
|
||||||
</el-radio>
|
</el-radio>
|
||||||
</el-card>
|
</el-card>
|
||||||
<el-card shadow="never" class="mb-16" :class="radio === '2' ? 'active' : ''">
|
<el-card shadow="never" class="mb-16" :class="radio === '2' ? 'active' : ''">
|
||||||
<el-radio label="2" size="large">
|
<el-radio value="2" size="large">
|
||||||
<p class="mb-4">高级分段</p>
|
<p class="mb-4">高级分段</p>
|
||||||
<el-text type="info"
|
<el-text type="info"
|
||||||
>用户可根据文档规范自行设置分段标识符、分段长度以及清洗规则
|
>用户可根据文档规范自行设置分段标识符、分段长度以及清洗规则
|
||||||
|
|||||||
@ -169,10 +169,10 @@ import useStore from '@/stores'
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const {
|
const {
|
||||||
params: { id }
|
params: { id } // id为datasetID
|
||||||
} = route as any
|
} = route as any
|
||||||
|
|
||||||
const { dataset } = useStore()
|
const { dataset, document } = useStore()
|
||||||
|
|
||||||
const SyncWebDialogRef = ref()
|
const SyncWebDialogRef = ref()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@ -262,9 +262,9 @@ function rowClickHandle(row: any) {
|
|||||||
function creatQuickHandle(val: string) {
|
function creatQuickHandle(val: string) {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const obj = [{ name: val }]
|
const obj = [{ name: val }]
|
||||||
documentApi
|
document
|
||||||
.postDocument(id, obj)
|
.asyncPostDocument(id, obj)
|
||||||
.then((res) => {
|
.then(() => {
|
||||||
getList()
|
getList()
|
||||||
MsgSuccess('创建成功')
|
MsgSuccess('创建成功')
|
||||||
})
|
})
|
||||||
|
|||||||
@ -27,7 +27,7 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="6" class="border-l">
|
<el-col :span="6" class="border-l" style="width: 300px;">
|
||||||
<!-- 关联问题 -->
|
<!-- 关联问题 -->
|
||||||
<ProblemComponent
|
<ProblemComponent
|
||||||
:problemId="problemId"
|
:problemId="problemId"
|
||||||
@ -150,24 +150,5 @@ const handleDebounceClick = debounce(() => {
|
|||||||
defineExpose({ open })
|
defineExpose({ open })
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scope>
|
<style lang="scss" scope>
|
||||||
.paragraph-dialog {
|
|
||||||
padding: 0 !important;
|
|
||||||
.el-scrollbar {
|
|
||||||
height: auto !important;
|
|
||||||
}
|
|
||||||
.el-dialog__header {
|
|
||||||
padding: 16px 24px;
|
|
||||||
}
|
|
||||||
.el-dialog__body {
|
|
||||||
border-top: 1px solid var(--el-border-color);
|
|
||||||
}
|
|
||||||
.el-dialog__footer {
|
|
||||||
padding: 16px 24px;
|
|
||||||
border-top: 1px solid var(--el-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
color: var(--app-text-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -11,16 +11,7 @@
|
|||||||
<div v-loading="loading">
|
<div v-loading="loading">
|
||||||
<el-scrollbar height="345px">
|
<el-scrollbar height="345px">
|
||||||
<div class="p-24" style="padding-top: 16px">
|
<div class="p-24" style="padding-top: 16px">
|
||||||
<el-input
|
<el-select
|
||||||
ref="inputRef"
|
|
||||||
v-if="isAddProblem"
|
|
||||||
v-model="problemValue"
|
|
||||||
@change="addProblemHandle"
|
|
||||||
placeholder="请输入问题,回车保存"
|
|
||||||
class="mb-8"
|
|
||||||
autofocus
|
|
||||||
/>
|
|
||||||
<!-- <el-select
|
|
||||||
v-if="isAddProblem"
|
v-if="isAddProblem"
|
||||||
v-model="problemValue"
|
v-model="problemValue"
|
||||||
filterable
|
filterable
|
||||||
@ -28,15 +19,19 @@
|
|||||||
default-first-option
|
default-first-option
|
||||||
:reserve-keyword="false"
|
:reserve-keyword="false"
|
||||||
placeholder="请选择问题"
|
placeholder="请选择问题"
|
||||||
style="width: 240px"
|
remote
|
||||||
|
:remote-method="remoteMethod"
|
||||||
|
:loading="optionLoading"
|
||||||
|
@change="addProblemHandle"
|
||||||
|
class="mb-16"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in problemList"
|
v-for="item in problemOptions"
|
||||||
:key="item.value"
|
:key="item.id"
|
||||||
:label="item.label"
|
:label="item.content"
|
||||||
:value="item.value"
|
:value="item.id"
|
||||||
/>
|
/>
|
||||||
</el-select> -->
|
</el-select>
|
||||||
<template v-for="(item, index) in problemList" :key="index">
|
<template v-for="(item, index) in problemList" :key="index">
|
||||||
<TagEllipsis
|
<TagEllipsis
|
||||||
@close="delProblemHandle(item, index)"
|
@close="delProblemHandle(item, index)"
|
||||||
@ -56,6 +51,7 @@
|
|||||||
import { ref, nextTick, onMounted, onUnmounted, watch } from 'vue'
|
import { ref, nextTick, onMounted, onUnmounted, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import paragraphApi from '@/api/paragraph'
|
import paragraphApi from '@/api/paragraph'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
problemId: String,
|
problemId: String,
|
||||||
@ -65,9 +61,10 @@ const props = defineProps({
|
|||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const {
|
const {
|
||||||
params: { id, documentId }
|
params: { id, documentId } // id为datasetId
|
||||||
} = route as any
|
} = route as any
|
||||||
|
|
||||||
|
const { problem } = useStore()
|
||||||
const inputRef = ref()
|
const inputRef = ref()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const isAddProblem = ref(false)
|
const isAddProblem = ref(false)
|
||||||
@ -75,6 +72,9 @@ const isAddProblem = ref(false)
|
|||||||
const problemValue = ref('')
|
const problemValue = ref('')
|
||||||
const problemList = ref<any[]>([])
|
const problemList = ref<any[]>([])
|
||||||
|
|
||||||
|
const problemOptions = ref<any[]>([])
|
||||||
|
const optionLoading = ref(false)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.problemId,
|
() => props.problemId,
|
||||||
(value) => {
|
(value) => {
|
||||||
@ -88,19 +88,20 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
function delProblemHandle(item: any, index: number) {
|
function delProblemHandle(item: any, index: number) {
|
||||||
loading.value = true
|
|
||||||
if (item.id) {
|
if (item.id) {
|
||||||
paragraphApi
|
problem
|
||||||
.delProblem(props.datasetId || id, documentId || props.docId, props.problemId || '', item.id)
|
.asyncDisassociationProblem(
|
||||||
.then((res) => {
|
props.datasetId || id,
|
||||||
|
documentId || props.docId,
|
||||||
|
props.problemId || '',
|
||||||
|
item.id,
|
||||||
|
loading
|
||||||
|
)
|
||||||
|
.then((res: any) => {
|
||||||
getProblemList()
|
getProblemList()
|
||||||
})
|
})
|
||||||
.catch(() => {
|
|
||||||
loading.value = false
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
problemList.value.splice(index, 1)
|
problemList.value.splice(index, 1)
|
||||||
loading.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,32 +125,52 @@ function addProblem() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
function addProblemHandle(val: string) {
|
function addProblemHandle(val: string) {
|
||||||
if (val) {
|
if (props.problemId) {
|
||||||
const obj = {
|
const api = problemOptions.value.some((option) => option.id === val)
|
||||||
content: val
|
? problem.asyncAssociationProblem(
|
||||||
}
|
props.datasetId || id,
|
||||||
loading.value = true
|
documentId || props.docId,
|
||||||
if (props.problemId) {
|
props.problemId,
|
||||||
paragraphApi
|
val,
|
||||||
.postProblem(props.datasetId || id, documentId || props.docId, props.problemId, obj)
|
loading
|
||||||
.then((res) => {
|
)
|
||||||
getProblemList()
|
: paragraphApi.postProblem(
|
||||||
problemValue.value = ''
|
props.datasetId || id,
|
||||||
isAddProblem.value = false
|
documentId || props.docId,
|
||||||
})
|
props.problemId,
|
||||||
.catch(() => {
|
{
|
||||||
loading.value = false
|
content: val
|
||||||
})
|
},
|
||||||
} else {
|
loading
|
||||||
problemList.value.unshift(obj)
|
)
|
||||||
|
api.then(() => {
|
||||||
|
getProblemList()
|
||||||
problemValue.value = ''
|
problemValue.value = ''
|
||||||
isAddProblem.value = false
|
isAddProblem.value = false
|
||||||
loading.value = false
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {})
|
const remoteMethod = (query: string) => {
|
||||||
|
getProblemOption(query)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProblemOption(filterText?: string) {
|
||||||
|
return problem
|
||||||
|
.asyncGetProblem(
|
||||||
|
id as string,
|
||||||
|
{ current_page: 1, page_size: 100 },
|
||||||
|
filterText && { content: filterText },
|
||||||
|
optionLoading
|
||||||
|
)
|
||||||
|
.then((res: any) => {
|
||||||
|
problemOptions.value = res.data.records
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getProblemOption()
|
||||||
|
})
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
problemList.value = []
|
problemList.value = []
|
||||||
problemValue.value = ''
|
problemValue.value = ''
|
||||||
@ -162,6 +183,6 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.question-tag {
|
.question-tag {
|
||||||
width: 217px;
|
// width: 217px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
90
ui/src/views/problem/component/CreateProblemDialog.vue
Normal file
90
ui/src/views/problem/component/CreateProblemDialog.vue
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
title="创建问题"
|
||||||
|
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="问题" prop="data">
|
||||||
|
<el-input
|
||||||
|
v-model="form.data"
|
||||||
|
placeholder="请输入问题,支持输入多个,一行一个。"
|
||||||
|
:rows="10"
|
||||||
|
type="textarea"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click.prevent="dialogVisible = false"> 取消 </el-button>
|
||||||
|
<el-button type="primary" @click="submit(problemFormRef)" :loading="loading">
|
||||||
|
确定
|
||||||
|
</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'
|
||||||
|
|
||||||
|
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: '请输入问题', 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')
|
||||||
|
problem.asyncPostProblem(id, arr, loading).then((res: any) => {
|
||||||
|
MsgSuccess('创建成功')
|
||||||
|
emit('refresh')
|
||||||
|
dialogVisible.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
197
ui/src/views/problem/component/DetailProblemDrawer.vue
Normal file
197
ui/src/views/problem/component/DetailProblemDrawer.vue
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer v-model="visible" size="60%" @close="closeHandel">
|
||||||
|
<template #header>
|
||||||
|
<h4>问题详情</h4>
|
||||||
|
</template>
|
||||||
|
<div>
|
||||||
|
<el-scrollbar>
|
||||||
|
<div class="p-8">
|
||||||
|
<el-form label-position="top" v-loading="loading">
|
||||||
|
<el-form-item label="问题">
|
||||||
|
<ReadWrite
|
||||||
|
@change="editName"
|
||||||
|
:data="currentContent"
|
||||||
|
:showEditIcon="true"
|
||||||
|
:maxlength="256"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="关联分段">
|
||||||
|
<template v-for="(item, index) in paragraphList" :key="index">
|
||||||
|
<CardBox
|
||||||
|
shadow="never"
|
||||||
|
:title="item.title || '-'"
|
||||||
|
class="paragraph-source-card cursor mb-8"
|
||||||
|
:showIcon="false"
|
||||||
|
>
|
||||||
|
<div class="active-button">
|
||||||
|
<span class="mr-4">
|
||||||
|
<el-tooltip effect="dark" content="编辑" placement="top">
|
||||||
|
<el-button type="primary" text @click.stop="editParagraph(item)">
|
||||||
|
<el-icon><EditPen /></el-icon>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip effect="dark" content="取消关联" 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="编辑分段" @refresh="refresh" />
|
||||||
|
<RelateProblemDialog ref="RelateProblemDialogRef" @refresh="refresh" />
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div>
|
||||||
|
<el-button @click="relateProblem">关联分段</el-button>
|
||||||
|
<el-button @click="pre" :disabled="pre_disable || loading">上一条</el-button>
|
||||||
|
<el-button @click="next" :disabled="next_disable || loading">下一条</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/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'
|
||||||
|
|
||||||
|
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('修改成功')
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
MsgError('问题不能为空!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeHandel() {
|
||||||
|
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>
|
||||||
261
ui/src/views/problem/component/RelateProblemDialog.vue
Normal file
261
ui/src/views/problem/component/RelateProblemDialog.vue
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
title="关联分段"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
width="80%"
|
||||||
|
class="paragraph-dialog"
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<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">选择文档</div>
|
||||||
|
<div class="p-8" style="padding-bottom: 8px">
|
||||||
|
<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">
|
||||||
|
选择分段
|
||||||
|
<el-text> (已选分段:{{ associationCount(currentDocument) }} 个) </el-text>
|
||||||
|
</div>
|
||||||
|
<el-input
|
||||||
|
v-model="search"
|
||||||
|
placeholder="搜索"
|
||||||
|
class="input-with-select"
|
||||||
|
style="width: 260px"
|
||||||
|
@change="searchHandle"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<el-select v-model="searchType" placeholder="Select" style="width: 80px">
|
||||||
|
<el-option label="标题" value="title" />
|
||||||
|
<el-option label="内容" value="content" />
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
<el-empty v-if="paragraphList.length == 0" description="暂无数据" />
|
||||||
|
|
||||||
|
<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) ? 'active' : ''"
|
||||||
|
:showIcon="false"
|
||||||
|
@click="associationClick(item)"
|
||||||
|
>
|
||||||
|
</CardBox>
|
||||||
|
</template>
|
||||||
|
</InfiniteScroll>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, reactive } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import problemApi from '@/api/problem'
|
||||||
|
import paragraphApi from '@/api/paragraph'
|
||||||
|
import useStore from '@/stores'
|
||||||
|
|
||||||
|
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 paragraphList = ref<any[]>([])
|
||||||
|
const currentProblemId = ref<String>('')
|
||||||
|
|
||||||
|
// 回显
|
||||||
|
const associationParagraph = ref<any[]>([])
|
||||||
|
|
||||||
|
const currentDocument = ref<String>('')
|
||||||
|
const search = ref('')
|
||||||
|
const searchType = ref('title')
|
||||||
|
|
||||||
|
const paginationConfig = reactive({
|
||||||
|
current_page: 1,
|
||||||
|
page_size: 50,
|
||||||
|
total: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
function associationClick(item: any) {
|
||||||
|
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 = []
|
||||||
|
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) => {
|
||||||
|
documentList.value = res.data
|
||||||
|
currentDocument.value = documentList.value?.length > 0 ? documentList.value[0].id : ''
|
||||||
|
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 = []
|
||||||
|
paragraphList.value = []
|
||||||
|
associationParagraph.value = []
|
||||||
|
|
||||||
|
currentDocument.value = ''
|
||||||
|
search.value = ''
|
||||||
|
searchType.value = 'title'
|
||||||
|
emit('refresh')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const open = (problemId: string) => {
|
||||||
|
currentProblemId.value = problemId
|
||||||
|
getDocument()
|
||||||
|
getRecord(problemId)
|
||||||
|
dialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ open })
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scope>
|
||||||
|
.paragraph-card {
|
||||||
|
position: relative;
|
||||||
|
&.active {
|
||||||
|
border: 1px solid var(--el-color-primary);
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
border: 14px solid var(--el-color-primary);
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
border-left-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: '';
|
||||||
|
width: 3px;
|
||||||
|
height: 6px;
|
||||||
|
position: absolute;
|
||||||
|
right: 5px;
|
||||||
|
top: 2px;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
border-top-color: transparent;
|
||||||
|
border-left-color: transparent;
|
||||||
|
transform: rotate(35deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.paragraph-badge {
|
||||||
|
.el-badge__content {
|
||||||
|
height: auto;
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
340
ui/src/views/problem/index.vue
Normal file
340
ui/src/views/problem/index.vue
Normal file
@ -0,0 +1,340 @@
|
|||||||
|
<template>
|
||||||
|
<LayoutContainer header="问题">
|
||||||
|
<div class="main-calc-height">
|
||||||
|
<div class="p-24">
|
||||||
|
<div class="flex-between">
|
||||||
|
<div>
|
||||||
|
<el-button type="primary" @click="createProblem">创建问题</el-button>
|
||||||
|
<el-button @click="deleteMulDocument" :disabled="multipleSelection.length === 0"
|
||||||
|
>批量删除</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-input
|
||||||
|
v-model="filterText"
|
||||||
|
placeholder="搜索内容"
|
||||||
|
prefix-icon="Search"
|
||||||
|
class="w-240"
|
||||||
|
@change="getList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<app-table
|
||||||
|
ref="multipleTableRef"
|
||||||
|
class="mt-16"
|
||||||
|
:data="problemData"
|
||||||
|
:pagination-config="paginationConfig"
|
||||||
|
quick-create
|
||||||
|
quickCreateName="问题"
|
||||||
|
quickCreatePlaceholder="快速创建问题"
|
||||||
|
: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="问题" min-width="280">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ReadWrite
|
||||||
|
@change="editName"
|
||||||
|
:data="row.content"
|
||||||
|
:showEditIcon="row.id === currentMouseId"
|
||||||
|
:maxlength="256"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="paragraph_count" label="关联分段数" 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="创建时间" width="170">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ datetimeFormat(row.create_time) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="update_time" label="更新时间" width="170">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ datetimeFormat(row.update_time) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" align="left">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div>
|
||||||
|
<span class="mr-4">
|
||||||
|
<el-tooltip effect="dark" content="关联分段" 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="删除" 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="refresh"
|
||||||
|
/>
|
||||||
|
<RelateProblemDialog ref="RelateProblemDialogRef" @refresh="refresh" />
|
||||||
|
</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/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'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const {
|
||||||
|
params: { 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) {
|
||||||
|
RelateProblemDialogRef.value.open(row.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
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('创建成功')
|
||||||
|
})
|
||||||
|
.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('批量删除成功')
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteProblem(row: any) {
|
||||||
|
MsgConfirm(
|
||||||
|
`是否删除问题:${row.content} ?`,
|
||||||
|
`删除问题关联的 ${row.paragraph_count} 个分段会被取消关联,请谨慎操作。`,
|
||||||
|
{
|
||||||
|
confirmButtonText: '删除',
|
||||||
|
confirmButtonClass: 'danger'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
problemApi.delProblems(id, row.id, loading).then(() => {
|
||||||
|
MsgSuccess('删除成功')
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
function editName(val: string) {
|
||||||
|
if (val) {
|
||||||
|
const obj = {
|
||||||
|
content: val
|
||||||
|
}
|
||||||
|
problemApi.putProblems(id, currentMouseId.value, obj, loading).then(() => {
|
||||||
|
getList()
|
||||||
|
MsgSuccess('修改成功')
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
MsgError('问题不能为空!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (row.paragraph_count) {
|
||||||
|
currentClickId.value = row.id
|
||||||
|
currentContent.value = row.content
|
||||||
|
DetailProblemRef.value.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setRowClass = ({ row }: any) => {
|
||||||
|
return currentClickId.value === row?.id ? 'hightlight' : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
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 refresh() {
|
||||||
|
paginationConfig.current_page = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {})
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
Loading…
Reference in New Issue
Block a user