Merge branch 'main' into pr@main@fix_bugs

This commit is contained in:
shaohuzhang1 2024-04-09 11:35:54 +08:00
commit 5e200b2cec
28 changed files with 1409 additions and 119 deletions

View File

@ -54,6 +54,10 @@ docker run -d --name=maxkb -p 8080:8080 -v ~/.maxkb:/var/lib/postgresql/data 1pa
- 向量数据库:[PostgreSQL / pgvector](https://www.postgresql.org/)
- 大模型Azure OpenAI、百度千帆大模型、[Ollama](https://github.com/ollama/ollama)
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/MaxKB&type=Date)](https://star-history.com/#1Panel-dev/MaxKB&Date)
## License
Copyright (c) 2014-2024 飞致云 FIT2CLOUD, All rights reserved.

View File

@ -218,7 +218,7 @@ class ParagraphSerializers(ApiMixin, serializers.Serializer):
def association(self, with_valid=True, with_embedding=True):
if with_valid:
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(),
document_id=self.data.get('document_id'),
paragraph_id=self.data.get('paragraph_id'),

View File

@ -8,7 +8,7 @@
"""
import os
import uuid
from typing import Dict
from typing import Dict, List
from django.db import transaction
from django.db.models import QuerySet
@ -83,6 +83,7 @@ class ProblemSerializers(ApiMixin, serializers.Serializer):
**{'dataset_id': self.data.get('dataset_id')})
if 'content' in self.data:
query_set = query_set.filter(**{'content__contains': self.data.get('content')})
query_set = query_set.order_by("-create_time")
return query_set
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(
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):
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)
problem_paragraph_mapping = QuerySet(ProblemParagraphMapping).filter(dataset_id=self.data.get("dataset_id"),
problem_id=self.data.get("problem_id"))
if problem_paragraph_mapping is None or len(problem_paragraph_mapping)==0:
return []
return native_search(
QuerySet(Paragraph).filter(id__in=[row.paragraph_id for row in problem_paragraph_mapping]),
select_string=get_file_content(
@ -123,6 +142,7 @@ class ProblemSerializers(ApiMixin, serializers.Serializer):
dataset_id=self.data.get('dataset_id'),
problem_id=self.data.get('problem_id'))
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()
ListenerManagement.delete_embedding_by_source_ids_signal.send(source_ids)
return True

View File

@ -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):
@staticmethod
def get_request_params_api():

View File

@ -36,6 +36,7 @@ urlpatterns = [
'dataset/<str:dataset_id>/document/<str:document_id>/paragraph/<str:paragraph_id>/problem/<str:problem_id>/association',
views.Paragraph.Problem.Association.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/<str:problem_id>', views.Problem.Operate.as_view()),
path('dataset/<str:dataset_id>/problem/<str:problem_id>/paragraph', views.Problem.Paragraph.as_view()),

View File

@ -51,7 +51,7 @@ class Problem(APIView):
def post(self, request: Request, dataset_id: str):
return result.success(
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):
authentication_classes = [TokenAuth]
@ -70,6 +70,24 @@ class Problem(APIView):
data={**query_params_to_single_dict(request.query_params), 'dataset_id': dataset_id,
'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):
authentication_classes = [TokenAuth]

View File

@ -129,25 +129,55 @@ const postProblem: (
dataset_id: string,
document_id: string,
paragraph_id: string,
data: any
) => Promise<Result<any>> = (dataset_id, document_id, paragraph_id, data: any) => {
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
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 delProblem: (
const disassociationProblem: (
dataset_id: string,
document_id: string,
paragraph_id: string,
problem_id: string
) => Promise<Result<boolean>> = (dataset_id, document_id, paragraph_id, problem_id) => {
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`
`${prefix}/${dataset_id}/document/${document_id}/paragraph/${paragraph_id}/problem/${problem_id}/un_association`,
{},
{},
loading
)
}
@ -158,5 +188,6 @@ export default {
postParagraph,
getProblem,
postProblem,
delProblem
disassociationProblem,
associationProblem
}

107
ui/src/api/problem.ts Normal file
View 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
}

View File

@ -102,13 +102,4 @@ defineExpose({ open })
height: calc(100vh - 260px);
}
}
.paragraph-source-card {
height: 210px;
width: 100%;
.active-button {
position: absolute;
right: 16px;
top: 16px;
}
}
</style>

View File

@ -6,9 +6,11 @@
<el-input
ref="quickInputRef"
v-model="inputValue"
placeholder="请输入文档名称"
:placeholder="`请输入${quickCreateName}`"
class="w-500 mr-12"
autofocus
:maxlength="quickCreateMaxlength"
:show-word-limit="quickCreateMaxlength ? true : false"
/>
<el-button type="primary" @click="submitHandle" :disabled="loading">创建</el-button>
@ -17,7 +19,7 @@
<div v-else @click="quickCreateHandel" class="w-full">
<el-button type="primary" link class="quich-button">
<el-icon><Plus /></el-icon>
<span class="ml-4">快速创建空白文档</span>
<span class="ml-4">{{ quickCreatePlaceholder }}</span>
</el-button>
</div>
</template>
@ -51,6 +53,18 @@ const props = defineProps({
quickCreate: {
type: Boolean,
default: false
},
quickCreateName: {
type: String,
default: '文档名称'
},
quickCreatePlaceholder: {
type: String,
default: '快速创建空白文档'
},
quickCreateMaxlength: {
type: Number,
default: () => 0
}
})
const emit = defineEmits(['changePage', 'sizeChange', 'creatQuick'])
@ -81,7 +95,7 @@ function submitHandle() {
loading.value = false
}, 200)
} else {
MsgError('文件名称不能为空!')
MsgError(`${props.quickCreateName}不能为空!`)
}
}

View File

@ -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'
})
]
)
])
}
}
}

View File

@ -1,20 +1,27 @@
<template>
<div class="cursor">
<div class="cursor w-full">
<slot name="read">
<div class="flex align-center" v-if="!isEdit">
<auto-tooltip :content="data">
{{ data }}
</auto-tooltip>
<el-button @click.stop="editNameHandle" text v-if="showEditIcon">
<el-icon><Edit /></el-icon>
<el-button class="ml-4" @click.stop="editNameHandle" text v-if="showEditIcon">
<el-icon><EditPen /></el-icon>
</el-button>
</div>
</slot>
<slot>
<div class="flex align-center" v-if="isEdit">
<div @click.stop>
<el-input ref="inputRef" v-model="writeValue" placeholder="请输入" autofocus></el-input>
<div class="flex align-center" @click.stop v-if="isEdit">
<div class="w-full">
<el-input
ref="inputRef"
v-model="writeValue"
placeholder="请输入"
autofocus
:maxlength="maxlength"
:show-word-limit="maxlength ? true : false"
></el-input>
</div>
<span class="ml-4">
@ -42,6 +49,10 @@ const props = defineProps({
showEditIcon: {
type: Boolean,
default: false
},
maxlength: {
type: Number,
default: () => 0
}
})
const emit = defineEmits(['change'])

View File

@ -37,6 +37,18 @@ const datasetRouter = {
},
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',
name: 'DatasetHitTest',

View File

@ -8,6 +8,7 @@ import useParagraphStore from './modules/paragraph'
import useModelStore from './modules/model'
import useApplicationStore from './modules/application'
import useDocumentStore from './modules/document'
import useProblemStore from './modules/problem'
const useStore = () => ({
common: useCommonStore(),
@ -16,7 +17,8 @@ const useStore = () => ({
paragraph: useParagraphStore(),
model: useModelStore(),
application: useApplicationStore(),
document: useDocumentStore()
document: useDocumentStore(),
problem: useProblemStore()
})
export default useStore

View File

@ -3,7 +3,7 @@ import documentApi from '@/api/document'
import { type Ref } from 'vue'
const useDocumentStore = defineStore({
id: 'documents',
id: 'document',
state: () => ({}),
actions: {
async asyncGetAllDocument(id: string, loading?: Ref<boolean>) {
@ -17,6 +17,18 @@ const useDocumentStore = defineStore({
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)
})
})
}
}
})

View 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

View File

@ -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);
}
}

View File

@ -58,18 +58,18 @@ import StepSecond from './step/StepSecond.vue'
import ResultSuccess from './step/ResultSuccess.vue'
import datasetApi from '@/api/dataset'
import type { datasetData } from '@/api/type/dataset'
import documentApi from '@/api/document'
import { MsgConfirm, MsgSuccess } from '@/utils/message'
import useStore from '@/stores'
const { dataset } = useStore()
const { dataset, document } = useStore()
const baseInfo = computed(() => dataset.baseInfo)
const webInfo = computed(() => dataset.webInfo)
const router = useRouter()
const route = useRoute()
const {
params: { id, type }
params: { type },
query: { id } // iddatasetIDid
} = route
const isCreate = type === 'create'
// const steps = [
@ -112,19 +112,19 @@ function clearStore() {
}
function submit() {
loading.value = true
const documents = [] as any[]
const data = [] as any
StepSecondRef.value?.paragraphList.map((item: any) => {
documents.push({
data.push({
name: item.name,
paragraphs: item.content
})
})
const obj = { ...baseInfo.value, documents } as datasetData
const id = route.query.id
const obj = { ...baseInfo.value, data } as datasetData
if (id) {
documentApi
.postDocument(id as string, documents)
.then((res) => {
//
document
.asyncPostDocument(id as string, data)
.then(() => {
MsgSuccess('提交成功')
clearStore()
router.push({ path: `/dataset/${id}/document` })

View File

@ -10,14 +10,14 @@
<p class="mb-8">同步方式</p>
<el-radio-group v-model="method" class="card__radio">
<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>
<el-text type="info">重新获取 Web 站点文档覆盖替换本地知识库中的文档</el-text>
</el-radio>
</el-card>
<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>
<el-text type="info">先删除本地知识库所有文档重新获取 Web 站点文档</el-text>
</el-radio>

View File

@ -16,7 +16,7 @@
<el-row :gutter="20">
<el-col :span="12">
<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">
<AppAvatar class="mr-8" shape="square" :size="32">
<img src="@/assets/icon_document.svg" style="width: 58%" alt="" />
@ -31,7 +31,7 @@
</el-col>
<el-col :span="12">
<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">
<AppAvatar class="mr-8 avatar-purple" shape="square" :size="32">
<img src="@/assets/icon_web.svg" style="width: 58%" alt="" />

View File

@ -8,13 +8,13 @@
<div class="left-height" @click.stop>
<el-radio-group v-model="radio" class="set-rules__radio">
<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>
<el-text type="info">不了解如何设置分段规则推荐使用智能分段</el-text>
</el-radio>
</el-card>
<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>
<el-text type="info"
>用户可根据文档规范自行设置分段标识符分段长度以及清洗规则

View File

@ -169,10 +169,10 @@ import useStore from '@/stores'
const router = useRouter()
const route = useRoute()
const {
params: { id }
params: { id } // iddatasetID
} = route as any
const { dataset } = useStore()
const { dataset, document } = useStore()
const SyncWebDialogRef = ref()
const loading = ref(false)
@ -262,9 +262,9 @@ function rowClickHandle(row: any) {
function creatQuickHandle(val: string) {
loading.value = true
const obj = [{ name: val }]
documentApi
.postDocument(id, obj)
.then((res) => {
document
.asyncPostDocument(id, obj)
.then(() => {
getList()
MsgSuccess('创建成功')
})

View File

@ -27,7 +27,7 @@
</el-button>
</div>
</el-col>
<el-col :span="6" class="border-l">
<el-col :span="6" class="border-l" style="width: 300px;">
<!-- 关联问题 -->
<ProblemComponent
:problemId="problemId"
@ -150,24 +150,5 @@ const handleDebounceClick = debounce(() => {
defineExpose({ open })
</script>
<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>

View File

@ -11,16 +11,7 @@
<div v-loading="loading">
<el-scrollbar height="345px">
<div class="p-24" style="padding-top: 16px">
<el-input
ref="inputRef"
v-if="isAddProblem"
v-model="problemValue"
@change="addProblemHandle"
placeholder="请输入问题,回车保存"
class="mb-8"
autofocus
/>
<!-- <el-select
<el-select
v-if="isAddProblem"
v-model="problemValue"
filterable
@ -28,15 +19,19 @@
default-first-option
:reserve-keyword="false"
placeholder="请选择问题"
style="width: 240px"
remote
:remote-method="remoteMethod"
:loading="optionLoading"
@change="addProblemHandle"
class="mb-16"
>
<el-option
v-for="item in problemList"
:key="item.value"
:label="item.label"
:value="item.value"
v-for="item in problemOptions"
:key="item.id"
:label="item.content"
:value="item.id"
/>
</el-select> -->
</el-select>
<template v-for="(item, index) in problemList" :key="index">
<TagEllipsis
@close="delProblemHandle(item, index)"
@ -56,6 +51,7 @@
import { ref, nextTick, onMounted, onUnmounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import paragraphApi from '@/api/paragraph'
import useStore from '@/stores'
const props = defineProps({
problemId: String,
@ -65,9 +61,10 @@ const props = defineProps({
const route = useRoute()
const {
params: { id, documentId }
params: { id, documentId } // iddatasetId
} = route as any
const { problem } = useStore()
const inputRef = ref()
const loading = ref(false)
const isAddProblem = ref(false)
@ -75,6 +72,9 @@ const isAddProblem = ref(false)
const problemValue = ref('')
const problemList = ref<any[]>([])
const problemOptions = ref<any[]>([])
const optionLoading = ref(false)
watch(
() => props.problemId,
(value) => {
@ -88,19 +88,20 @@ watch(
)
function delProblemHandle(item: any, index: number) {
loading.value = true
if (item.id) {
paragraphApi
.delProblem(props.datasetId || id, documentId || props.docId, props.problemId || '', item.id)
.then((res) => {
problem
.asyncDisassociationProblem(
props.datasetId || id,
documentId || props.docId,
props.problemId || '',
item.id,
loading
)
.then((res: any) => {
getProblemList()
})
.catch(() => {
loading.value = false
})
} else {
problemList.value.splice(index, 1)
loading.value = false
}
}
@ -124,32 +125,52 @@ function addProblem() {
})
}
function addProblemHandle(val: string) {
if (val) {
const obj = {
content: val
}
loading.value = true
if (props.problemId) {
paragraphApi
.postProblem(props.datasetId || id, documentId || props.docId, props.problemId, obj)
.then((res) => {
getProblemList()
problemValue.value = ''
isAddProblem.value = false
})
.catch(() => {
loading.value = false
})
} else {
problemList.value.unshift(obj)
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
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(() => {
problemList.value = []
problemValue.value = ''
@ -162,6 +183,6 @@ defineExpose({
</script>
<style scoped lang="scss">
.question-tag {
width: 217px;
// width: 217px;
}
</style>

View 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>

View 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>

View 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>

View 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('')
// drawerid
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>