mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 10:12:51 +00:00
feat: knowledg
This commit is contained in:
parent
0d06343dc3
commit
7210e5d1d5
|
|
@ -33,7 +33,9 @@
|
|||
<div class="flex-between w-full" @mouseenter.stop="handleMouseEnter(data)">
|
||||
<div class="flex align-center">
|
||||
<AppIcon iconName="app-folder" style="font-size: 16px"></AppIcon>
|
||||
<span class="ml-8 ellipsis" style="max-width:110px" :label="node.label">{{ node.label }}</span>
|
||||
<span class="ml-8 ellipsis" style="max-width: 110px" :label="node.label">{{
|
||||
node.label
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
|
@ -79,12 +81,15 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { onBeforeRouteLeave } from 'vue-router'
|
||||
import type { TreeInstance } from 'element-plus'
|
||||
import CreateFolderDialog from '@/components/folder-tree/CreateFolderDialog.vue'
|
||||
import { t } from '@/locales'
|
||||
import folderApi from '@/api/folder'
|
||||
import { EditionConst } from '@/utils/permission/data'
|
||||
import { hasPermission } from '@/utils/permission/index'
|
||||
import useStore from '@/stores'
|
||||
|
||||
defineOptions({ name: 'FolderTree' })
|
||||
const props = defineProps({
|
||||
data: {
|
||||
|
|
@ -116,6 +121,12 @@ const props = defineProps({
|
|||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
const { folder } = useStore()
|
||||
onBeforeRouteLeave((to, from) => {
|
||||
folder.setCurrentFolder({})
|
||||
})
|
||||
|
||||
interface Tree {
|
||||
name: string
|
||||
children?: Tree[]
|
||||
|
|
|
|||
|
|
@ -1,204 +0,0 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="$t('views.document.generateQuestion.title')"
|
||||
v-model="dialogVisible"
|
||||
width="650"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<div class="content-height">
|
||||
<el-form
|
||||
ref="FormRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
>
|
||||
<div class="update-info flex border-r-4 mb-16 p-8-12">
|
||||
<div class="mt-4">
|
||||
<AppIcon iconName="app-warning-colorful" style="font-size: 16px"></AppIcon>
|
||||
</div>
|
||||
<div class="ml-12 lighter">
|
||||
<p>{{ $t('views.document.generateQuestion.tip1', { data: '{data}' }) }}</p>
|
||||
<p>
|
||||
{{ $t('views.document.generateQuestion.tip2')+ '<question></question>' +
|
||||
$t('views.document.generateQuestion.tip3') }}
|
||||
</p>
|
||||
<p>{{ $t('views.document.generateQuestion.tip4') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<el-form-item
|
||||
:label="$t('views.application.form.aiModel.label')"
|
||||
prop="model_id"
|
||||
>
|
||||
<ModelSelect
|
||||
v-model="form.model_id"
|
||||
:placeholder="$t('views.application.form.aiModel.placeholder')"
|
||||
:options="modelOptions"
|
||||
showFooter
|
||||
:model-type="'LLM'"
|
||||
></ModelSelect>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('views.application.form.prompt.label')"
|
||||
prop="prompt"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.prompt"
|
||||
:placeholder="$t('views.application.form.prompt.placeholder')"
|
||||
:rows="7"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="['document', 'knowledge'].includes(apiType)"
|
||||
:label="$t('components.selectParagraph.title')"
|
||||
prop="state"
|
||||
>
|
||||
<el-radio-group v-model="state" class="radio-block">
|
||||
<el-radio value="error" size="large">{{
|
||||
$t('components.selectParagraph.error')
|
||||
}}</el-radio>
|
||||
<el-radio value="all" size="large">{{ $t('components.selectParagraph.all') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
|
||||
<el-button type="primary" @click="submitHandle(FormRef)" :disabled="!model || loading">
|
||||
{{ $t('common.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import documentApi from '@/api/system-shared/document'
|
||||
import paragraphApi from '@/api/system-shared/paragraph'
|
||||
import knowledgeApi from '@/api/system-shared/knowledge'
|
||||
import useStoreShared from '@/stores/modules-shared-system'
|
||||
import useStore from '@/stores'
|
||||
import { groupBy } from 'lodash'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
import { t } from '@/locales'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id, documentId }, // id为knowledgeID
|
||||
} = route as any
|
||||
const { model } = useStoreShared()
|
||||
const { prompt, user } = useStore()
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const modelOptions = ref<any>(null)
|
||||
const idList = ref<string[]>([])
|
||||
const apiType = ref('') // 文档document或段落paragraph
|
||||
const state = ref<'all' | 'error'>('error')
|
||||
const stateMap = {
|
||||
all: ['0', '1', '2', '3', '4', '5', 'n'],
|
||||
error: ['0', '1', '3', '4', '5', 'n']
|
||||
}
|
||||
const FormRef = ref()
|
||||
const knowledgeId = ref<string>()
|
||||
const userId = user.userInfo?.id as string
|
||||
const form = ref(prompt.get(userId))
|
||||
const rules = reactive({
|
||||
model_id: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.application.form.aiModel.placeholder'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
prompt: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.application.form.prompt.placeholder'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
form.value = prompt.get(userId)
|
||||
FormRef.value?.clearValidate()
|
||||
}
|
||||
})
|
||||
|
||||
const open = (ids: string[], type: string, _knowledgeId?: string) => {
|
||||
knowledgeId.value = _knowledgeId
|
||||
getModel()
|
||||
idList.value = ids
|
||||
apiType.value = type
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const submitHandle = async (formEl: FormInstance) => {
|
||||
if (!formEl) {
|
||||
return
|
||||
}
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
// 保存提示词
|
||||
prompt.save(user.userInfo?.id as string, form.value)
|
||||
if (apiType.value === 'paragraph') {
|
||||
const data = {
|
||||
...form.value,
|
||||
paragraph_id_list: idList.value
|
||||
}
|
||||
paragraphApi.putBatchGenerateRelated(id, documentId, data, loading).then(() => {
|
||||
MsgSuccess(t('views.document.generateQuestion.successMessage'))
|
||||
emit('refresh')
|
||||
dialogVisible.value = false
|
||||
})
|
||||
} else if (apiType.value === 'document') {
|
||||
const data = {
|
||||
...form.value,
|
||||
document_id_list: idList.value,
|
||||
state_list: stateMap[state.value]
|
||||
}
|
||||
documentApi.putBatchGenerateRelated(id, data, loading).then(() => {
|
||||
MsgSuccess(t('views.document.generateQuestion.successMessage'))
|
||||
emit('refresh')
|
||||
dialogVisible.value = false
|
||||
})
|
||||
} else if (apiType.value === 'knowledge') {
|
||||
const data = {
|
||||
...form.value,
|
||||
state_list: stateMap[state.value]
|
||||
}
|
||||
knowledgeApi.putGenerateRelated(id ? id : knowledgeId.value, data, loading).then(() => {
|
||||
MsgSuccess(t('views.document.generateQuestion.successMessage'))
|
||||
dialogVisible.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getModel() {
|
||||
loading.value = true
|
||||
knowledgeApi
|
||||
.getKnowledgeModel()
|
||||
.then((res: any) => {
|
||||
modelOptions.value = groupBy(res?.data, 'provider')
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -135,7 +135,7 @@ const systemRouter = {
|
|||
parentPath: '/system',
|
||||
parentName: 'system',
|
||||
},
|
||||
component: () => import('@/views/shared/knowledge-shared/index.vue'),
|
||||
component: () => import('@/views/system-shared/KnowLedgeSharedIndex.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/shared/tool',
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import type { knowledgeData } from '@/api/type/knowledge'
|
||||
import type { UploadUserFile } from 'element-plus'
|
||||
import knowledgeApi from '@/api/knowledge/knowledge'
|
||||
import type { pageRequest } from '@/api/type/common'
|
||||
import { type Ref } from 'vue'
|
||||
import useUserStore from './user'
|
||||
import useFolderStore from './folder'
|
||||
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
||||
|
||||
export interface knowledgeStateTypes {
|
||||
baseInfo: knowledgeData | null
|
||||
webInfo: any
|
||||
documentsType: string
|
||||
documentsFiles: UploadUserFile[]
|
||||
knowledgeList: knowledgeData[]
|
||||
}
|
||||
|
||||
const useKnowledgeStore = defineStore('knowledge', {
|
||||
|
|
@ -18,6 +21,7 @@ const useKnowledgeStore = defineStore('knowledge', {
|
|||
webInfo: null,
|
||||
documentsType: '',
|
||||
documentsFiles: [],
|
||||
knowledgeList: [],
|
||||
}),
|
||||
actions: {
|
||||
saveBaseInfo(info: knowledgeData | null) {
|
||||
|
|
@ -32,6 +36,34 @@ const useKnowledgeStore = defineStore('knowledge', {
|
|||
saveDocumentsFile(file: UploadUserFile[]) {
|
||||
this.documentsFiles = file
|
||||
},
|
||||
setKnowledgeList(list: any[]) {
|
||||
this.knowledgeList = list
|
||||
},
|
||||
async asyncGetKnowledgeListPage(
|
||||
page: pageRequest,
|
||||
isShared?: boolean | undefined,
|
||||
systemType: 'systemShare' | 'workspace' | 'systemManage' = 'workspace',
|
||||
paramsData: any,
|
||||
loading?: Ref<boolean>,
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const folder = useFolderStore()
|
||||
const user = useUserStore()
|
||||
const params = {
|
||||
folder_id: folder.currentFolder?.id || user.getWorkspaceId(),
|
||||
scope: systemType === 'systemShare' ? 'SHARED' : 'WORKSPACE',
|
||||
...paramsData,
|
||||
}
|
||||
loadSharedApi({ type: 'knowledge', isShared, systemType })
|
||||
.getToolListPage(page, params, loading)
|
||||
.then((res: any) => {
|
||||
resolve(res)
|
||||
})
|
||||
.catch((error: any) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
async asyncGetFolderKnowledge(folder_id?: string, loading?: Ref<boolean>) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const params = {
|
||||
|
|
@ -59,46 +91,6 @@ const useKnowledgeStore = defineStore('knowledge', {
|
|||
})
|
||||
})
|
||||
},
|
||||
// async asyncGetTreeRootKnowledge(loading?: Ref<boolean>) {
|
||||
// const folder = useFolderStore()
|
||||
// return Promise.all([
|
||||
// folder.asyncGetFolder('KNOWLEDGE', {}, loading),
|
||||
// this.asyncGetFolderKnowledge(loading),
|
||||
// ])
|
||||
// .then((res: any) => {
|
||||
// const folderList = res[0].data
|
||||
// const knowledgeList = res[1].data
|
||||
// const arrMap: any = {}
|
||||
// function buildIdMap(arr: any) {
|
||||
// arr.forEach((item: any) => {
|
||||
// arrMap[item.id] = item
|
||||
// // 递归处理子节点
|
||||
// if (item.children && item.children.length > 0) {
|
||||
// buildIdMap(item.children)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// buildIdMap(folderList)
|
||||
// knowledgeList
|
||||
// .filter((v: any) => v.resource_type !== 'folder')
|
||||
// .forEach((item: any) => {
|
||||
// const targetFolder = arrMap[item.folder_id]
|
||||
// if (targetFolder) {
|
||||
// // 检查是否已有相同ID的子节点(避免重复插入)
|
||||
// const existingChild = targetFolder.children.find(
|
||||
// (child: any) => child.id === item.id,
|
||||
// )
|
||||
// if (!existingChild) {
|
||||
// targetFolder.children.push(item)
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// return Promise.resolve(folderList)
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// return Promise.reject(error)
|
||||
// })
|
||||
// },
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@ const useToolStore = defineStore('tool', {
|
|||
},
|
||||
async asyncGetToolListPage(
|
||||
page: pageRequest,
|
||||
isShared?: boolean | undefined,
|
||||
isShared: boolean | undefined,
|
||||
systemType: 'systemShare' | 'workspace' | 'systemManage' = 'workspace',
|
||||
paramsData: any,
|
||||
loading?: Ref<boolean>,
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
@ -25,6 +26,7 @@ const useToolStore = defineStore('tool', {
|
|||
const params = {
|
||||
folder_id: folder.currentFolder?.id || user.getWorkspaceId(),
|
||||
scope: systemType === 'systemShare' ? 'SHARED' : 'WORKSPACE',
|
||||
...paramsData,
|
||||
}
|
||||
loadSharedApi({ type: 'tool', isShared, systemType })
|
||||
.getToolListPage(page, params, loading)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,472 @@
|
|||
<template>
|
||||
<ContentContainer :header="currentFolder?.name">
|
||||
<template #search>
|
||||
<div class="flex">
|
||||
<div class="flex-between complex-search">
|
||||
<el-select
|
||||
class="complex-search__left"
|
||||
v-model="search_type"
|
||||
style="width: 120px"
|
||||
@change="search_type_change"
|
||||
>
|
||||
<el-option :label="$t('common.creator')" value="create_user" />
|
||||
|
||||
<el-option :label="$t('common.name')" value="name" />
|
||||
</el-select>
|
||||
<el-input
|
||||
v-if="search_type === 'name'"
|
||||
v-model="search_form.name"
|
||||
@change="getList"
|
||||
:placeholder="$t('common.searchBar.placeholder')"
|
||||
style="width: 220px"
|
||||
clearable
|
||||
/>
|
||||
<el-select
|
||||
v-else-if="search_type === 'create_user'"
|
||||
v-model="search_form.create_user"
|
||||
@change="getList"
|
||||
clearable
|
||||
style="width: 220px"
|
||||
>
|
||||
<el-option v-for="u in user_options" :key="u.id" :value="u.id" :label="u.username" />
|
||||
</el-select>
|
||||
</div>
|
||||
<el-dropdown trigger="click" v-if="!isShared">
|
||||
<el-button type="primary" class="ml-8" v-if="permissionPrecise.create()">
|
||||
{{ $t('common.create') }}
|
||||
<el-icon class="el-icon--right">
|
||||
<arrow-down />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu class="create-dropdown">
|
||||
<el-dropdown-item @click="openCreateDialog(CreateKnowledgeDialog)">
|
||||
<div class="flex">
|
||||
<el-avatar class="avatar-blue mt-4" shape="square" :size="32">
|
||||
<img src="@/assets/knowledge/icon_document.svg" style="width: 58%" alt="" />
|
||||
</el-avatar>
|
||||
<div class="pre-wrap ml-8">
|
||||
<div class="lighter">
|
||||
{{ $t('views.knowledge.knowledgeType.generalKnowledge') }}
|
||||
</div>
|
||||
<el-text type="info" size="small"
|
||||
>{{ $t('views.knowledge.knowledgeType.generalInfo') }}
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="openCreateDialog(CreateWebKnowledgeDialog)">
|
||||
<div class="flex">
|
||||
<el-avatar class="avatar-purple mt-4" shape="square" :size="32">
|
||||
<img src="@/assets/knowledge/icon_web.svg" style="width: 58%" alt="" />
|
||||
</el-avatar>
|
||||
<div class="pre-wrap ml-8">
|
||||
<div class="lighter">
|
||||
{{ $t('views.knowledge.knowledgeType.webKnowledge') }}
|
||||
</div>
|
||||
<el-text type="info" size="small"
|
||||
>{{ $t('views.knowledge.knowledgeType.webInfo') }}
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="openCreateDialog(CreateLarkKnowledgeDialog)">
|
||||
<div class="flex">
|
||||
<el-avatar
|
||||
class="avatar-purple mt-4"
|
||||
shape="square"
|
||||
:size="32"
|
||||
style="background: none"
|
||||
>
|
||||
<img src="@/assets/knowledge/logo_lark.svg" alt="" />
|
||||
</el-avatar>
|
||||
<div class="pre-wrap ml-8">
|
||||
<div class="lighter">
|
||||
{{ $t('views.knowledge.knowledgeType.larkKnowledge') }}
|
||||
</div>
|
||||
<el-text type="info" size="small"
|
||||
>{{ $t('views.knowledge.knowledgeType.larkInfo') }}
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<div class="flex">
|
||||
<el-avatar
|
||||
class="avatar-purple mt-4"
|
||||
shape="square"
|
||||
:size="32"
|
||||
style="background: none"
|
||||
>
|
||||
<img src="@/assets/knowledge/logo_yuque.svg" alt="" />
|
||||
</el-avatar>
|
||||
<div class="pre-wrap ml-8">
|
||||
<div class="lighter">
|
||||
{{ $t('views.knowledge.knowledgeType.yuqueKnowledge') }}
|
||||
</div>
|
||||
<el-text type="info" size="small"
|
||||
>{{ $t('views.knowledge.knowledgeType.yuqueInfo') }}
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="openCreateFolder" divided>
|
||||
<div class="flex align-center">
|
||||
<AppIcon iconName="app-folder" style="font-size: 32px"></AppIcon>
|
||||
<div class="pre-wrap ml-4">
|
||||
<div class="lighter">
|
||||
{{ $t('components.folder.addFolder') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div
|
||||
v-loading.fullscreen.lock="paginationConfig.current_page === 1 && loading"
|
||||
style="max-height: calc(100vh - 140px)"
|
||||
>
|
||||
<InfiniteScroll
|
||||
:size="knowledgeList.length"
|
||||
:total="paginationConfig.total"
|
||||
:page_size="paginationConfig.page_size"
|
||||
v-model:current_page="paginationConfig.current_page"
|
||||
@load="getList"
|
||||
:loading="loading"
|
||||
>
|
||||
<el-row v-if="knowledgeList.length > 0" :gutter="15" class="w-full">
|
||||
<template v-for="(item, index) in knowledgeList" :key="index">
|
||||
<el-col
|
||||
v-if="item.resource_type === 'folder'"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="12"
|
||||
:lg="8"
|
||||
:xl="6"
|
||||
class="mb-16"
|
||||
>
|
||||
<CardBox
|
||||
:title="item.name"
|
||||
:description="item.desc || $t('common.noData')"
|
||||
class="cursor"
|
||||
@click="clickFolder(item)"
|
||||
>
|
||||
<template #icon>
|
||||
<el-avatar shape="square" :size="32" style="background: none">
|
||||
<AppIcon iconName="app-folder" style="font-size: 32px"></AppIcon>
|
||||
</el-avatar>
|
||||
</template>
|
||||
<template #subTitle>
|
||||
<el-text class="color-secondary lighter" size="small">
|
||||
{{ $t('common.creator') }}: {{ item.nick_name }}
|
||||
</el-text>
|
||||
</template>
|
||||
</CardBox>
|
||||
</el-col>
|
||||
<el-col v-else :xs="24" :sm="12" :md="12" :lg="8" :xl="6" class="mb-16">
|
||||
<CardBox
|
||||
:title="item.name"
|
||||
:description="item.desc"
|
||||
class="cursor"
|
||||
@click="router.push({ path: `/knowledge/${item.id}/${currentFolder.id}/document` })"
|
||||
>
|
||||
<template #icon>
|
||||
<KnowledgeIcon :type="item.type" />
|
||||
</template>
|
||||
<template #subTitle>
|
||||
<el-text class="color-secondary" size="small">
|
||||
{{ $t('common.creator') }}: {{ item.nick_name }}
|
||||
</el-text>
|
||||
</template>
|
||||
<template #tag>
|
||||
<el-tag v-if="isShared" type="info" class="info-tag">
|
||||
{{ t('views.system.shared.label') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="footer-content flex-between">
|
||||
<div>
|
||||
<span class="bold mr-4">{{ item?.document_count || 0 }}</span>
|
||||
<span class="color-secondary">{{
|
||||
$t('views.knowledge.document_count')
|
||||
}}</span>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="bold mr-4">{{ numberFormat(item?.char_length) || 0 }}</span>
|
||||
<span class="color-secondary">{{ $t('common.character') }}</span>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="bold mr-4">{{ item?.application_mapping_count || 0 }}</span>
|
||||
<span class="color-secondary">{{
|
||||
$t('views.knowledge.relatedApp_count')
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #mouseEnter>
|
||||
<div @click.stop v-if="!isShared">
|
||||
<el-dropdown trigger="click">
|
||||
<el-button text @click.stop>
|
||||
<el-icon>
|
||||
<MoreFilled />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
icon="Refresh"
|
||||
@click.stop="syncKnowledge(item)"
|
||||
v-if="item.type === 1 && permissionPrecise.sync(item.id)"
|
||||
>{{ $t('views.knowledge.setting.sync') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
@click.stop="reEmbeddingKnowledge(item)"
|
||||
v-if="permissionPrecise.vector(item.id)"
|
||||
>
|
||||
<AppIcon iconName="app-vectorization"></AppIcon>
|
||||
{{ $t('views.knowledge.setting.vectorization') }}
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-dropdown-item
|
||||
icon="Connection"
|
||||
@click.stop="openGenerateDialog(item)"
|
||||
v-if="permissionPrecise.doc_generate(item.id)"
|
||||
>{{ $t('views.document.generateQuestion.title') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
icon="Setting"
|
||||
@click.stop="
|
||||
router.push({
|
||||
path: `/knowledge/${item.id}/${currentFolder.value}/setting`,
|
||||
})
|
||||
"
|
||||
v-if="permissionPrecise.setting(item.id)"
|
||||
>
|
||||
{{ $t('common.setting') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
@click.stop="exportKnowledge(item)"
|
||||
v-if="permissionPrecise.export(item.id)"
|
||||
>
|
||||
<AppIcon iconName="app-export"></AppIcon
|
||||
>{{ $t('views.document.setting.export') }} Excel
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
@click.stop="exportZipKnowledge(item)"
|
||||
v-if="permissionPrecise.export(item.id)"
|
||||
>
|
||||
<AppIcon iconName="app-export"></AppIcon
|
||||
>{{ $t('views.document.setting.export') }} ZIP</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item
|
||||
icon="Delete"
|
||||
type="danger"
|
||||
@click.stop="deleteKnowledge(item)"
|
||||
v-if="permissionPrecise.delete(item.id)"
|
||||
>
|
||||
{{ $t('common.delete') }}</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
</CardBox>
|
||||
</el-col>
|
||||
</template>
|
||||
</el-row>
|
||||
<el-empty :description="$t('common.noData')" v-else />
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</ContentContainer>
|
||||
|
||||
<component :is="currentCreateDialog" ref="CreateKnowledgeDialogRef" v-if="!isShared" />
|
||||
<CreateFolderDialog ref="CreateFolderDialogRef" @refresh="refreshFolder" v-if="!isShared" />
|
||||
<GenerateRelatedDialog ref="GenerateRelatedDialogRef" />
|
||||
<SyncWebDialog ref="SyncWebDialogRef" v-if="!isShared" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, reactive, shallowRef, nextTick, computed, watch } from 'vue'
|
||||
import CreateKnowledgeDialog from '@/views/knowledge/create-component/CreateKnowledgeDialog.vue'
|
||||
import CreateWebKnowledgeDialog from '@/views/knowledge/create-component/CreateWebKnowledgeDialog.vue'
|
||||
import CreateLarkKnowledgeDialog from '@/views/knowledge/create-component/CreateLarkKnowledgeDialog.vue'
|
||||
import SyncWebDialog from '@/views/knowledge/component/SyncWebDialog.vue'
|
||||
import CreateFolderDialog from '@/components/folder-tree/CreateFolderDialog.vue'
|
||||
import GenerateRelatedDialog from '@/components/generate-related-dialog/index.vue'
|
||||
import KnowledgeApi from '@/api/knowledge/knowledge'
|
||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||
import useStore from '@/stores'
|
||||
import { numberFormat } from '@/utils/common'
|
||||
import { t } from '@/locales'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { FolderSource } from '@/enums/common'
|
||||
import { PermissionConst, RoleConst } from '@/utils/permission/data'
|
||||
import { hasPermission } from '@/utils/permission/index'
|
||||
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
||||
import permissionMap from '@/permission'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { folder, user, knowledge } = useStore()
|
||||
|
||||
const type = computed(() => {
|
||||
if (route.path.includes('shared')) {
|
||||
return 'systemShare'
|
||||
} else if (route.path.includes('resource-management')) {
|
||||
return 'systemManage'
|
||||
} else {
|
||||
return 'workspace'
|
||||
}
|
||||
})
|
||||
const permissionPrecise = computed(() => {
|
||||
return permissionMap['knowledge'][type.value]
|
||||
})
|
||||
|
||||
const isShared = computed(() => {
|
||||
return folder.currentFolder.id === 'share'
|
||||
})
|
||||
const isSystemShare = computed(() => {
|
||||
return type.value === 'systemShare'
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const search_type = ref('name')
|
||||
const search_form = ref<any>({
|
||||
name: '',
|
||||
create_user: '',
|
||||
})
|
||||
|
||||
const user_options = ref<any[]>([])
|
||||
|
||||
const paginationConfig = reactive({
|
||||
current_page: 1,
|
||||
page_size: 30,
|
||||
total: 0,
|
||||
})
|
||||
|
||||
const folderList = ref<any[]>([])
|
||||
const knowledgeList = ref<any[]>([])
|
||||
const currentFolder = ref<any>({})
|
||||
|
||||
const CreateKnowledgeDialogRef = ref()
|
||||
const currentCreateDialog = shallowRef<any>(null)
|
||||
|
||||
function openCreateDialog(data: any) {
|
||||
currentCreateDialog.value = data
|
||||
nextTick(() => {
|
||||
CreateKnowledgeDialogRef.value.open(currentFolder.value)
|
||||
})
|
||||
|
||||
// common.asyncGetValid(ValidType.Dataset, ValidCount.Dataset, loading).then(async (res: any) => {
|
||||
// if (res?.data) {
|
||||
// CreateDatasetDialogRef.value.open()
|
||||
// } else if (res?.code === 400) {
|
||||
// MsgConfirm(t('common.tip'), t('views.knowledge.tip.professionalMessage'), {
|
||||
// cancelButtonText: t('common.confirm'),
|
||||
// confirmButtonText: t('common.professional'),
|
||||
// })
|
||||
// .then(() => {
|
||||
// window.open('https://maxkb.cn/pricing.html', '_blank')
|
||||
// })
|
||||
// .catch(() => {})
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
function reEmbeddingKnowledge(row: any) {
|
||||
KnowledgeApi.putReEmbeddingKnowledge(row.id).then(() => {
|
||||
MsgSuccess(t('common.submitSuccess'))
|
||||
})
|
||||
}
|
||||
|
||||
const SyncWebDialogRef = ref()
|
||||
|
||||
function syncKnowledge(row: any) {
|
||||
SyncWebDialogRef.value.open(row.id)
|
||||
}
|
||||
|
||||
const search_type_change = () => {
|
||||
search_form.value = { name: '', create_user: '' }
|
||||
}
|
||||
|
||||
const GenerateRelatedDialogRef = ref<InstanceType<typeof GenerateRelatedDialog>>()
|
||||
function openGenerateDialog(row: any) {
|
||||
if (GenerateRelatedDialogRef.value) {
|
||||
GenerateRelatedDialogRef.value.open([], 'knowledge', row.id)
|
||||
}
|
||||
}
|
||||
|
||||
const exportKnowledge = (item: any) => {
|
||||
KnowledgeApi.exportKnowledge(item.name, item.id, loading).then((ok) => {
|
||||
MsgSuccess(t('common.exportSuccess'))
|
||||
})
|
||||
}
|
||||
const exportZipKnowledge = (item: any) => {
|
||||
KnowledgeApi.exportZipKnowledge(item.name, item.id, loading).then((ok) => {
|
||||
MsgSuccess(t('common.exportSuccess'))
|
||||
})
|
||||
}
|
||||
|
||||
function deleteKnowledge(row: any) {
|
||||
MsgConfirm(
|
||||
`${t('views.knowledge.delete.confirmTitle')}${row.name} ?`,
|
||||
`${t('views.knowledge.delete.confirmMessage1')} ${row.application_mapping_count} ${t('views.knowledge.delete.confirmMessage2')}`,
|
||||
{
|
||||
confirmButtonText: t('common.confirm'),
|
||||
confirmButtonClass: 'color-danger',
|
||||
},
|
||||
)
|
||||
.then(() => {
|
||||
KnowledgeApi.delKnowledge(row.id, loading).then(() => {
|
||||
const index = knowledgeList.value.findIndex((v) => v.id === row.id)
|
||||
knowledgeList.value.splice(index, 1)
|
||||
MsgSuccess(t('common.deleteSuccess'))
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
// 文件夹相关
|
||||
const CreateFolderDialogRef = ref()
|
||||
function openCreateFolder() {
|
||||
CreateFolderDialogRef.value.open(FolderSource.KNOWLEDGE, currentFolder.value.id)
|
||||
}
|
||||
watch(
|
||||
() => folder.currentFolder,
|
||||
(newValue) => {
|
||||
if (newValue && newValue.id) {
|
||||
knowledge.setKnowledgeList([])
|
||||
getList()
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true },
|
||||
)
|
||||
|
||||
function getList() {
|
||||
const params = {
|
||||
[search_type.value]: search_form.value[search_type.value],
|
||||
}
|
||||
knowledge
|
||||
.asyncGetKnowledgeListPage(paginationConfig, isShared.value, type.value, params, loading)
|
||||
.then((res: any) => {
|
||||
paginationConfig.total = res.data?.total
|
||||
knowledge.setKnowledgeList([...knowledgeList.value, ...res.data.records])
|
||||
})
|
||||
}
|
||||
|
||||
function clickFolder(item: any) {
|
||||
folder.setCurrentFolder(item)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (type.value !== 'workspace') {
|
||||
getList()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -13,321 +13,24 @@
|
|||
@refreshTree="refreshFolder"
|
||||
/>
|
||||
</template>
|
||||
<ContentContainer :header="currentFolder?.name">
|
||||
<template #search>
|
||||
<div class="flex">
|
||||
<div class="flex-between complex-search">
|
||||
<el-select
|
||||
class="complex-search__left"
|
||||
v-model="search_type"
|
||||
style="width: 120px"
|
||||
@change="search_type_change"
|
||||
>
|
||||
<el-option :label="$t('common.creator')" value="create_user" />
|
||||
|
||||
<el-option :label="$t('common.name')" value="name" />
|
||||
</el-select>
|
||||
<el-input
|
||||
v-if="search_type === 'name'"
|
||||
v-model="search_form.name"
|
||||
@change="getList"
|
||||
:placeholder="$t('common.searchBar.placeholder')"
|
||||
style="width: 220px"
|
||||
clearable
|
||||
/>
|
||||
<el-select
|
||||
v-else-if="search_type === 'create_user'"
|
||||
v-model="search_form.create_user"
|
||||
@change="getList"
|
||||
clearable
|
||||
style="width: 220px"
|
||||
>
|
||||
<el-option v-for="u in user_options" :key="u.id" :value="u.id" :label="u.username" />
|
||||
</el-select>
|
||||
</div>
|
||||
<el-dropdown trigger="click" v-if="!isShared">
|
||||
<el-button type="primary" class="ml-8" v-if="permissionPrecise.create()">
|
||||
{{ $t('common.create') }}
|
||||
<el-icon class="el-icon--right">
|
||||
<arrow-down />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu class="create-dropdown">
|
||||
<el-dropdown-item @click="openCreateDialog(CreateKnowledgeDialog)">
|
||||
<div class="flex">
|
||||
<el-avatar class="avatar-blue mt-4" shape="square" :size="32">
|
||||
<img src="@/assets/knowledge/icon_document.svg" style="width: 58%" alt="" />
|
||||
</el-avatar>
|
||||
<div class="pre-wrap ml-8">
|
||||
<div class="lighter">
|
||||
{{ $t('views.knowledge.knowledgeType.generalKnowledge') }}
|
||||
</div>
|
||||
<el-text type="info" size="small"
|
||||
>{{ $t('views.knowledge.knowledgeType.generalInfo') }}
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="openCreateDialog(CreateWebKnowledgeDialog)">
|
||||
<div class="flex">
|
||||
<el-avatar class="avatar-purple mt-4" shape="square" :size="32">
|
||||
<img src="@/assets/knowledge/icon_web.svg" style="width: 58%" alt="" />
|
||||
</el-avatar>
|
||||
<div class="pre-wrap ml-8">
|
||||
<div class="lighter">
|
||||
{{ $t('views.knowledge.knowledgeType.webKnowledge') }}
|
||||
</div>
|
||||
<el-text type="info" size="small"
|
||||
>{{ $t('views.knowledge.knowledgeType.webInfo') }}
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="openCreateDialog(CreateLarkKnowledgeDialog)">
|
||||
<div class="flex">
|
||||
<el-avatar
|
||||
class="avatar-purple mt-4"
|
||||
shape="square"
|
||||
:size="32"
|
||||
style="background: none"
|
||||
>
|
||||
<img src="@/assets/knowledge/logo_lark.svg" alt="" />
|
||||
</el-avatar>
|
||||
<div class="pre-wrap ml-8">
|
||||
<div class="lighter">
|
||||
{{ $t('views.knowledge.knowledgeType.larkKnowledge') }}
|
||||
</div>
|
||||
<el-text type="info" size="small"
|
||||
>{{ $t('views.knowledge.knowledgeType.larkInfo') }}
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<div class="flex">
|
||||
<el-avatar
|
||||
class="avatar-purple mt-4"
|
||||
shape="square"
|
||||
:size="32"
|
||||
style="background: none"
|
||||
>
|
||||
<img src="@/assets/knowledge/logo_yuque.svg" alt="" />
|
||||
</el-avatar>
|
||||
<div class="pre-wrap ml-8">
|
||||
<div class="lighter">
|
||||
{{ $t('views.knowledge.knowledgeType.yuqueKnowledge') }}
|
||||
</div>
|
||||
<el-text type="info" size="small"
|
||||
>{{ $t('views.knowledge.knowledgeType.yuqueInfo') }}
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="openCreateFolder" divided>
|
||||
<div class="flex align-center">
|
||||
<AppIcon iconName="app-folder" style="font-size: 32px"></AppIcon>
|
||||
<div class="pre-wrap ml-4">
|
||||
<div class="lighter">
|
||||
{{ $t('components.folder.addFolder') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
<KnowledgeListContainer>
|
||||
<template #header>
|
||||
<FolderBreadcrumb :folderList="folderList" @click="folderClickHandel" />
|
||||
</template>
|
||||
|
||||
<div
|
||||
v-loading.fullscreen.lock="paginationConfig.current_page === 1 && loading"
|
||||
style="max-height: calc(100vh - 140px)"
|
||||
>
|
||||
<InfiniteScroll
|
||||
:size="knowledgeList.length"
|
||||
:total="paginationConfig.total"
|
||||
:page_size="paginationConfig.page_size"
|
||||
v-model:current_page="paginationConfig.current_page"
|
||||
@load="getList"
|
||||
:loading="loading"
|
||||
>
|
||||
<el-row v-if="knowledgeList.length > 0" :gutter="15" class="w-full">
|
||||
<template v-for="(item, index) in knowledgeList" :key="index">
|
||||
<el-col
|
||||
v-if="item.resource_type === 'folder'"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="12"
|
||||
:lg="8"
|
||||
:xl="6"
|
||||
class="mb-16"
|
||||
>
|
||||
<CardBox
|
||||
:title="item.name"
|
||||
:description="item.desc || $t('common.noData')"
|
||||
class="cursor"
|
||||
@click="clickFolder(item)"
|
||||
>
|
||||
<template #icon>
|
||||
<el-avatar shape="square" :size="32" style="background: none">
|
||||
<AppIcon iconName="app-folder" style="font-size: 32px"></AppIcon>
|
||||
</el-avatar>
|
||||
</template>
|
||||
<template #subTitle>
|
||||
<el-text class="color-secondary lighter" size="small">
|
||||
{{ $t('common.creator') }}: {{ item.nick_name }}
|
||||
</el-text>
|
||||
</template>
|
||||
</CardBox>
|
||||
</el-col>
|
||||
<el-col v-else :xs="24" :sm="12" :md="12" :lg="8" :xl="6" class="mb-16">
|
||||
<CardBox
|
||||
:title="item.name"
|
||||
:description="item.desc"
|
||||
class="cursor"
|
||||
@click="
|
||||
router.push({ path: `/knowledge/${item.id}/${currentFolder.id}/document` })
|
||||
"
|
||||
>
|
||||
<template #icon>
|
||||
<KnowledgeIcon :type="item.type" />
|
||||
</template>
|
||||
<template #subTitle>
|
||||
<el-text class="color-secondary" size="small">
|
||||
{{ $t('common.creator') }}: {{ item.nick_name }}
|
||||
</el-text>
|
||||
</template>
|
||||
<template #tag>
|
||||
<el-tag v-if="isShared" type="info" class="info-tag">
|
||||
{{ t('views.system.shared.label') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="footer-content flex-between">
|
||||
<div>
|
||||
<span class="bold mr-4">{{ item?.document_count || 0 }}</span>
|
||||
<span class="color-secondary">{{
|
||||
$t('views.knowledge.document_count')
|
||||
}}</span>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="bold mr-4">{{ numberFormat(item?.char_length) || 0 }}</span>
|
||||
<span class="color-secondary">{{ $t('common.character') }}</span>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="bold mr-4">{{ item?.application_mapping_count || 0 }}</span>
|
||||
<span class="color-secondary">{{
|
||||
$t('views.knowledge.relatedApp_count')
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #mouseEnter>
|
||||
<div @click.stop v-if="!isShared">
|
||||
<el-dropdown trigger="click">
|
||||
<el-button text @click.stop>
|
||||
<el-icon>
|
||||
<MoreFilled />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
icon="Refresh"
|
||||
@click.stop="syncKnowledge(item)"
|
||||
v-if="item.type === 1 && permissionPrecise.sync(item.id)"
|
||||
>{{ $t('views.knowledge.setting.sync') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
@click.stop="reEmbeddingKnowledge(item)"
|
||||
v-if="permissionPrecise.vector(item.id)"
|
||||
>
|
||||
<AppIcon iconName="app-vectorization"></AppIcon>
|
||||
{{ $t('views.knowledge.setting.vectorization') }}
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-dropdown-item
|
||||
icon="Connection"
|
||||
@click.stop="openGenerateDialog(item)"
|
||||
v-if="permissionPrecise.doc_generate(item.id)"
|
||||
>{{ $t('views.document.generateQuestion.title') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
icon="Setting"
|
||||
@click.stop="
|
||||
router.push({
|
||||
path: `/knowledge/${item.id}/${currentFolder.value}/setting`,
|
||||
})
|
||||
"
|
||||
v-if="permissionPrecise.setting(item.id)"
|
||||
>
|
||||
{{ $t('common.setting') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
@click.stop="exportKnowledge(item)"
|
||||
v-if="permissionPrecise.export(item.id)"
|
||||
>
|
||||
<AppIcon iconName="app-export"></AppIcon
|
||||
>{{ $t('views.document.setting.export') }} Excel
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
@click.stop="exportZipKnowledge(item)"
|
||||
v-if="permissionPrecise.export(item.id)"
|
||||
>
|
||||
<AppIcon iconName="app-export"></AppIcon
|
||||
>{{ $t('views.document.setting.export') }} ZIP</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item
|
||||
icon="Delete"
|
||||
type="danger"
|
||||
@click.stop="deleteKnowledge(item)"
|
||||
v-if="permissionPrecise.delete(item.id)"
|
||||
>
|
||||
{{ $t('common.delete') }}</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
</CardBox>
|
||||
</el-col>
|
||||
</template>
|
||||
</el-row>
|
||||
<el-empty :description="$t('common.noData')" v-else />
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</ContentContainer>
|
||||
|
||||
<component :is="currentCreateDialog" ref="CreateKnowledgeDialogRef" v-if="!isShared" />
|
||||
<CreateFolderDialog ref="CreateFolderDialogRef" @refresh="refreshFolder" v-if="!isShared" />
|
||||
<GenerateRelatedDialog ref="GenerateRelatedDialogRef" />
|
||||
<SyncWebDialog ref="SyncWebDialogRef" v-if="!isShared" />
|
||||
</KnowledgeListContainer>
|
||||
</LayoutContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, reactive, shallowRef, nextTick, computed } from 'vue'
|
||||
import CreateKnowledgeDialog from './create-component/CreateKnowledgeDialog.vue'
|
||||
import CreateWebKnowledgeDialog from './create-component/CreateWebKnowledgeDialog.vue'
|
||||
import CreateLarkKnowledgeDialog from './create-component/CreateLarkKnowledgeDialog.vue'
|
||||
import SyncWebDialog from './component/SyncWebDialog.vue'
|
||||
import CreateFolderDialog from '@/components/folder-tree/CreateFolderDialog.vue'
|
||||
import GenerateRelatedDialog from '@/components/generate-related-dialog/index.vue'
|
||||
import KnowledgeListContainer from '@/views/knowledge/component/KnowledgeListContainer.vue'
|
||||
import KnowledgeApi from '@/api/knowledge/knowledge'
|
||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||
import useStore from '@/stores'
|
||||
import { numberFormat } from '@/utils/common'
|
||||
import { t } from '@/locales'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { FolderSource } from '@/enums/common'
|
||||
import { PermissionConst, RoleConst } from '@/utils/permission/data'
|
||||
import { hasPermission } from '@/utils/permission/index'
|
||||
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
||||
import permissionMap from '@/permission'
|
||||
|
||||
const router = useRouter()
|
||||
import { useRoute } from 'vue-router'
|
||||
import useStore from '@/stores'
|
||||
const route = useRoute()
|
||||
const { folder, user } = useStore()
|
||||
const { folder, knowledge } = useStore()
|
||||
|
||||
const type = computed(() => {
|
||||
if (route.path.includes('shared')) {
|
||||
|
|
@ -341,133 +44,11 @@ const type = computed(() => {
|
|||
const permissionPrecise = computed(() => {
|
||||
return permissionMap['knowledge'][type.value]
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const search_type = ref('name')
|
||||
const search_form = ref<any>({
|
||||
name: '',
|
||||
create_user: '',
|
||||
})
|
||||
|
||||
const user_options = ref<any[]>([])
|
||||
|
||||
const paginationConfig = reactive({
|
||||
current_page: 1,
|
||||
page_size: 30,
|
||||
total: 0,
|
||||
})
|
||||
|
||||
const folderList = ref<any[]>([])
|
||||
const knowledgeList = ref<any[]>([])
|
||||
const currentFolder = ref<any>({})
|
||||
|
||||
const CreateKnowledgeDialogRef = ref()
|
||||
const currentCreateDialog = shallowRef<any>(null)
|
||||
|
||||
const isShared = computed(() => {
|
||||
return currentFolder.value.id === 'share'
|
||||
})
|
||||
function openCreateDialog(data: any) {
|
||||
currentCreateDialog.value = data
|
||||
nextTick(() => {
|
||||
CreateKnowledgeDialogRef.value.open(currentFolder.value)
|
||||
})
|
||||
|
||||
// common.asyncGetValid(ValidType.Dataset, ValidCount.Dataset, loading).then(async (res: any) => {
|
||||
// if (res?.data) {
|
||||
// CreateDatasetDialogRef.value.open()
|
||||
// } else if (res?.code === 400) {
|
||||
// MsgConfirm(t('common.tip'), t('views.knowledge.tip.professionalMessage'), {
|
||||
// cancelButtonText: t('common.confirm'),
|
||||
// confirmButtonText: t('common.professional'),
|
||||
// })
|
||||
// .then(() => {
|
||||
// window.open('https://maxkb.cn/pricing.html', '_blank')
|
||||
// })
|
||||
// .catch(() => {})
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
function reEmbeddingKnowledge(row: any) {
|
||||
KnowledgeApi.putReEmbeddingKnowledge(row.id).then(() => {
|
||||
MsgSuccess(t('common.submitSuccess'))
|
||||
})
|
||||
}
|
||||
|
||||
const SyncWebDialogRef = ref()
|
||||
|
||||
function syncKnowledge(row: any) {
|
||||
SyncWebDialogRef.value.open(row.id)
|
||||
}
|
||||
|
||||
const search_type_change = () => {
|
||||
search_form.value = { name: '', create_user: '' }
|
||||
}
|
||||
|
||||
function getList() {
|
||||
const params = {
|
||||
folder_id: currentFolder.value?.id || user.getWorkspaceId(),
|
||||
[search_type.value]: search_form.value[search_type.value],
|
||||
}
|
||||
|
||||
loadSharedApi({ type: 'knowledge', isShared: isShared.value })
|
||||
.getKnowledgeListPage(paginationConfig, params, loading)
|
||||
.then((res: any) => {
|
||||
paginationConfig.total = res.data.total
|
||||
knowledgeList.value = [...knowledgeList.value, ...res.data.records]
|
||||
})
|
||||
}
|
||||
|
||||
function clickFolder(item: any) {
|
||||
currentFolder.value.id = item.id
|
||||
knowledgeList.value = []
|
||||
getList()
|
||||
}
|
||||
|
||||
const GenerateRelatedDialogRef = ref<InstanceType<typeof GenerateRelatedDialog>>()
|
||||
function openGenerateDialog(row: any) {
|
||||
if (GenerateRelatedDialogRef.value) {
|
||||
GenerateRelatedDialogRef.value.open([], 'knowledge', row.id)
|
||||
}
|
||||
}
|
||||
|
||||
const exportKnowledge = (item: any) => {
|
||||
KnowledgeApi.exportKnowledge(item.name, item.id, loading).then((ok) => {
|
||||
MsgSuccess(t('common.exportSuccess'))
|
||||
})
|
||||
}
|
||||
const exportZipKnowledge = (item: any) => {
|
||||
KnowledgeApi.exportZipKnowledge(item.name, item.id, loading).then((ok) => {
|
||||
MsgSuccess(t('common.exportSuccess'))
|
||||
})
|
||||
}
|
||||
|
||||
function deleteKnowledge(row: any) {
|
||||
MsgConfirm(
|
||||
`${t('views.knowledge.delete.confirmTitle')}${row.name} ?`,
|
||||
`${t('views.knowledge.delete.confirmMessage1')} ${row.application_mapping_count} ${t('views.knowledge.delete.confirmMessage2')}`,
|
||||
{
|
||||
confirmButtonText: t('common.confirm'),
|
||||
confirmButtonClass: 'color-danger',
|
||||
},
|
||||
)
|
||||
.then(() => {
|
||||
KnowledgeApi.delKnowledge(row.id, loading).then(() => {
|
||||
const index = knowledgeList.value.findIndex((v) => v.id === row.id)
|
||||
knowledgeList.value.splice(index, 1)
|
||||
MsgSuccess(t('common.deleteSuccess'))
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
// 文件夹相关
|
||||
const CreateFolderDialogRef = ref()
|
||||
function openCreateFolder() {
|
||||
CreateFolderDialogRef.value.open(FolderSource.KNOWLEDGE, currentFolder.value.id)
|
||||
}
|
||||
function getFolder(bool?: boolean) {
|
||||
const params = {}
|
||||
folder.asyncGetFolder(FolderSource.KNOWLEDGE, params, loading).then((res: any) => {
|
||||
|
|
@ -475,17 +56,18 @@ function getFolder(bool?: boolean) {
|
|||
if (bool) {
|
||||
// 初始化刷新
|
||||
currentFolder.value = res.data?.[0] || {}
|
||||
folder.setCurrentFolder(currentFolder.value)
|
||||
}
|
||||
getList()
|
||||
})
|
||||
}
|
||||
|
||||
function folderClickHandel(row: any) {
|
||||
currentFolder.value = row
|
||||
knowledgeList.value = []
|
||||
getList()
|
||||
folder.setCurrentFolder(currentFolder.value)
|
||||
knowledge.setKnowledgeList([])
|
||||
}
|
||||
|
||||
function refreshFolder() {
|
||||
knowledgeList.value = []
|
||||
getFolder()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ import CreateLarkKnowledgeDialog from './create-component/CreateLarkKnowledgeDia
|
|||
import CreateFolderDialog from '@/components/folder-tree/CreateFolderDialog.vue'
|
||||
import GenerateRelatedDialog from '@/components/generate-related-dialog/index.vue'
|
||||
import KnowledgeApi from '@/api/resource-management/knowledge'
|
||||
import SharedWorkspace from '@/views/shared/knowledge-shared/SharedWorkspace.vue'
|
||||
|
||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||
import useStore from '@/stores/modules-resource-management'
|
||||
import { numberFormat } from '@/utils/common'
|
||||
|
|
|
|||
|
|
@ -1,290 +0,0 @@
|
|||
<template>
|
||||
<LayoutContainer :header="$t('views.document.importDocument')" class="create-dataset">
|
||||
<template #backButton>
|
||||
<back-button @click="back"></back-button>
|
||||
</template>
|
||||
<div class="create-dataset__main flex" v-loading="loading">
|
||||
<div class="create-dataset__component main-calc-height">
|
||||
<div class="upload-document p-24" style="min-width: 850px">
|
||||
<h4 class="title-decoration-1 mb-8">
|
||||
{{ $t('views.document.feishu.selectDocument') }}
|
||||
</h4>
|
||||
<el-form
|
||||
ref="FormRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
>
|
||||
<div class="mt-16 mb-16">
|
||||
<el-radio-group v-model="form.fileType" class="app-radio-button-group">
|
||||
<el-radio-button value="txt"
|
||||
>{{ $t('views.document.fileType.txt.label') }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div class="update-info flex p-8-12 border-r-4 mb-16">
|
||||
<div class="mt-4">
|
||||
<AppIcon iconName="app-warning-colorful" style="font-size: 16px"></AppIcon>
|
||||
</div>
|
||||
<div class="ml-16 lighter">
|
||||
<p>{{ $t('views.document.feishu.tip1') }}</p>
|
||||
<p>{{ $t('views.document.feishu.tip2') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-never border-r-4 mb-16">
|
||||
<el-checkbox
|
||||
v-model="allCheck"
|
||||
:label="$t('views.document.feishu.allCheck')"
|
||||
size="large"
|
||||
class="ml-24"
|
||||
@change="handleAllCheckChange"
|
||||
/>
|
||||
</div>
|
||||
<div style="height: calc(100vh - 450px)">
|
||||
<el-scrollbar>
|
||||
<el-tree
|
||||
:props="props"
|
||||
:load="loadNode"
|
||||
lazy
|
||||
show-checkbox
|
||||
node-key="token"
|
||||
ref="treeRef"
|
||||
>
|
||||
<template #default="{ node, data }">
|
||||
<div class="flex align-center lighter">
|
||||
<img
|
||||
src="@/assets/fileType/file-icon.svg"
|
||||
alt=""
|
||||
height="20"
|
||||
v-if="data.type === 'folder'"
|
||||
/>
|
||||
<img
|
||||
src="@/assets/fileType/docx-icon.svg"
|
||||
alt=""
|
||||
height="22"
|
||||
v-else-if="data.type === 'docx' || data.name.endsWith('.docx')"
|
||||
/>
|
||||
<img
|
||||
src="@/assets/fileType/xlsx-icon.svg"
|
||||
alt=""
|
||||
height="22"
|
||||
v-else-if="data.type === 'sheet' || data.name.endsWith('.xlsx')"
|
||||
/>
|
||||
<img
|
||||
src="@/assets/fileType/xls-icon.svg"
|
||||
alt=""
|
||||
height="22"
|
||||
v-else-if="data.name.endsWith('xls')"
|
||||
/>
|
||||
<img
|
||||
src="@/assets/fileType/csv-icon.svg"
|
||||
alt=""
|
||||
height="22"
|
||||
v-else-if="data.name.endsWith('csv')"
|
||||
/>
|
||||
<img
|
||||
src="@/assets/fileType/pdf-icon.svg"
|
||||
alt=""
|
||||
height="22"
|
||||
v-else-if="data.name.endsWith('.pdf')"
|
||||
/>
|
||||
<img
|
||||
src="@/assets/fileType/html-icon.svg"
|
||||
alt=""
|
||||
height="22"
|
||||
v-else-if="data.name.endsWith('.html')"
|
||||
/>
|
||||
<img
|
||||
src="@/assets/fileType/txt-icon.svg"
|
||||
alt=""
|
||||
height="22"
|
||||
v-else-if="data.name.endsWith('.txt')"
|
||||
/>
|
||||
<img
|
||||
src="@/assets/fileType/zip-icon.svg"
|
||||
alt=""
|
||||
height="22"
|
||||
v-else-if="data.name.endsWith('.zip')"
|
||||
/>
|
||||
<img
|
||||
src="@/assets/fileType/md-icon.svg"
|
||||
alt=""
|
||||
height="22"
|
||||
v-else-if="data.name.endsWith('.md')"
|
||||
/>
|
||||
|
||||
<span class="ml-4">{{ node.label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="create-dataset__footer text-right border-t">
|
||||
<el-button @click="router.go(-1)">{{ $t('common.cancel') }}</el-button>
|
||||
|
||||
<el-button @click="submit" type="primary" :disabled="disabled">
|
||||
{{ $t('views.document.buttons.import') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</LayoutContainer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onUnmounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { MsgConfirm, MsgSuccess, MsgWarning } from '@/utils/message'
|
||||
import { getImgUrl } from '@/utils/utils'
|
||||
import { t } from '@/locales'
|
||||
import type Node from 'element-plus/es/components/tree/src/model/node'
|
||||
import documentApi from '@/api/system-shared/document'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const {
|
||||
query: { id, folder_token }, // id为knowledgeID,有id的是上传文档 folder_token为飞书文件夹token
|
||||
} = route
|
||||
const knowledgeId = id as string
|
||||
const folderToken = folder_token as string
|
||||
|
||||
const loading = ref(false)
|
||||
const disabled = ref(false)
|
||||
const allCheck = ref(false)
|
||||
const treeRef = ref<any>(null)
|
||||
|
||||
interface Tree {
|
||||
name: string
|
||||
leaf?: boolean
|
||||
type: string
|
||||
token: string
|
||||
is_exist: boolean
|
||||
}
|
||||
|
||||
const form = ref({
|
||||
fileType: 'txt',
|
||||
fileList: [] as any,
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
fileList: [
|
||||
{ required: true, message: t('views.document.upload.requiredMessage'), trigger: 'change' },
|
||||
],
|
||||
})
|
||||
|
||||
const props = {
|
||||
label: 'name',
|
||||
children: 'zones',
|
||||
isLeaf: (data: any) => data.type !== 'folder',
|
||||
disabled: (data: any) => data.is_exist,
|
||||
}
|
||||
|
||||
const loadNode = (node: Node, resolve: (nodeData: Tree[]) => void) => {
|
||||
const token = node.level === 0 ? folderToken : node.data.token // 根节点使用 folder_token,其他节点使用 node.data.token
|
||||
documentApi
|
||||
.getLarkDocumentList(knowledgeId, token, {}, loading)
|
||||
.then((res: any) => {
|
||||
const nodes = res.data.files as Tree[]
|
||||
resolve(nodes)
|
||||
nodes.forEach((childNode) => {
|
||||
if (childNode.is_exist) {
|
||||
treeRef.value?.setChecked(childNode.token, true, false)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
.catch((err) => {
|
||||
console.error('Failed to load tree nodes:', err)
|
||||
})
|
||||
}
|
||||
|
||||
const handleAllCheckChange = (checked: boolean) => {
|
||||
if (checked) {
|
||||
// 获取所有已加载的节点
|
||||
const nodes = Object.values(treeRef.value?.store.nodesMap || {}) as any[]
|
||||
nodes.forEach((node) => {
|
||||
// 只选择未禁用且是文件的节点
|
||||
if (!node.disabled) {
|
||||
treeRef.value?.setChecked(node.data, true, false)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
treeRef.value?.setCheckedKeys([])
|
||||
}
|
||||
}
|
||||
|
||||
function submit() {
|
||||
loading.value = true
|
||||
disabled.value = true
|
||||
// 选中的节点的token
|
||||
const checkedNodes = treeRef.value?.getCheckedNodes() || []
|
||||
const filteredNodes = checkedNodes.filter((node: any) => !node.is_exist)
|
||||
const newList = filteredNodes.map((node: any) => {
|
||||
return {
|
||||
name: node.name,
|
||||
token: node.token,
|
||||
type: node.type,
|
||||
}
|
||||
})
|
||||
if (newList.length === 0) {
|
||||
disabled.value = false
|
||||
MsgWarning(t('views.document.feishu.errorMessage1'))
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
documentApi
|
||||
.importLarkDocument(knowledgeId, newList, loading)
|
||||
.then((res) => {
|
||||
MsgSuccess(t('views.document.tip.importMessage'))
|
||||
disabled.value = false
|
||||
router.go(-1)
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to load tree nodes:', err)
|
||||
})
|
||||
.finally(() => {
|
||||
disabled.value = false
|
||||
})
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
function back() {
|
||||
router.go(-1)
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.create-dataset {
|
||||
&__component {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
padding: 16px 24px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: #ffffff;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.upload-document {
|
||||
width: 70%;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.xlsx-icon {
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
stroke: #000000 !important;
|
||||
fill: #ffffff !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,216 +0,0 @@
|
|||
<template>
|
||||
<div class="upload-document p-12-24">
|
||||
<div class="flex align-center mb-16">
|
||||
<back-button to="-1" style="margin-left: -4px"></back-button>
|
||||
<h3 style="display: inline-block">{{ $t('views.document.uploadDocument') }}</h3>
|
||||
</div>
|
||||
<el-card style="--el-card-padding: 0">
|
||||
<div class="upload-document__main flex" v-loading="loading">
|
||||
<div class="upload-document__component main-calc-height">
|
||||
<el-scrollbar>
|
||||
<template v-if="active === 0">
|
||||
<div class="upload-component p-24">
|
||||
<!-- 上传文档 -->
|
||||
<UploadComponent ref="UploadComponentRef" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="active === 1">
|
||||
<SetRules ref="SetRulesRef" />
|
||||
</template>
|
||||
<template v-else-if="active === 2">
|
||||
<ResultSuccess :data="successInfo" />
|
||||
</template>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<div class="upload-document__footer text-right border-t" v-if="active !== 2">
|
||||
<el-button @click="router.go(-1)" :disabled="SetRulesRef?.loading || loading">{{
|
||||
$t('common.cancel')
|
||||
}}</el-button>
|
||||
<el-button @click="prev" v-if="active === 1" :disabled="SetRulesRef?.loading || loading">{{
|
||||
$t('views.document.buttons.prev')
|
||||
}}</el-button>
|
||||
<el-button
|
||||
@click="next"
|
||||
type="primary"
|
||||
v-if="active === 0"
|
||||
:disabled="SetRulesRef?.loading || loading"
|
||||
>
|
||||
{{
|
||||
documentsType === 'txt'
|
||||
? $t('views.document.buttons.next')
|
||||
: $t('views.document.buttons.import')
|
||||
}}
|
||||
</el-button>
|
||||
<el-button
|
||||
@click="submit"
|
||||
type="primary"
|
||||
v-if="active === 1"
|
||||
:disabled="SetRulesRef?.loading || loading"
|
||||
>
|
||||
{{ $t('views.document.buttons.import') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onUnmounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import SetRules from './upload/SetRules.vue'
|
||||
import ResultSuccess from './upload/ResultSuccess.vue'
|
||||
import UploadComponent from './upload/UploadComponent.vue'
|
||||
import documentApi from '@/api/system-shared/document'
|
||||
import { MsgConfirm, MsgSuccess } from '@/utils/message'
|
||||
import { t } from '@/locales'
|
||||
import useStore from '@/stores/modules-shared-system'
|
||||
const { knowledge, document } = useStore()
|
||||
const documentsFiles = computed(() => knowledge.documentsFiles)
|
||||
const documentsType = computed(() => knowledge.documentsType)
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const {
|
||||
query: { id }, // id为knowledgeID,有id的是上传文档
|
||||
} = route
|
||||
|
||||
const SetRulesRef = ref()
|
||||
const UploadComponentRef = ref()
|
||||
|
||||
const loading = ref(false)
|
||||
const disabled = ref(false)
|
||||
const active = ref(0)
|
||||
const successInfo = ref<any>(null)
|
||||
async function next() {
|
||||
disabled.value = true
|
||||
if (await UploadComponentRef.value.validate()) {
|
||||
if (documentsType.value === 'QA') {
|
||||
const fd = new FormData()
|
||||
documentsFiles.value.forEach((item: any) => {
|
||||
if (item?.raw) {
|
||||
fd.append('file', item?.raw)
|
||||
}
|
||||
})
|
||||
if (id) {
|
||||
// QA文档上传
|
||||
documentApi.postQADocument(id as string, fd, loading).then((res) => {
|
||||
MsgSuccess(t('common.submitSuccess'))
|
||||
clearStore()
|
||||
router.push({ path: `/knowledge/${id}/document` })
|
||||
})
|
||||
}
|
||||
} else if (documentsType.value === 'table') {
|
||||
const fd = new FormData()
|
||||
documentsFiles.value.forEach((item: any) => {
|
||||
if (item?.raw) {
|
||||
fd.append('file', item?.raw)
|
||||
}
|
||||
})
|
||||
if (id) {
|
||||
// table文档上传
|
||||
documentApi.postTableDocument(id as string, fd, loading).then((res) => {
|
||||
MsgSuccess(t('common.submitSuccess'))
|
||||
clearStore()
|
||||
router.push({ path: `/knowledge/${id}/document` })
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (active.value++ > 2) active.value = 0
|
||||
}
|
||||
} else {
|
||||
disabled.value = false
|
||||
}
|
||||
}
|
||||
const prev = () => {
|
||||
active.value = 0
|
||||
}
|
||||
|
||||
function clearStore() {
|
||||
knowledge.saveDocumentsFile([])
|
||||
knowledge.saveDocumentsType('')
|
||||
}
|
||||
function submit() {
|
||||
loading.value = true
|
||||
const documents = [] as any
|
||||
SetRulesRef.value?.paragraphList.map((item: any) => {
|
||||
if (!SetRulesRef.value?.checkedConnect) {
|
||||
item.content.map((v: any) => {
|
||||
delete v['problem_list']
|
||||
})
|
||||
}
|
||||
documents.push({
|
||||
name: item.name,
|
||||
paragraphs: item.content,
|
||||
source_file_id: item.source_file_id,
|
||||
})
|
||||
})
|
||||
|
||||
if (id) {
|
||||
// 上传文档
|
||||
document
|
||||
.asyncPutDocument(id as string, documents)
|
||||
.then(() => {
|
||||
MsgSuccess(t('common.submitSuccess'))
|
||||
clearStore()
|
||||
router.push({ path: `/knowledge/system/${id}/documentShared` })
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
function back() {
|
||||
if (documentsFiles.value?.length > 0) {
|
||||
MsgConfirm(t('common.tip'), t('views.document.tip.saveMessage'), {
|
||||
confirmButtonText: t('common.confirm'),
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
router.go(-1)
|
||||
clearStore()
|
||||
})
|
||||
.catch(() => {})
|
||||
} else {
|
||||
router.go(-1)
|
||||
}
|
||||
}
|
||||
onUnmounted(() => {
|
||||
clearStore()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.upload-document {
|
||||
&__steps {
|
||||
min-width: 450px;
|
||||
max-width: 800px;
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
padding-right: 60px;
|
||||
|
||||
:deep(.el-step__line) {
|
||||
left: 64% !important;
|
||||
right: -33% !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__component {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
&__footer {
|
||||
padding: 16px 24px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: #ffffff;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.upload-component {
|
||||
width: 70%;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="$t('components.selectParagraph.title')"
|
||||
:before-close="close"
|
||||
width="450"
|
||||
>
|
||||
<el-radio-group v-model="state" class="radio-block">
|
||||
<el-radio value="error" size="large">{{
|
||||
$t('components.selectParagraph.error')
|
||||
}}</el-radio>
|
||||
<el-radio value="all" size="large">{{ $t('components.selectParagraph.all') }}</el-radio>
|
||||
</el-radio-group>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="close">{{ $t('common.cancel') }} </el-button>
|
||||
<el-button type="primary" @click="submit"> {{ $t('common.submit') }} </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const state = ref<'all' | 'error'>('error')
|
||||
const stateMap = {
|
||||
all: ['0', '1', '2', '3', '4', '5', 'n'],
|
||||
error: ['0', '1', '3', '4', '5', 'n']
|
||||
}
|
||||
const submit_handle = ref<(stateList: Array<string>) => void>()
|
||||
const submit = () => {
|
||||
if (submit_handle.value) {
|
||||
submit_handle.value(stateMap[state.value])
|
||||
}
|
||||
close()
|
||||
}
|
||||
|
||||
const open = (handle: (stateList: Array<string>) => void) => {
|
||||
submit_handle.value = handle
|
||||
dialogVisible.value = true
|
||||
}
|
||||
const close = () => {
|
||||
submit_handle.value = undefined
|
||||
dialogVisible.value = false
|
||||
}
|
||||
defineExpose({ open, close })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,233 +0,0 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="title"
|
||||
v-model="dialogVisible"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:destroy-on-close="true"
|
||||
width="550"
|
||||
>
|
||||
<el-form
|
||||
label-position="top"
|
||||
ref="webFormRef"
|
||||
:rules="rules"
|
||||
:model="form"
|
||||
require-asterisk-position="right"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$t('views.document.form.source_url.label')"
|
||||
prop="source_url"
|
||||
v-if="isImport"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.source_url"
|
||||
:placeholder="$t('views.document.form.source_url.placeholder')"
|
||||
:rows="10"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-else-if="!isImport && documentType === '1'"
|
||||
:label="$t('views.document.form.source_url.label')"
|
||||
prop="source_url"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.source_url"
|
||||
:placeholder="$t('views.document.form.source_url.requiredMessage')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('views.document.form.selector.label')" v-if="documentType === '1'">
|
||||
<el-input
|
||||
v-model="form.selector"
|
||||
:placeholder="$t('views.document.form.selector.placeholder')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!isImport">
|
||||
<template #label>
|
||||
<div class="flex align-center">
|
||||
<span class="mr-4">{{ $t('views.document.form.hit_handling_method.label') }}</span>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('views.document.form.hit_handling_method.tooltip')"
|
||||
placement="right"
|
||||
>
|
||||
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-radio-group v-model="form.hit_handling_method" class="radio-block mt-4">
|
||||
<template v-for="(value, key) of hitHandlingMethod" :key="key">
|
||||
<el-radio :value="key">{{ $t(value) }} </el-radio>
|
||||
</template>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
prop="directly_return_similarity"
|
||||
v-if="!isImport && form.hit_handling_method === 'directly_return'"
|
||||
>
|
||||
<div class="lighter w-full" style="margin-top: -20px">
|
||||
<span>{{ $t('views.document.form.similarity.label') }}</span>
|
||||
<el-input-number
|
||||
v-model="form.directly_return_similarity"
|
||||
:min="0"
|
||||
:max="1"
|
||||
:precision="3"
|
||||
:step="0.1"
|
||||
:value-on-clear="0"
|
||||
controls-position="right"
|
||||
size="small"
|
||||
class="ml-4 mr-4"
|
||||
/><span>{{ $t('views.document.form.similarity.placeholder') }}</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
|
||||
<el-button type="primary" @click="submit(webFormRef)" :loading="loading">
|
||||
{{ $t('common.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { FormInstance } from 'element-plus'
|
||||
import documentApi from '@/api/system-shared/document'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
import { hitHandlingMethod } from '@/enums/document'
|
||||
import { t } from '@/locales'
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id }
|
||||
} = route as any
|
||||
|
||||
const props = defineProps({
|
||||
title: String
|
||||
})
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
const webFormRef = ref()
|
||||
const loading = ref<boolean>(false)
|
||||
const isImport = ref<boolean>(false)
|
||||
const form = ref<any>({
|
||||
source_url: '',
|
||||
selector: '',
|
||||
hit_handling_method: 'optimization',
|
||||
directly_return_similarity: 0.9
|
||||
})
|
||||
|
||||
// 文档设置
|
||||
const documentId = ref('')
|
||||
const documentType = ref<string | number>('') //文档类型:1: web文档;0:普通文档
|
||||
|
||||
// 批量设置
|
||||
const documentList = ref<Array<string>>([])
|
||||
|
||||
const rules = reactive({
|
||||
source_url: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.document.form.source_url.requiredMessage'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
directly_return_similarity: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.document.form.similarity.requiredMessage'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
form.value = {
|
||||
source_url: '',
|
||||
selector: '',
|
||||
hit_handling_method: 'optimization',
|
||||
directly_return_similarity: 0.9
|
||||
}
|
||||
isImport.value = false
|
||||
documentType.value = ''
|
||||
documentId.value = ''
|
||||
documentList.value = []
|
||||
}
|
||||
})
|
||||
|
||||
const open = (row: any, list: Array<string>) => {
|
||||
if (row) {
|
||||
documentType.value = row.type
|
||||
documentId.value = row.id
|
||||
form.value = {
|
||||
hit_handling_method: row.hit_handling_method,
|
||||
directly_return_similarity: row.directly_return_similarity,
|
||||
...row.meta
|
||||
}
|
||||
isImport.value = false
|
||||
} else if (list) {
|
||||
// 批量设置
|
||||
documentList.value = list
|
||||
} else {
|
||||
// 导入 只有web文档类型
|
||||
documentType.value = '1'
|
||||
isImport.value = true
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
if (isImport.value) {
|
||||
const obj = {
|
||||
source_url_list: form.value.source_url.split('\n'),
|
||||
selector: form.value.selector
|
||||
}
|
||||
documentApi.postWebDocument(id, obj, loading).then(() => {
|
||||
MsgSuccess(t('views.document.tip.importMessage'))
|
||||
emit('refresh')
|
||||
dialogVisible.value = false
|
||||
})
|
||||
} else {
|
||||
if (documentId.value) {
|
||||
const obj = {
|
||||
hit_handling_method: form.value.hit_handling_method,
|
||||
directly_return_similarity: form.value.directly_return_similarity,
|
||||
meta: {
|
||||
source_url: form.value.source_url,
|
||||
selector: form.value.selector
|
||||
}
|
||||
}
|
||||
documentApi.putDocument(id, documentId.value, obj, loading).then(() => {
|
||||
MsgSuccess(t('common.settingSuccess'))
|
||||
emit('refresh')
|
||||
dialogVisible.value = false
|
||||
})
|
||||
} else if (documentList.value.length > 0) {
|
||||
// 批量设置
|
||||
const obj = {
|
||||
hit_handling_method: form.value.hit_handling_method,
|
||||
directly_return_similarity: form.value.directly_return_similarity,
|
||||
id_list: documentList.value
|
||||
}
|
||||
documentApi.putBatchEditHitHandling(id, obj, loading).then(() => {
|
||||
MsgSuccess(t('common.settingSuccess'))
|
||||
emit('refresh')
|
||||
dialogVisible.value = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="$t('views.chatLog.selectKnowledge')"
|
||||
v-model="dialogVisible"
|
||||
width="600"
|
||||
class="select-knowledge-dialog"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<template #header="{ titleId, titleClass }">
|
||||
<h4 :id="titleId" :class="titleClass">{{ '文档迁移到' }}</h4>
|
||||
</template>
|
||||
<el-form
|
||||
class="p-24"
|
||||
ref="FormRef"
|
||||
:model="form"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-form-item :label="$t('views.chatLog.selectKnowledge')" required>
|
||||
<el-tree-select
|
||||
v-model="form.selectKnowledge"
|
||||
:data="knowledgeList"
|
||||
:props="defaultProps"
|
||||
node-key="id"
|
||||
>
|
||||
<template #default="{ data }">
|
||||
<div class="flex align-center">
|
||||
<KnowledgeIcon class="mr-12" :size="20" v-if="data.resource_type" :type="data.type" />
|
||||
<el-avatar v-else class="mr-12" shape="square" :size="20" style="background: none">
|
||||
<img
|
||||
src="@/assets/knowledge/icon_file-folder_colorful.svg"
|
||||
style="width: 100%"
|
||||
alt=""
|
||||
/>
|
||||
</el-avatar>
|
||||
|
||||
{{ data.name }}
|
||||
</div>
|
||||
</template>
|
||||
</el-tree-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="submitHandle"
|
||||
:disabled="!form.selectKnowledge || loading"
|
||||
>
|
||||
{{ $t('common.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import documentApi from '@/api/system-shared/document'
|
||||
|
||||
import useStore from '@/stores/modules-shared-system'
|
||||
const { knowledge } = useStore()
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id }, // id为knowledgeID
|
||||
} = route as any
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const knowledgeList = ref<any>([])
|
||||
const documentList = ref<any>([])
|
||||
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'name',
|
||||
disabled: (data: any, node: any) => {
|
||||
console.log(data, node)
|
||||
return data.id === id || (node?.isLeaf && !data.resource_type)
|
||||
},
|
||||
}
|
||||
|
||||
const form = ref<any>({
|
||||
selectKnowledge: '',
|
||||
})
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
form.value.selectKnowledge = ''
|
||||
knowledgeList.value = []
|
||||
documentList.value = []
|
||||
}
|
||||
})
|
||||
|
||||
const open = (list: any) => {
|
||||
documentList.value = list
|
||||
getKnowledge()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
const submitHandle = () => {
|
||||
documentApi
|
||||
.putMigrateMulDocument(id, form.value.selectKnowledge, documentList.value, loading)
|
||||
.then((res) => {
|
||||
emit('refresh')
|
||||
dialogVisible.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function getKnowledge() {
|
||||
knowledge.asyncGetTreeRootKnowledge(loading).then((res: any) => {
|
||||
knowledgeList.value = res || []
|
||||
console.log(knowledgeList.value)
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.select-knowledge-dialog {
|
||||
padding: 0;
|
||||
.el-dialog__header {
|
||||
padding: 24px 24px 0 24px;
|
||||
}
|
||||
.el-dialog__body {
|
||||
padding: 8px !important;
|
||||
}
|
||||
.el-dialog__footer {
|
||||
padding: 0 24px 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
<template>
|
||||
<el-popover
|
||||
v-model:visible="visible"
|
||||
placement="top"
|
||||
trigger="hover"
|
||||
:popper-style="{ width: 'auto' }"
|
||||
>
|
||||
<template #default
|
||||
><StatusTable
|
||||
v-if="visible"
|
||||
:status="status"
|
||||
:statusMeta="statusMeta"
|
||||
:taskTypeMap="taskTypeMap"
|
||||
:stateMap="stateMap"
|
||||
></StatusTable>
|
||||
</template>
|
||||
<template #reference>
|
||||
<el-text v-if="aggStatus?.value === State.SUCCESS || aggStatus?.value === State.REVOKED">
|
||||
<el-icon class="color-success"><SuccessFilled /></el-icon>
|
||||
{{ stateMap[aggStatus.value](aggStatus.key) }}
|
||||
</el-text>
|
||||
<el-text v-else-if="aggStatus?.value === State.FAILURE">
|
||||
<el-icon class="color-danger"><CircleCloseFilled /></el-icon>
|
||||
{{ stateMap[aggStatus.value](aggStatus.key) }}
|
||||
</el-text>
|
||||
<el-text v-else-if="aggStatus?.value === State.STARTED">
|
||||
<el-icon class="is-loading color-primary"><Loading /></el-icon>
|
||||
{{ stateMap[aggStatus.value](aggStatus.key) }}
|
||||
</el-text>
|
||||
<el-text v-else-if="aggStatus?.value === State.PENDING">
|
||||
<el-icon class="is-loading color-primary"><Loading /></el-icon>
|
||||
{{ stateMap[aggStatus.value](aggStatus.key) }}
|
||||
</el-text>
|
||||
<el-text v-else-if="aggStatus?.value === State.REVOKE">
|
||||
<el-icon class="is-loading color-primary"><Loading /></el-icon>
|
||||
{{ stateMap[aggStatus.value](aggStatus.key) }}
|
||||
</el-text>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { TaskType, State } from '@/utils/status'
|
||||
import StatusTable from '@/views/shared/document-shared/component/StatusTable.vue'
|
||||
import { t } from '@/locales'
|
||||
const props = defineProps<{ status: string; statusMeta: any }>()
|
||||
const visible = ref<boolean>(false)
|
||||
const checkList: Array<string> = [
|
||||
State.REVOKE,
|
||||
State.STARTED,
|
||||
State.PENDING,
|
||||
State.FAILURE,
|
||||
State.REVOKED,
|
||||
State.SUCCESS
|
||||
]
|
||||
const aggStatus = computed(() => {
|
||||
let obj = { key: 0, value: '' }
|
||||
for (const i in checkList) {
|
||||
const state = checkList[i]
|
||||
const index = props.status.indexOf(state)
|
||||
if (index > -1) {
|
||||
obj = { key: props.status.length - index, value: state }
|
||||
break
|
||||
}
|
||||
}
|
||||
return obj
|
||||
})
|
||||
const startedMap = {
|
||||
[TaskType.EMBEDDING]: t('views.document.fileStatus.EMBEDDING'),
|
||||
[TaskType.GENERATE_PROBLEM]: t('views.document.fileStatus.GENERATE'),
|
||||
[TaskType.SYNC]: t('views.document.fileStatus.SYNC')
|
||||
}
|
||||
const taskTypeMap = {
|
||||
[TaskType.EMBEDDING]: t('views.knowledge.setting.vectorization'),
|
||||
[TaskType.GENERATE_PROBLEM]: t('views.document.generateQuestion.title'),
|
||||
[TaskType.SYNC]: t('views.knowledge.setting.sync')
|
||||
}
|
||||
const stateMap: any = {
|
||||
[State.PENDING]: (type: number) => t('views.document.fileStatus.PENDING'),
|
||||
[State.STARTED]: (type: number) => startedMap[type],
|
||||
[State.REVOKE]: (type: number) => t('views.document.fileStatus.REVOKE'),
|
||||
[State.REVOKED]: (type: number) => t('views.document.fileStatus.SUCCESS'),
|
||||
[State.FAILURE]: (type: number) => t('views.document.fileStatus.FAILURE'),
|
||||
[State.SUCCESS]: (type: number) => t('views.document.fileStatus.SUCCESS'),
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
<template>
|
||||
<div v-for="status in statusTable" :key="status.type" >
|
||||
<span> {{ taskTypeMap[status.type] }}:</span>
|
||||
<span>
|
||||
<el-text v-if="status.state === State.SUCCESS || status.state === State.REVOKED">
|
||||
<el-icon class="color-success"><SuccessFilled /></el-icon>
|
||||
{{ stateMap[status.state](status.type) }}
|
||||
</el-text>
|
||||
<el-text v-else-if="status.state === State.FAILURE">
|
||||
<el-icon class="color-danger"><CircleCloseFilled /></el-icon>
|
||||
{{ stateMap[status.state](status.type) }}
|
||||
</el-text>
|
||||
<el-text v-else-if="status.state === State.STARTED">
|
||||
<el-icon class="is-loading color-primary"><Loading /></el-icon>
|
||||
{{ stateMap[status.state](status.type) }}
|
||||
</el-text>
|
||||
<el-text v-else-if="status.state === State.PENDING">
|
||||
<el-icon class="is-loading color-primary"><Loading /></el-icon>
|
||||
{{ stateMap[status.state](status.type) }}
|
||||
</el-text>
|
||||
<el-text v-else-if="status.state === State.REVOKE">
|
||||
<el-icon class="is-loading color-primary"><Loading /></el-icon>
|
||||
{{ stateMap[status.state](status.type) }}
|
||||
</el-text>
|
||||
</span>
|
||||
<span
|
||||
class="ml-8 lighter"
|
||||
:style="{ color: [State.FAILURE, State.REVOKED].includes(status.state) ? '#F54A45' : '' }"
|
||||
>
|
||||
{{ $t('views.document.fileStatus.finish') }}
|
||||
{{
|
||||
Object.keys(status.aggs ? status.aggs : {})
|
||||
.filter((k) => k == State.SUCCESS)
|
||||
.map((k) => status.aggs[k])
|
||||
.reduce((x: any, y: any) => x + y, 0)
|
||||
}}/{{
|
||||
Object.values(status.aggs ? status.aggs : {}).reduce((x: any, y: any) => x + y, 0)
|
||||
}}</span
|
||||
>
|
||||
<el-text type="info" class="ml-12">
|
||||
{{
|
||||
status.time
|
||||
? status.time[status.state == State.REVOKED ? State.REVOKED : State.PENDING]?.substring(
|
||||
0,
|
||||
19
|
||||
)
|
||||
: undefined
|
||||
}}
|
||||
</el-text>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { Status, TaskType, State, type TaskTypeInterface } from '@/utils/status'
|
||||
import { mergeWith } from 'lodash'
|
||||
const props = defineProps<{ status: string; statusMeta: any; stateMap: any; taskTypeMap: any }>()
|
||||
|
||||
const parseAgg = (agg: { count: number; status: string }) => {
|
||||
const status = new Status(agg.status)
|
||||
return Object.keys(TaskType)
|
||||
.map((key) => {
|
||||
const value = TaskType[key as keyof TaskTypeInterface]
|
||||
return { [value]: { [status.task_status[value]]: agg.count } }
|
||||
})
|
||||
.reduce((x, y) => ({ ...x, ...y }), {})
|
||||
}
|
||||
|
||||
const customizer: (x: any, y: any) => any = (objValue: any, srcValue: any) => {
|
||||
if (objValue == undefined && srcValue) {
|
||||
return srcValue
|
||||
}
|
||||
if (srcValue == undefined && objValue) {
|
||||
return objValue
|
||||
}
|
||||
// 如果是数组,我们将元素进行聚合
|
||||
if (typeof objValue === 'object' && typeof srcValue === 'object') {
|
||||
// 若是object类型的对象,我们进行递归
|
||||
return mergeWith(objValue, srcValue, customizer)
|
||||
} else {
|
||||
// 否则,单纯的将值进行累加
|
||||
return objValue + srcValue
|
||||
}
|
||||
}
|
||||
const aggs = computed(() => {
|
||||
return (props.statusMeta.aggs ? props.statusMeta.aggs : [])
|
||||
.map((agg: any) => {
|
||||
return parseAgg(agg)
|
||||
})
|
||||
.reduce((x: any, y: any) => {
|
||||
return mergeWith(x, y, customizer)
|
||||
}, {})
|
||||
})
|
||||
|
||||
const statusTable = computed(() => {
|
||||
return Object.keys(TaskType)
|
||||
.map((key) => {
|
||||
const value = TaskType[key as keyof TaskTypeInterface]
|
||||
const parseStatus = new Status(props.status)
|
||||
return {
|
||||
type: value,
|
||||
state: parseStatus.task_status[value],
|
||||
aggs: aggs.value[value],
|
||||
time: props.statusMeta.state_time[value]
|
||||
}
|
||||
})
|
||||
.filter((item) => item.state !== State.IGNORED)
|
||||
})
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,98 +0,0 @@
|
|||
<template>
|
||||
<el-scrollbar>
|
||||
<el-result icon="success" :title="`🎉 ${$t('views.knowledge.ResultSuccess.title')} 🎉`">
|
||||
<template #sub-title>
|
||||
<div class="mt-8">
|
||||
<span class="bold">{{ data?.document_list.length || 0 }}</span>
|
||||
<el-text type="info" class="ml-4">{{ $t('common.fileUpload.document') }}</el-text>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="bold">{{ paragraph_count || 0 }}</span>
|
||||
<el-text type="info" class="ml-4">{{
|
||||
$t('views.knowledge.ResultSuccess.paragraph')
|
||||
}}</el-text>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="bold">{{ numberFormat(char_length) || 0 }}</span>
|
||||
<el-text type="info" class="ml-4">{{ $t('common.character') }} </el-text>
|
||||
</div>
|
||||
</template>
|
||||
<template #extra>
|
||||
<el-button @click="router.push({ path: `/knowledge` })">{{
|
||||
$t('views.knowledge.ResultSuccess.buttons.toDataset')
|
||||
}}</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="router.push({ path: `/knowledge/${data?.id}/${folderId}/document` })"
|
||||
>{{ $t('views.knowledge.ResultSuccess.buttons.toDocument') }}</el-button
|
||||
>
|
||||
</template>
|
||||
</el-result>
|
||||
<div class="result-success">
|
||||
<p class="bolder">{{ $t('views.knowledge.ResultSuccess.documentList') }}</p>
|
||||
<el-card
|
||||
shadow="never"
|
||||
class="file-List-card mt-8"
|
||||
v-for="(item, index) in data?.document_list"
|
||||
:key="index"
|
||||
>
|
||||
<div class="flex-between">
|
||||
<div class="flex">
|
||||
<img :src="getImgUrl(item && item?.name)" alt="" width="40" />
|
||||
<div class="ml-8">
|
||||
<p>{{ item && item?.name }}</p>
|
||||
<el-text type="info" size="small">{{ filesize(item && item?.char_length) }}</el-text>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<el-text type="color-info" class="mr-16"
|
||||
>{{ item && item?.paragraph_count }}
|
||||
{{ $t('views.knowledge.ResultSuccess.paragraph_count') }}</el-text
|
||||
>
|
||||
<el-text v-if="item.status === '1'">
|
||||
<el-icon class="color-success"><SuccessFilled /></el-icon>
|
||||
</el-text>
|
||||
<el-text v-else-if="item.status === '2'">
|
||||
<el-icon class="color-danger"><CircleCloseFilled /></el-icon>
|
||||
</el-text>
|
||||
<el-text v-else-if="item.status === '0'">
|
||||
<el-icon class="is-loading primary"><Loading /></el-icon>
|
||||
{{ $t('views.knowledge.ResultSuccess.loading') }}...
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { numberFormat } from '@/utils/utils'
|
||||
import { filesize, getImgUrl } from '@/utils/utils'
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
})
|
||||
|
||||
const {
|
||||
params: { id, folderId }, // id为knowledgeID
|
||||
} = route as any
|
||||
const paragraph_count = computed(() =>
|
||||
props.data?.document_list.reduce((sum: number, obj: any) => (sum += obj.paragraph_count), 0),
|
||||
)
|
||||
|
||||
const char_length = computed(
|
||||
() =>
|
||||
props.data?.document_list.reduce((sum: number, obj: any) => (sum += obj.char_length), 0) || 0,
|
||||
)
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.result-success {
|
||||
width: 70%;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,258 +0,0 @@
|
|||
<template>
|
||||
<div class="set-rules">
|
||||
<el-row>
|
||||
<el-col :span="10" class="p-24">
|
||||
<h4 class="title-decoration-1 mb-16">{{ $t('views.document.setRules.title.setting') }}</h4>
|
||||
<div class="set-rules__right">
|
||||
<el-scrollbar>
|
||||
<div class="left-height" @click.stop>
|
||||
<el-radio-group v-model="radio" class="card__radio">
|
||||
<el-card shadow="never" class="mb-16" :class="radio === '1' ? 'active' : ''">
|
||||
<el-radio value="1" size="large">
|
||||
<p class="mb-4">{{ $t('views.document.setRules.intelligent.label') }}</p>
|
||||
<el-text type="info">{{
|
||||
$t('views.document.setRules.intelligent.text')
|
||||
}}</el-text>
|
||||
</el-radio>
|
||||
</el-card>
|
||||
<el-card shadow="never" class="mb-16" :class="radio === '2' ? 'active' : ''">
|
||||
<el-radio value="2" size="large">
|
||||
<p class="mb-4">{{ $t('views.document.setRules.advanced.label') }}</p>
|
||||
<el-text type="info">
|
||||
{{ $t('views.document.setRules.advanced.text') }}
|
||||
</el-text>
|
||||
</el-radio>
|
||||
|
||||
<el-card
|
||||
v-if="radio === '2'"
|
||||
shadow="never"
|
||||
class="card-never mt-16"
|
||||
style="margin-left: 30px"
|
||||
>
|
||||
<div class="set-rules__form">
|
||||
<div class="form-item mb-16">
|
||||
<div class="title flex align-center mb-8">
|
||||
<span style="margin-right: 4px">{{
|
||||
$t('views.document.setRules.patterns.label')
|
||||
}}</span>
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('views.document.setRules.patterns.tooltip')"
|
||||
placement="right"
|
||||
>
|
||||
<AppIcon iconName="app-warning" class="app-warning-icon"></AppIcon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div @click.stop>
|
||||
<el-select
|
||||
v-model="form.patterns"
|
||||
multiple
|
||||
allow-create
|
||||
default-first-option
|
||||
filterable
|
||||
:placeholder="$t('views.document.setRules.patterns.placeholder')"
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, index) in splitPatternList"
|
||||
:key="index"
|
||||
:label="item.key"
|
||||
:value="item.value"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item mb-16">
|
||||
<div class="title mb-8">
|
||||
{{ $t('views.document.setRules.limit.label') }}
|
||||
</div>
|
||||
<el-slider
|
||||
v-model="form.limit"
|
||||
show-input
|
||||
:show-input-controls="false"
|
||||
:min="50"
|
||||
:max="100000"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-item mb-16">
|
||||
<div class="title mb-8">
|
||||
{{ $t('views.document.setRules.with_filter.label') }}
|
||||
</div>
|
||||
<el-switch size="small" v-model="form.with_filter" />
|
||||
<div style="margin-top: 4px">
|
||||
<el-text type="info">
|
||||
{{ $t('views.document.setRules.with_filter.text') }}</el-text
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-card>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<div>
|
||||
<el-checkbox
|
||||
v-model="checkedConnect"
|
||||
@change="changeHandle"
|
||||
style="white-space: normal"
|
||||
>
|
||||
{{ $t('views.document.setRules.checkedConnect.label') }}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
<div class="text-right mt-8">
|
||||
<el-button @click="splitDocument">
|
||||
{{ $t('views.document.buttons.preview') }}</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="14" class="p-24 border-l">
|
||||
<div v-loading="loading">
|
||||
<h4 class="title-decoration-1 mb-8">{{ $t('views.document.setRules.title.preview') }}</h4>
|
||||
|
||||
<ParagraphPreview v-model:data="paragraphList" :isConnect="checkedConnect" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, reactive, watch } from 'vue'
|
||||
import ParagraphPreview from '@/views/knowledge/component/ParagraphPreview.vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { cutFilename } from '@/utils/utils'
|
||||
import documentApi from '@/api/system-shared/document'
|
||||
import useStore from '@/stores/modules-shared-system'
|
||||
import type { KeyValue } from '@/api/type/common'
|
||||
const { knowledge } = useStore()
|
||||
const documentsFiles = computed(() => knowledge.documentsFiles)
|
||||
const splitPatternList = ref<Array<KeyValue<string, string>>>([])
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
query: { id }, // id为knowledgeID
|
||||
} = route as any
|
||||
const radio = ref('1')
|
||||
const loading = ref(false)
|
||||
const paragraphList = ref<any[]>([])
|
||||
const patternLoading = ref<boolean>(false)
|
||||
const checkedConnect = ref<boolean>(false)
|
||||
|
||||
const firstChecked = ref(true)
|
||||
|
||||
const form = reactive<{
|
||||
patterns: Array<string>
|
||||
limit: number
|
||||
with_filter: boolean
|
||||
[propName: string]: any
|
||||
}>({
|
||||
patterns: [],
|
||||
limit: 500,
|
||||
with_filter: true,
|
||||
})
|
||||
|
||||
function changeHandle(val: boolean) {
|
||||
if (val && firstChecked.value) {
|
||||
paragraphList.value = paragraphList.value.map((item: any) => ({
|
||||
...item,
|
||||
content: item.content.map((v: any) => ({
|
||||
...v,
|
||||
problem_list: v.title.trim()
|
||||
? [
|
||||
{
|
||||
content: v.title.trim(),
|
||||
},
|
||||
]
|
||||
: [],
|
||||
})),
|
||||
}))
|
||||
firstChecked.value = false
|
||||
}
|
||||
}
|
||||
function splitDocument() {
|
||||
loading.value = true
|
||||
const fd = new FormData()
|
||||
documentsFiles.value.forEach((item) => {
|
||||
if (item?.raw) {
|
||||
fd.append('file', item?.raw)
|
||||
}
|
||||
})
|
||||
if (radio.value === '2') {
|
||||
Object.keys(form).forEach((key) => {
|
||||
if (key == 'patterns') {
|
||||
form.patterns.forEach((item) => fd.append('patterns', item))
|
||||
} else {
|
||||
fd.append(key, form[key])
|
||||
}
|
||||
})
|
||||
}
|
||||
documentApi
|
||||
.postSplitDocument(id, fd)
|
||||
.then((res: any) => {
|
||||
const list = res.data
|
||||
|
||||
list.map((item: any) => {
|
||||
if (item.name.length > 128) {
|
||||
item.name = cutFilename(item.name, 128)
|
||||
}
|
||||
if (checkedConnect.value) {
|
||||
item.content.map((v: any) => {
|
||||
v['problem_list'] = v.title.trim()
|
||||
? [
|
||||
{
|
||||
content: v.title.trim(),
|
||||
},
|
||||
]
|
||||
: []
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
paragraphList.value = list
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const initSplitPatternList = () => {
|
||||
documentApi.listSplitPattern(id, patternLoading).then((ok) => {
|
||||
splitPatternList.value = ok.data
|
||||
})
|
||||
}
|
||||
|
||||
watch(radio, () => {
|
||||
if (radio.value === '2') {
|
||||
initSplitPatternList()
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
splitDocument()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
paragraphList,
|
||||
checkedConnect,
|
||||
loading,
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.set-rules {
|
||||
width: 100%;
|
||||
|
||||
.left-height {
|
||||
max-height: calc(var(--create-dataset-height) - 110px);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
&__form {
|
||||
.title {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,327 +0,0 @@
|
|||
<template>
|
||||
<h4 class="title-decoration-1 mb-8">{{ $t('views.document.uploadDocument') }}</h4>
|
||||
<el-form
|
||||
ref="FormRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
>
|
||||
<div class="mt-16 mb-16">
|
||||
<el-radio-group v-model="form.fileType" @change="radioChange" class="app-radio-button-group">
|
||||
<el-radio-button value="txt">{{ $t('views.document.fileType.txt.label') }}</el-radio-button>
|
||||
<el-radio-button value="table">{{
|
||||
$t('views.document.fileType.table.label')
|
||||
}}</el-radio-button>
|
||||
<el-radio-button value="QA">{{ $t('views.document.fileType.QA.label') }}</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<el-form-item prop="fileList" v-if="form.fileType === 'QA'">
|
||||
<div class="update-info flex p-8-12 border-r-4 mb-16 w-full">
|
||||
<div class="mt-4">
|
||||
<AppIcon iconName="app-warning-colorful" style="font-size: 16px"></AppIcon>
|
||||
</div>
|
||||
<div class="ml-16 lighter">
|
||||
<p>
|
||||
{{ $t('views.document.fileType.QA.tip1') }}
|
||||
<el-button type="primary" link @click="downloadTemplate('excel')">
|
||||
{{ $t('views.document.upload.download') }} Excel
|
||||
{{ $t('views.document.upload.template') }}
|
||||
</el-button>
|
||||
<el-button type="primary" link @click="downloadTemplate('csv')">
|
||||
{{ $t('views.document.upload.download') }} CSV
|
||||
{{ $t('views.document.upload.template') }}
|
||||
</el-button>
|
||||
</p>
|
||||
<p>{{ $t('views.document.fileType.QA.tip2') }}</p>
|
||||
<p>{{ $t('views.document.fileType.QA.tip3') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<el-upload
|
||||
:webkitdirectory="false"
|
||||
class="w-full mb-4"
|
||||
drag
|
||||
multiple
|
||||
v-model:file-list="form.fileList"
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
accept=".xlsx, .xls, .csv,.zip"
|
||||
:limit="50"
|
||||
:on-exceed="onExceed"
|
||||
:on-change="fileHandleChange"
|
||||
@click.prevent="handlePreview(false)"
|
||||
>
|
||||
<img src="@/assets/upload-icon.svg" alt="" />
|
||||
<div class="el-upload__text">
|
||||
<p>
|
||||
{{ $t('views.document.upload.uploadMessage') }}
|
||||
<em class="hover" @click.prevent="handlePreview(false)">
|
||||
{{ $t('views.document.upload.selectFile') }}
|
||||
</em>
|
||||
<em class="hove ml-4" @click.prevent="handlePreview(true)">
|
||||
{{ $t('views.document.upload.selectFiles') }}
|
||||
</em>
|
||||
</p>
|
||||
<div class="upload__decoration">
|
||||
<p>{{ $t('views.document.upload.formats') }}XLS、XLSX、CSV、ZIP</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item prop="fileList" v-else-if="form.fileType === 'table'">
|
||||
<div class="update-info flex p-8-12 border-r-4 mb-16 w-full">
|
||||
<div class="mt-4">
|
||||
<AppIcon iconName="app-warning-colorful" style="font-size: 16px"></AppIcon>
|
||||
</div>
|
||||
<div class="ml-16 lighter">
|
||||
<p>
|
||||
{{ $t('views.document.fileType.table.tip1') }}
|
||||
<el-button type="primary" link @click="downloadTableTemplate('excel')">
|
||||
{{ $t('views.document.upload.download') }} Excel
|
||||
{{ $t('views.document.upload.template') }}
|
||||
</el-button>
|
||||
<el-button type="primary" link @click="downloadTableTemplate('csv')">
|
||||
{{ $t('views.document.upload.download') }} CSV
|
||||
{{ $t('views.document.upload.template') }}
|
||||
</el-button>
|
||||
</p>
|
||||
<p>{{ $t('views.document.fileType.table.tip2') }}</p>
|
||||
<p>{{ $t('views.document.fileType.table.tip3') }}</p>
|
||||
<p>{{ $t('views.document.fileType.table.tip4') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<el-upload
|
||||
:webkitdirectory="false"
|
||||
class="w-full mb-4"
|
||||
drag
|
||||
multiple
|
||||
v-model:file-list="form.fileList"
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
accept=".xlsx, .xls, .csv"
|
||||
:limit="50"
|
||||
:on-exceed="onExceed"
|
||||
:on-change="fileHandleChange"
|
||||
@click.prevent="handlePreview(false)"
|
||||
>
|
||||
<img src="@/assets/upload-icon.svg" alt="" />
|
||||
<div class="el-upload__text">
|
||||
<p>
|
||||
{{ $t('views.document.upload.uploadMessage') }}
|
||||
<em class="hover" @click.prevent="handlePreview(false)">
|
||||
{{ $t('views.document.upload.selectFile') }}
|
||||
</em>
|
||||
<em class="hover ml-4" @click.prevent="handlePreview(true)">
|
||||
{{ $t('views.document.upload.selectFiles') }}
|
||||
</em>
|
||||
</p>
|
||||
<div class="upload__decoration">
|
||||
<p>{{ $t('views.document.upload.formats') }}XLS、XLSX、CSV</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item prop="fileList" v-else>
|
||||
<div class="update-info flex p-8-12 border-r-4 mb-16 w-full">
|
||||
<div class="mt-4">
|
||||
<AppIcon iconName="app-warning-colorful" style="font-size: 16px"></AppIcon>
|
||||
</div>
|
||||
<div class="ml-16 lighter">
|
||||
<p>{{ $t('views.document.fileType.txt.tip1') }}</p>
|
||||
<p>{{ $t('views.document.fileType.txt.tip2') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<el-upload
|
||||
:webkitdirectory="false"
|
||||
class="w-full"
|
||||
drag
|
||||
multiple
|
||||
v-model:file-list="form.fileList"
|
||||
action="#"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
accept=".txt, .md, .log, .docx, .pdf, .html,.zip,.xlsx,.xls,.csv"
|
||||
:limit="50"
|
||||
:on-exceed="onExceed"
|
||||
:on-change="fileHandleChange"
|
||||
@click.prevent="handlePreview(false)"
|
||||
>
|
||||
<img src="@/assets/upload-icon.svg" alt="" />
|
||||
<div class="el-upload__text">
|
||||
<p>
|
||||
{{ $t('views.document.upload.uploadMessage') }}
|
||||
<em class="hover" @click.prevent="handlePreview(false)">
|
||||
{{ $t('views.document.upload.selectFile') }}
|
||||
</em>
|
||||
<em class="hover ml-4" @click.prevent="handlePreview(true)">
|
||||
{{ $t('views.document.upload.selectFiles') }}
|
||||
</em>
|
||||
</p>
|
||||
<div class="upload__decoration">
|
||||
<p>
|
||||
{{
|
||||
$t('views.document.upload.formats')
|
||||
}}TXT、Markdown、PDF、DOCX、HTML、XLS、XLSX、CSV、ZIP
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row :gutter="8" v-if="form.fileList?.length">
|
||||
<template v-for="(item, index) in form.fileList" :key="index">
|
||||
<el-col :span="12" class="mb-8">
|
||||
<el-card shadow="never" class="file-List-card">
|
||||
<div class="flex-between">
|
||||
<div class="flex">
|
||||
<img :src="getImgUrl(item && item?.name)" alt="" width="40" />
|
||||
<div class="ml-8">
|
||||
<p>{{ item && item?.name }}</p>
|
||||
<el-text type="info" size="small">{{
|
||||
filesize(item && item?.size) || '0K'
|
||||
}}</el-text>
|
||||
</div>
|
||||
</div>
|
||||
<el-button text @click="deleteFile(index)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</template>
|
||||
</el-row>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onUnmounted, onMounted, computed, watch, nextTick } from 'vue'
|
||||
import type { UploadFiles } from 'element-plus'
|
||||
import { filesize, getImgUrl, isRightType } from '@/utils/utils'
|
||||
import { MsgError } from '@/utils/message'
|
||||
import documentApi from '@/api/system-shared/document'
|
||||
import useStore from '@/stores/modules-shared-system'
|
||||
import { t } from '@/locales'
|
||||
const { knowledge } = useStore()
|
||||
const documentsFiles = computed(() => knowledge.documentsFiles)
|
||||
const documentsType = computed(() => knowledge.documentsType)
|
||||
const form = ref({
|
||||
fileType: 'txt',
|
||||
fileList: [] as any
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
fileList: [
|
||||
{ required: true, message: t('views.document.upload.requiredMessage'), trigger: 'change' }
|
||||
]
|
||||
})
|
||||
const FormRef = ref()
|
||||
|
||||
watch(form.value, (value) => {
|
||||
knowledge.saveDocumentsType(value.fileType)
|
||||
knowledge.saveDocumentsFile(value.fileList)
|
||||
})
|
||||
|
||||
function downloadTemplate(type: string) {
|
||||
documentApi.exportQATemplate(
|
||||
`${type}${t('views.document.upload.template')}.${type == 'csv' ? type : 'xlsx'}`,
|
||||
type
|
||||
)
|
||||
}
|
||||
|
||||
function downloadTableTemplate(type: string) {
|
||||
documentApi.exportTableTemplate(
|
||||
`${type}${t('views.document.upload.template')}.${type == 'csv' ? type : 'xlsx'}`,
|
||||
type
|
||||
)
|
||||
}
|
||||
|
||||
function radioChange() {
|
||||
form.value.fileList = []
|
||||
}
|
||||
|
||||
function deleteFile(index: number) {
|
||||
form.value.fileList.splice(index, 1)
|
||||
}
|
||||
|
||||
// 上传on-change事件
|
||||
const fileHandleChange = (file: any, fileList: UploadFiles) => {
|
||||
//1、判断文件大小是否合法,文件限制不能大于100M
|
||||
const isLimit = file?.size / 1024 / 1024 < 100
|
||||
if (!isLimit) {
|
||||
MsgError(t('views.document.upload.errorMessage1'))
|
||||
fileList.splice(-1, 1) //移除当前超出大小的文件
|
||||
return false
|
||||
}
|
||||
|
||||
if (!isRightType(file?.name, form.value.fileType)) {
|
||||
if (file?.name !== '.DS_Store') {
|
||||
MsgError(t('views.document.upload.errorMessage2'))
|
||||
}
|
||||
fileList.splice(-1, 1)
|
||||
return false
|
||||
}
|
||||
if (file?.size === 0) {
|
||||
MsgError(t('views.document.upload.errorMessage3'))
|
||||
fileList.splice(-1, 1)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const onExceed = () => {
|
||||
MsgError(t('views.document.upload.errorMessage4'))
|
||||
}
|
||||
|
||||
const handlePreview = (bool: boolean) => {
|
||||
let inputDom: any = null
|
||||
nextTick(() => {
|
||||
if (document.querySelector('.el-upload__input') != null) {
|
||||
inputDom = document.querySelector('.el-upload__input')
|
||||
inputDom.webkitdirectory = bool
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
表单校验
|
||||
*/
|
||||
function validate() {
|
||||
if (!FormRef.value) return
|
||||
return FormRef.value.validate((valid: any) => {
|
||||
return valid
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (documentsType.value) {
|
||||
form.value.fileType = documentsType.value
|
||||
}
|
||||
if (documentsFiles.value) {
|
||||
form.value.fileList = documentsFiles.value
|
||||
}
|
||||
})
|
||||
onUnmounted(() => {
|
||||
form.value = {
|
||||
fileType: 'txt',
|
||||
fileList: []
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
validate,
|
||||
form
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.upload__decoration {
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
.el-upload__text {
|
||||
.hover:hover {
|
||||
color: var(--el-color-primary-light-5);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -1,434 +0,0 @@
|
|||
<template>
|
||||
<div class="hit-test p-16-24">
|
||||
<h4>
|
||||
{{ $t('views.application.hitTest.title') }}
|
||||
<el-text type="info" class="ml-4"> {{ $t('views.application.hitTest.text') }}</el-text>
|
||||
</h4>
|
||||
<el-card style="--el-card-padding: 0" class="hit-test__main p-16 mt-16 mb-16" v-loading="loading">
|
||||
<div class="question-title" :style="{ visibility: questionTitle ? 'visible' : 'hidden' }">
|
||||
<div class="avatar">
|
||||
<el-avatar>
|
||||
<img src="@/assets/user-icon.svg" style="width: 54%" alt="" />
|
||||
</el-avatar>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h4 class="text break-all">{{ questionTitle }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
<el-scrollbar>
|
||||
<div class="hit-test-height">
|
||||
<el-empty
|
||||
v-if="first"
|
||||
:image="emptyImg"
|
||||
:description="$t('views.application.hitTest.emptyMessage1')"
|
||||
style="padding-top: 160px"
|
||||
:image-size="125"
|
||||
/>
|
||||
<el-empty
|
||||
v-else-if="paragraphDetail.length == 0"
|
||||
:description="$t('views.application.hitTest.emptyMessage2')"
|
||||
style="padding-top: 160px"
|
||||
:image-size="125"
|
||||
/>
|
||||
<el-row v-else>
|
||||
<el-col
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="12"
|
||||
:lg="8"
|
||||
:xl="6"
|
||||
v-for="(item, index) in paragraphDetail"
|
||||
:key="index"
|
||||
class="p-8"
|
||||
>
|
||||
<CardBox
|
||||
shadow="hover"
|
||||
:title="item.title || '-'"
|
||||
:description="item.content"
|
||||
class="document-card layout-bg layout-bg cursor"
|
||||
:class="item.is_active ? '' : 'disabled'"
|
||||
:showIcon="false"
|
||||
@click="editParagraph(item)"
|
||||
>
|
||||
<template #icon>
|
||||
<el-avatar class="mr-12 avatar-light" :size="22"> {{ index + 1 + '' }}</el-avatar>
|
||||
</template>
|
||||
<div class="active-button primary">{{ item.similarity?.toFixed(3) }}</div>
|
||||
<template #footer>
|
||||
<div class="footer-content flex-between">
|
||||
<el-text>
|
||||
<el-icon>
|
||||
<Document />
|
||||
</el-icon>
|
||||
{{ item?.document_name }}
|
||||
</el-text>
|
||||
<div v-if="item.trample_num || item.star_num">
|
||||
<span v-if="item.star_num">
|
||||
<AppIcon iconName="app-like-color"></AppIcon>
|
||||
{{ item.star_num }}
|
||||
</span>
|
||||
<span v-if="item.trample_num" class="ml-4">
|
||||
<AppIcon iconName="app-oppose-color"></AppIcon>
|
||||
{{ item.trample_num }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</CardBox>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-card>
|
||||
<ParagraphDialog ref="ParagraphDialogRef" :title="title" @refresh="refresh" />
|
||||
|
||||
<div class="hit-test__operate">
|
||||
<el-popover :visible="popoverVisible" placement="right-end" :width="500" trigger="click">
|
||||
<template #reference>
|
||||
<el-button icon="Setting" class="mb-8" @click="settingChange('open')">{{
|
||||
$t('common.paramSetting')
|
||||
}}</el-button>
|
||||
</template>
|
||||
<div class="mb-16">
|
||||
<div class="title mb-8">
|
||||
{{ $t('views.application.dialog.selectSearchMode') }}
|
||||
</div>
|
||||
<el-radio-group
|
||||
v-model="cloneForm.search_mode"
|
||||
class="card__radio"
|
||||
@change="changeHandle"
|
||||
>
|
||||
<el-card
|
||||
shadow="never"
|
||||
class="mb-16"
|
||||
:class="cloneForm.search_mode === 'embedding' ? 'active' : ''"
|
||||
>
|
||||
<el-radio value="embedding" size="large">
|
||||
<p class="mb-4">
|
||||
{{ $t('views.application.dialog.vectorSearch') }}
|
||||
</p>
|
||||
<el-text type="info">{{
|
||||
$t('views.application.dialog.vectorSearchTooltip')
|
||||
}}</el-text>
|
||||
</el-radio>
|
||||
</el-card>
|
||||
<el-card
|
||||
shadow="never"
|
||||
class="mb-16"
|
||||
:class="cloneForm.search_mode === 'keywords' ? 'active' : ''"
|
||||
>
|
||||
<el-radio value="keywords" size="large">
|
||||
<p class="mb-4">
|
||||
{{ $t('views.application.dialog.fullTextSearch') }}
|
||||
</p>
|
||||
<el-text type="info">{{
|
||||
$t('views.application.dialog.fullTextSearchTooltip')
|
||||
}}</el-text>
|
||||
</el-radio>
|
||||
</el-card>
|
||||
<el-card
|
||||
shadow="never"
|
||||
class="mb-16"
|
||||
:class="cloneForm.search_mode === 'blend' ? 'active' : ''"
|
||||
>
|
||||
<el-radio value="blend" size="large">
|
||||
<p class="mb-4">
|
||||
{{ $t('views.application.dialog.hybridSearch') }}
|
||||
</p>
|
||||
<el-text type="info">{{
|
||||
$t('views.application.dialog.hybridSearchTooltip')
|
||||
}}</el-text>
|
||||
</el-radio>
|
||||
</el-card>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<div class="mb-16">
|
||||
<div class="title mb-8">
|
||||
{{ $t('views.application.dialog.similarityThreshold') }}
|
||||
</div>
|
||||
<el-input-number
|
||||
v-model="cloneForm.similarity"
|
||||
:min="0"
|
||||
:max="cloneForm.search_mode === 'blend' ? 2 : 1"
|
||||
:precision="3"
|
||||
:step="0.1"
|
||||
:value-on-clear="0"
|
||||
controls-position="right"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div class="mb-16">
|
||||
<div class="title mb-8">
|
||||
{{ $t('views.application.dialog.topReferences') }}
|
||||
</div>
|
||||
<el-input-number
|
||||
v-model="cloneForm.top_number"
|
||||
:min="1"
|
||||
:max="10000"
|
||||
controls-position="right"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="text-right">
|
||||
<el-button @click="popoverVisible = false">{{ $t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="settingChange('close')">{{
|
||||
$t('common.confirm')
|
||||
}}</el-button>
|
||||
</div>
|
||||
</el-popover>
|
||||
<div class="operate-textarea flex">
|
||||
<el-input
|
||||
ref="quickInputRef"
|
||||
v-model="inputValue"
|
||||
type="textarea"
|
||||
:placeholder="$t('common.inputPlaceholder')"
|
||||
:autosize="{ minRows: 1, maxRows: 8 }"
|
||||
@keydown.enter="sendChatHandle($event)"
|
||||
/>
|
||||
<div class="operate">
|
||||
<el-button
|
||||
text
|
||||
class="sent-button"
|
||||
:disabled="isDisabledChart || loading"
|
||||
@click="sendChatHandle"
|
||||
>
|
||||
<img v-show="isDisabledChart || loading" src="@/assets/icon_send.svg" alt="" />
|
||||
<img
|
||||
v-show="!isDisabledChart && !loading"
|
||||
src="@/assets/icon_send_colorful.svg"
|
||||
alt=""
|
||||
/>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref, onMounted, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import KnowledgeApi from '@/api/system-shared/knowledge'
|
||||
// import applicationApi from '@/api/application/application'
|
||||
import ParagraphDialog from '@/views/paragraph/component/ParagraphDialog.vue'
|
||||
import { arraySort } from '@/utils/common'
|
||||
import emptyImg from '@/assets/hit-test-empty.png'
|
||||
import { t } from '@/locales'
|
||||
const route = useRoute()
|
||||
const {
|
||||
meta: { activeMenu },
|
||||
params: { id },
|
||||
} = route as any
|
||||
|
||||
const quickInputRef = ref()
|
||||
const ParagraphDialogRef = ref()
|
||||
const loading = ref(false)
|
||||
const paragraphDetail = ref<any[]>([])
|
||||
const title = ref('')
|
||||
const inputValue = ref('')
|
||||
const formInline = ref({
|
||||
similarity: 0.6,
|
||||
top_number: 5,
|
||||
search_mode: 'embedding',
|
||||
})
|
||||
|
||||
// 第一次加载
|
||||
const first = ref(true)
|
||||
|
||||
const cloneForm = ref<any>({})
|
||||
|
||||
const popoverVisible = ref(false)
|
||||
const questionTitle = ref('')
|
||||
|
||||
const isDisabledChart = computed(() => !inputValue.value)
|
||||
|
||||
const isApplication = computed(() => {
|
||||
return activeMenu.includes('application')
|
||||
})
|
||||
const isDataset = computed(() => {
|
||||
return activeMenu.includes('knowledge')
|
||||
})
|
||||
|
||||
function changeHandle(val: string) {
|
||||
if (val === 'keywords') {
|
||||
cloneForm.value.similarity = 0
|
||||
} else {
|
||||
cloneForm.value.similarity = 0.6
|
||||
}
|
||||
}
|
||||
|
||||
function settingChange(val: string) {
|
||||
if (val === 'open') {
|
||||
popoverVisible.value = true
|
||||
cloneForm.value = cloneDeep(formInline.value)
|
||||
} else if (val === 'close') {
|
||||
popoverVisible.value = false
|
||||
formInline.value = cloneDeep(cloneForm.value)
|
||||
}
|
||||
}
|
||||
|
||||
function editParagraph(row: any) {
|
||||
title.value = t('views.paragraph.paragraphDetail')
|
||||
ParagraphDialogRef.value.open(row)
|
||||
}
|
||||
|
||||
function sendChatHandle(event: any) {
|
||||
if (!event?.ctrlKey && !event?.shiftKey && !event?.altKey && !event?.metaKey) {
|
||||
// 如果没有按下组合键,则会阻止默认事件
|
||||
event.preventDefault()
|
||||
if (!isDisabledChart.value && !loading.value) {
|
||||
getHitTestList()
|
||||
}
|
||||
} else {
|
||||
// 如果同时按下ctrl/shift/cmd/opt +enter,则会换行
|
||||
insertNewlineAtCursor(event)
|
||||
}
|
||||
}
|
||||
const insertNewlineAtCursor = (event?: any) => {
|
||||
const textarea = quickInputRef.value.$el.querySelector(
|
||||
'.el-textarea__inner',
|
||||
) as HTMLTextAreaElement
|
||||
const startPos = textarea.selectionStart
|
||||
const endPos = textarea.selectionEnd
|
||||
// 阻止默认行为(避免额外的换行符)
|
||||
event.preventDefault()
|
||||
// 在光标处插入换行符
|
||||
inputValue.value = inputValue.value.slice(0, startPos) + '\n' + inputValue.value.slice(endPos)
|
||||
nextTick(() => {
|
||||
textarea.setSelectionRange(startPos + 1, startPos + 1) // 光标定位到换行后位置
|
||||
})
|
||||
}
|
||||
|
||||
function getHitTestList() {
|
||||
const obj = {
|
||||
query_text: inputValue.value,
|
||||
...formInline.value,
|
||||
}
|
||||
if (isDataset.value) {
|
||||
KnowledgeApi.putKnowledgeHitTest(id, obj, loading).then((res) => {
|
||||
paragraphDetail.value = res.data && arraySort(res.data, 'comprehensive_score', true)
|
||||
questionTitle.value = inputValue.value
|
||||
inputValue.value = ''
|
||||
first.value = false
|
||||
})
|
||||
} else if (isApplication.value) {
|
||||
// applicationApi.getApplicationHitTest(id, obj, loading).then((res) => {
|
||||
// paragraphDetail.value = res.data && arraySort(res.data, 'comprehensive_score', true)
|
||||
// questionTitle.value = inputValue.value
|
||||
// inputValue.value = ''
|
||||
// first.value = false
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
function refresh(data: any) {
|
||||
if (data) {
|
||||
const obj = paragraphDetail.value.filter((v) => v.id === data.id)[0]
|
||||
obj.content = data.content
|
||||
obj.title = data.title
|
||||
} else {
|
||||
paragraphDetail.value = []
|
||||
getHitTestList()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.hit-test {
|
||||
.question-title {
|
||||
.avatar {
|
||||
float: left;
|
||||
}
|
||||
.content {
|
||||
padding-left: 40px;
|
||||
.text {
|
||||
padding: 6px 0;
|
||||
height: 34px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__operate {
|
||||
.operate-textarea {
|
||||
box-shadow: 0px 6px 24px 0px rgba(31, 35, 41, 0.08);
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ffffff;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:has(.el-textarea__inner:focus) {
|
||||
border: 1px solid var(--el-color-primary);
|
||||
}
|
||||
|
||||
:deep(.el-textarea__inner) {
|
||||
border-radius: 8px !important;
|
||||
box-shadow: none;
|
||||
resize: none;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
.operate {
|
||||
padding: 6px 10px;
|
||||
.sent-button {
|
||||
max-height: none;
|
||||
.el-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
:deep(.el-loading-spinner) {
|
||||
margin-top: -15px;
|
||||
.circular {
|
||||
width: 31px;
|
||||
height: 31px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.hit-test {
|
||||
&__header {
|
||||
position: absolute;
|
||||
right: calc(var(--app-base-px) * 3);
|
||||
}
|
||||
|
||||
.hit-test-height {
|
||||
height: calc(100vh - 300px);
|
||||
}
|
||||
.document-card {
|
||||
height: 210px;
|
||||
border: 1px solid var(--app-layout-bg-color);
|
||||
&:hover {
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--el-border-color);
|
||||
}
|
||||
&.disabled {
|
||||
background: var(--app-layout-bg-color);
|
||||
border: 1px solid var(--app-layout-bg-color);
|
||||
:deep(.description) {
|
||||
color: var(--app-border-color-dark);
|
||||
}
|
||||
:deep(.title) {
|
||||
color: var(--app-border-color-dark);
|
||||
}
|
||||
}
|
||||
:deep(.description) {
|
||||
-webkit-line-clamp: 5 !important;
|
||||
height: 110px;
|
||||
}
|
||||
.active-button {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,284 +0,0 @@
|
|||
<template>
|
||||
<div class="p-16-24">
|
||||
<h2 class="mb-16">{{ $t('common.setting') }}</h2>
|
||||
<el-card style="--el-card-padding: 0">
|
||||
<div class="knowledge-setting main-calc-height">
|
||||
<el-scrollbar>
|
||||
<div class="p-24" v-loading="loading">
|
||||
<h4 class="title-decoration-1 mb-16">
|
||||
{{ $t('common.info') }}
|
||||
</h4>
|
||||
<BaseForm ref="BaseFormRef" :data="detail" />
|
||||
|
||||
<el-form
|
||||
ref="webFormRef"
|
||||
:rules="rules"
|
||||
:model="form"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
>
|
||||
<el-form-item :label="$t('views.knowledge.knowledgeType.label')" required>
|
||||
<el-card
|
||||
shadow="never"
|
||||
class="mb-8 w-full"
|
||||
style="line-height: 22px"
|
||||
v-if="detail.type === 0"
|
||||
>
|
||||
<div class="flex align-center">
|
||||
<el-avatar class="mr-8 avatar-blue" shape="square" :size="32">
|
||||
<img src="@/assets/knowledge/icon_document.svg" style="width: 58%" alt="" />
|
||||
</el-avatar>
|
||||
<div>
|
||||
<div>{{ $t('views.knowledge.knowledgeType.generalKnowledge') }}</div>
|
||||
<el-text type="info"
|
||||
>{{ $t('views.knowledge.knowledgeType.generalInfo') }}
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card
|
||||
shadow="never"
|
||||
class="mb-8 w-full"
|
||||
style="line-height: 22px"
|
||||
v-if="detail?.type === 1"
|
||||
>
|
||||
<div class="flex align-center">
|
||||
<el-avatar class="mr-8 avatar-purple" shape="square" :size="32">
|
||||
<img src="@/assets/knowledge/icon_web.svg" style="width: 58%" alt="" />
|
||||
</el-avatar>
|
||||
<div>
|
||||
<div>{{ $t('views.knowledge.knowledgeType.webKnowledge') }}</div>
|
||||
<el-text type="info">
|
||||
{{ $t('views.knowledge.knowledgeType.webInfo') }}
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card
|
||||
shadow="never"
|
||||
class="mb-8 w-full"
|
||||
style="line-height: 22px"
|
||||
v-if="detail?.type === 2"
|
||||
>
|
||||
<div class="flex align-center">
|
||||
<el-avatar shape="square" :size="32" style="background: none">
|
||||
<img src="@/assets/knowledge/logo_lark.svg" style="width: 100%" alt="" />
|
||||
</el-avatar>
|
||||
<div>
|
||||
<p>
|
||||
<el-text>{{ $t('views.knowledge.knowledgeType.larkKnowledge') }}</el-text>
|
||||
</p>
|
||||
<el-text type="info"
|
||||
>{{ $t('views.knowledge.knowledgeType.larkInfo') }}
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('views.knowledge.form.source_url.label')"
|
||||
prop="source_url"
|
||||
v-if="detail.type === 1"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.source_url"
|
||||
:placeholder="$t('views.knowledge.form.source_url.placeholder')"
|
||||
@blur="form.source_url = form.source_url.trim()"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('views.knowledge.form.selector.label')"
|
||||
v-if="detail.type === 1"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.selector"
|
||||
:placeholder="$t('views.knowledge.form.selector.placeholder')"
|
||||
@blur="form.selector = form.selector.trim()"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="App ID" prop="app_id" v-if="detail.type === 2">
|
||||
<el-input
|
||||
v-model="form.app_id"
|
||||
:placeholder="
|
||||
$t('views.application.applicationAccess.larkSetting.appIdPlaceholder')
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="App Secret" prop="app_id" v-if="detail.type === 2">
|
||||
<el-input
|
||||
v-model="form.app_secret"
|
||||
type="password"
|
||||
show-password
|
||||
:placeholder="
|
||||
$t('views.application.applicationAccess.larkSetting.appSecretPlaceholder')
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="Folder Token" prop="folder_token" v-if="detail.type === 2">
|
||||
<el-input
|
||||
v-model="form.folder_token"
|
||||
:placeholder="
|
||||
$t('views.application.applicationAccess.larkSetting.folderTokenPlaceholder')
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<div v-if="detail.type === 0">
|
||||
<h4 class="title-decoration-1 mb-16">
|
||||
{{ $t('common.otherSetting') }}
|
||||
</h4>
|
||||
</div>
|
||||
<el-form-item :label="$t('上传的每个文档最大限制')">
|
||||
<el-slider
|
||||
v-model="form.max_paragraph_char_number"
|
||||
show-input
|
||||
:show-input-controls="false"
|
||||
:min="500"
|
||||
:max="100000"
|
||||
class="custom-slider"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="text-right">
|
||||
<el-button @click="submit" type="primary"> {{ $t('common.save') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, reactive } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import BaseForm from '@/views/shared/knowledge-shared/component/BaseForm.vue'
|
||||
import KnowledgeApi from '@/api/system-shared/knowledge'
|
||||
import type { ApplicationFormType } from '@/api/type/application'
|
||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||
import { isAppIcon } from '@/utils/common'
|
||||
import useStore from '@/stores/modules-shared-system'
|
||||
import { t } from '@/locales'
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id },
|
||||
} = route as any
|
||||
|
||||
const { knowledge } = useStore()
|
||||
const webFormRef = ref()
|
||||
const BaseFormRef = ref()
|
||||
const loading = ref(false)
|
||||
const detail = ref<any>({})
|
||||
const cloneModelId = ref('')
|
||||
|
||||
const form = ref<any>({
|
||||
source_url: '',
|
||||
selector: '',
|
||||
app_id: '',
|
||||
app_secret: '',
|
||||
folder_token: '',
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
source_url: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.knowledge.form.source_url.requiredMessage'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
app_id: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.application.applicationAccess.larkSetting.appIdPlaceholder'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
app_secret: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.application.applicationAccess.larkSetting.appSecretPlaceholder'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
folder_token: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.application.applicationAccess.larkSetting.folderTokenPlaceholder'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
async function submit() {
|
||||
if (await BaseFormRef.value?.validate()) {
|
||||
await webFormRef.value.validate((valid: any) => {
|
||||
if (valid) {
|
||||
const obj =
|
||||
detail.value.type === 1 || detail.value.type === 2
|
||||
? {
|
||||
meta: form.value,
|
||||
...BaseFormRef.value.form,
|
||||
}
|
||||
: {
|
||||
...BaseFormRef.value.form,
|
||||
}
|
||||
|
||||
if (cloneModelId.value !== BaseFormRef.value.form.embedding_model_id) {
|
||||
MsgConfirm(t('common.tip'), t('views.knowledge.tip.updateModeMessage'), {
|
||||
confirmButtonText: t('views.knowledge.setting.vectorization'),
|
||||
})
|
||||
.then(() => {
|
||||
if (detail.value.type === 2) {
|
||||
// KnowledgeApi.putLarkKnowledge(id, obj, loading).then((res) => {
|
||||
// KnowledgeApi.putReEmbeddingKnowledge(id).then(() => {
|
||||
// MsgSuccess(t('common.saveSuccess'))
|
||||
// })
|
||||
// })
|
||||
} else {
|
||||
KnowledgeApi.putKnowledge(id, obj, loading).then((res) => {
|
||||
KnowledgeApi.putReEmbeddingKnowledge(id).then(() => {
|
||||
MsgSuccess(t('common.saveSuccess'))
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
} else {
|
||||
if (detail.value.type === 2) {
|
||||
// KnowledgeApi.putLarkKnowledge(id, obj, loading).then((res) => {
|
||||
// KnowledgeApi.putReEmbeddingKnowledge(id).then(() => {
|
||||
// MsgSuccess(t('common.saveSuccess'))
|
||||
// })
|
||||
// })
|
||||
} else {
|
||||
KnowledgeApi.putKnowledge(id, obj, loading).then((res) => {
|
||||
MsgSuccess(t('common.saveSuccess'))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getDetail() {
|
||||
knowledge.asyncGetKnowledgeDetail(id, loading).then((res: any) => {
|
||||
detail.value = res.data
|
||||
cloneModelId.value = res.data?.embedding_model_id
|
||||
if (detail.value.type === '1' || detail.value.type === '2') {
|
||||
form.value = res.data.meta
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDetail()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.knowledge-setting {
|
||||
width: 70%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,318 +0,0 @@
|
|||
<template>
|
||||
<div class="knowledge-shared">
|
||||
<ContentContainer :header="$t('views.system.shared.shared_knowledge')">
|
||||
<template #search>
|
||||
<div class="flex">
|
||||
<div class="flex-between complex-search">
|
||||
<el-select
|
||||
class="complex-search__left"
|
||||
v-model="search_type"
|
||||
style="width: 120px"
|
||||
@change="search_type_change"
|
||||
>
|
||||
<el-option :label="$t('common.creator')" value="create_user" />
|
||||
|
||||
<el-option :label="$t('common.name')" value="name" />
|
||||
</el-select>
|
||||
<el-input
|
||||
v-if="search_type === 'name'"
|
||||
v-model="search_form.name"
|
||||
@change="getList"
|
||||
:placeholder="$t('common.searchBar.placeholder')"
|
||||
style="width: 220px"
|
||||
clearable
|
||||
/>
|
||||
<el-select
|
||||
v-else-if="search_type === 'create_user'"
|
||||
v-model="search_form.create_user"
|
||||
@change="getList"
|
||||
clearable
|
||||
style="width: 220px"
|
||||
>
|
||||
<el-option v-for="u in user_options" :key="u.id" :value="u.id" :label="u.username" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-loading.fullscreen.lock="paginationConfig.current_page === 1 && loading">
|
||||
<InfiniteScroll
|
||||
:size="knowledgeList.length"
|
||||
:total="paginationConfig.total"
|
||||
:page_size="paginationConfig.page_size"
|
||||
v-model:current_page="paginationConfig.current_page"
|
||||
@load="getList"
|
||||
:loading="loading"
|
||||
>
|
||||
<el-row v-if="knowledgeList.length > 0" :gutter="15">
|
||||
<template v-for="(item, index) in knowledgeList" :key="index">
|
||||
<el-col
|
||||
v-if="item.resource_type === 'folder'"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="12"
|
||||
:lg="8"
|
||||
:xl="6"
|
||||
class="mb-16"
|
||||
>
|
||||
<CardBox
|
||||
:title="item.name"
|
||||
:description="item.desc || $t('common.noData')"
|
||||
class="cursor"
|
||||
>
|
||||
<template #icon>
|
||||
<el-avatar shape="square" :size="32" style="background: none">
|
||||
<AppIcon iconName="app-folder" style="font-size: 32px"></AppIcon>
|
||||
</el-avatar>
|
||||
</template>
|
||||
<template #subTitle>
|
||||
<el-text class="color-secondary lighter" size="small">
|
||||
{{ $t('common.creator') }}: {{ item.username }}
|
||||
</el-text>
|
||||
</template>
|
||||
</CardBox>
|
||||
</el-col>
|
||||
<el-col v-else :xs="24" :sm="12" :md="12" :lg="8" :xl="6" class="mb-16">
|
||||
<CardBox
|
||||
:title="item.name"
|
||||
:description="item.desc"
|
||||
isShared
|
||||
class="cursor"
|
||||
@click="
|
||||
router.push({
|
||||
path: `/knowledge/system/${item.id}/documentShared`,
|
||||
})
|
||||
"
|
||||
>
|
||||
<template #icon>
|
||||
<KnowledgeIcon :type="item.type" />
|
||||
</template>
|
||||
<template #subTitle>
|
||||
<el-text class="color-secondary" size="small">
|
||||
{{ $t('common.creator') }}: {{ item.username }}
|
||||
</el-text>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div class="footer-content flex-between">
|
||||
<div>
|
||||
<span class="bold mr-4">{{ item?.document_count || 0 }}</span>
|
||||
<span class="color-secondary">{{
|
||||
$t('views.knowledge.document_count')
|
||||
}}</span>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="bold mr-4">{{ numberFormat(item?.char_length) || 0 }}</span>
|
||||
<span class="color-secondary">{{ $t('common.character') }}</span>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="bold mr-4">{{ item?.application_mapping_count || 0 }}</span>
|
||||
<span class="color-secondary">{{
|
||||
$t('views.knowledge.relatedApp_count')
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</CardBox>
|
||||
</el-col>
|
||||
</template>
|
||||
</el-row>
|
||||
<el-empty :description="$t('common.noData')" v-else />
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</ContentContainer>
|
||||
|
||||
<component :is="currentCreateDialog" ref="CreateKnowledgeDialogRef" />
|
||||
<CreateFolderDialog ref="CreateFolderDialogRef" @refresh="refreshFolder" />
|
||||
<GenerateRelatedDialog ref="GenerateRelatedDialogRef" />
|
||||
<AuthorizedWorkspace ref="AuthorizedWorkspaceDialogRef"></AuthorizedWorkspace>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, reactive, shallowRef, nextTick } from 'vue'
|
||||
import KnowledgeIcon from '@/views/shared/knowledge-shared/component/KnowledgeIcon.vue'
|
||||
import CreateKnowledgeDialog from './create-component/CreateKnowledgeDialog.vue'
|
||||
import CreateWebKnowledgeDialog from './create-component/CreateWebKnowledgeDialog.vue'
|
||||
import CreateFolderDialog from '@/components/folder-tree/CreateFolderDialog.vue'
|
||||
import GenerateRelatedDialog from '@/components/generate-related-shared-dialog/index.vue'
|
||||
import KnowledgeApi from '@/api/system-shared/knowledge'
|
||||
import KnowledgeWorkspaceApi from '@/api/shared-workspace'
|
||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||
import useStore from '@/stores/modules-shared-system'
|
||||
import { numberFormat } from '@/utils/common'
|
||||
import { t } from '@/locales'
|
||||
import { useRouter } from 'vue-router'
|
||||
import AuthorizedWorkspace from '@/views/system-shared/AuthorizedWorkspaceDialog.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const { folder } = useStore()
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const search_type = ref('name')
|
||||
const search_form = ref<any>({
|
||||
name: '',
|
||||
create_user: '',
|
||||
})
|
||||
|
||||
const user_options = ref<any[]>([])
|
||||
|
||||
const paginationConfig = reactive({
|
||||
current_page: 1,
|
||||
page_size: 30,
|
||||
total: 0,
|
||||
})
|
||||
|
||||
const folderList = ref<any[]>([])
|
||||
const knowledgeList = ref<any[]>([])
|
||||
const currentFolder = ref<any>({})
|
||||
|
||||
const CreateKnowledgeDialogRef = ref()
|
||||
const currentCreateDialog = shallowRef<any>(null)
|
||||
const AuthorizedWorkspaceDialogRef = ref()
|
||||
function openCreateDialog(data: any) {
|
||||
currentCreateDialog.value = data
|
||||
nextTick(() => {
|
||||
CreateKnowledgeDialogRef.value.open(currentFolder.value)
|
||||
})
|
||||
|
||||
// common.asyncGetValid(ValidType.Dataset, ValidCount.Dataset, loading).then(async (res: any) => {
|
||||
// if (res?.data) {
|
||||
// CreateDatasetDialogRef.value.open()
|
||||
// } else if (res?.code === 400) {
|
||||
// MsgConfirm(t('common.tip'), t('views.knowledge.tip.professionalMessage'), {
|
||||
// cancelButtonText: t('common.confirm'),
|
||||
// confirmButtonText: t('common.professional'),
|
||||
// })
|
||||
// .then(() => {
|
||||
// window.open('https://maxkb.cn/pricing.html', '_blank')
|
||||
// })
|
||||
// .catch(() => {})
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
function reEmbeddingKnowledge(row: any) {
|
||||
KnowledgeApi.putReEmbeddingKnowledge(row.id).then(() => {
|
||||
MsgSuccess(t('common.submitSuccess'))
|
||||
})
|
||||
}
|
||||
|
||||
const SyncWebDialogRef = ref()
|
||||
|
||||
function syncKnowledge(row: any) {
|
||||
SyncWebDialogRef.value.open(row.id)
|
||||
}
|
||||
|
||||
const search_type_change = () => {
|
||||
search_form.value = { name: '', create_user: '' }
|
||||
}
|
||||
|
||||
function getList() {
|
||||
const params = {
|
||||
[search_type.value]: search_form.value[search_type.value],
|
||||
}
|
||||
|
||||
if (!search_form.value[search_type.value]) {
|
||||
delete params[search_type.value]
|
||||
}
|
||||
KnowledgeWorkspaceApi.getSharedWorkspaceKnowledgePage(paginationConfig, params, loading).then(
|
||||
(res: any) => {
|
||||
paginationConfig.total = res.data.total
|
||||
knowledgeList.value = [...knowledgeList.value, ...res.data.records]
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
function getFolder() {
|
||||
const params = {}
|
||||
folder.asyncGetFolder('KNOWLEDGE', params, loading).then((res: any) => {
|
||||
folderList.value = res.data
|
||||
currentFolder.value = res.data?.[0] || {}
|
||||
getList()
|
||||
})
|
||||
}
|
||||
|
||||
function folderClickHandel(row: any) {
|
||||
currentFolder.value = row
|
||||
knowledgeList.value = []
|
||||
getList()
|
||||
}
|
||||
|
||||
const CreateFolderDialogRef = ref()
|
||||
|
||||
function openCreateFolder() {
|
||||
CreateFolderDialogRef.value.open('KNOWLEDGE', currentFolder.value.id)
|
||||
}
|
||||
|
||||
const GenerateRelatedDialogRef = ref<InstanceType<typeof GenerateRelatedDialog>>()
|
||||
function openGenerateDialog(row: any) {
|
||||
if (GenerateRelatedDialogRef.value) {
|
||||
GenerateRelatedDialogRef.value.open([], 'knowledge', row.id)
|
||||
}
|
||||
}
|
||||
|
||||
function openAuthorizedWorkspaceDialog(row: any) {
|
||||
if (AuthorizedWorkspaceDialogRef.value) {
|
||||
AuthorizedWorkspaceDialogRef.value.open(row)
|
||||
}
|
||||
}
|
||||
|
||||
const exportKnowledge = (item: any) => {
|
||||
KnowledgeApi.exportKnowledge(item.name, item.id, loading).then((ok) => {
|
||||
MsgSuccess(t('common.exportSuccess'))
|
||||
})
|
||||
}
|
||||
const exportZipKnowledge = (item: any) => {
|
||||
KnowledgeApi.exportZipKnowledge(item.name, item.id, loading).then((ok) => {
|
||||
MsgSuccess(t('common.exportSuccess'))
|
||||
})
|
||||
}
|
||||
|
||||
function deleteKnowledge(row: any) {
|
||||
MsgConfirm(
|
||||
`${t('views.knowledge.delete.confirmTitle')}${row.name} ?`,
|
||||
`${t('views.knowledge.delete.confirmMessage1')} ${row.application_mapping_count} ${t('views.knowledge.delete.confirmMessage2')}`,
|
||||
{
|
||||
confirmButtonText: t('common.confirm'),
|
||||
confirmButtonClass: 'color-danger',
|
||||
},
|
||||
)
|
||||
.then(() => {
|
||||
KnowledgeApi.delKnowledge(row.id, loading).then(() => {
|
||||
const index = knowledgeList.value.findIndex((v) => v.id === row.id)
|
||||
knowledgeList.value.splice(index, 1)
|
||||
MsgSuccess(t('common.deleteSuccess'))
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
function refreshFolder() {
|
||||
getFolder()
|
||||
getList()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.knowledge-shared {
|
||||
padding-left: 8px;
|
||||
.shared-header {
|
||||
color: #646a73;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
:deep(.el-icon i) {
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
color: #1f2329;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
<template>
|
||||
<el-form
|
||||
ref="FormRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
v-loading="loading"
|
||||
>
|
||||
<el-form-item :label="$t('views.knowledge.form.knowledgeName.label')" prop="name">
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
:placeholder="$t('views.knowledge.form.knowledgeName.placeholder')"
|
||||
maxlength="64"
|
||||
show-word-limit
|
||||
@blur="form.name = form.name.trim()"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('views.knowledge.form.knowledgeDescription.label')" prop="desc">
|
||||
<el-input
|
||||
v-model="form.desc"
|
||||
type="textarea"
|
||||
:placeholder="$t('views.knowledge.form.knowledgeDescription.placeholder')"
|
||||
maxlength="256"
|
||||
show-word-limit
|
||||
:autosize="{ minRows: 3 }"
|
||||
@blur="form.desc = form.desc.trim()"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('views.knowledge.form.EmbeddingModel.label')"
|
||||
prop="embedding_model_id"
|
||||
>
|
||||
<ModelSelect
|
||||
v-model="form.embedding_model_id"
|
||||
:placeholder="$t('views.knowledge.form.EmbeddingModel.placeholder')"
|
||||
:options="modelOptions"
|
||||
:model-type="'EMBEDDING'"
|
||||
showFooter
|
||||
></ModelSelect>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, onUnmounted, computed, watch } from 'vue'
|
||||
import { groupBy } from 'lodash'
|
||||
import useStore from '@/stores/modules-shared-system'
|
||||
import type { knowledgeData } from '@/api/type/knowledge'
|
||||
import { t } from '@/locales'
|
||||
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
})
|
||||
const { model } = useStore()
|
||||
const form = ref<knowledgeData>({
|
||||
name: '',
|
||||
desc: '',
|
||||
embedding_model_id: '',
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.knowledge.form.knowledgeName.requiredMessage'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
desc: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.knowledge.form.knowledgeDescription.requiredMessage'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
embedding_model_id: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.knowledge.form.EmbeddingModel.requiredMessage'),
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const FormRef = ref()
|
||||
const loading = ref(false)
|
||||
const modelOptions = ref<any>([])
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(value) => {
|
||||
if (value && JSON.stringify(value) !== '{}') {
|
||||
form.value.name = value.name
|
||||
form.value.desc = value.desc
|
||||
form.value.embedding_model_id = value.embedding_model_id
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
)
|
||||
|
||||
/*
|
||||
表单校验
|
||||
*/
|
||||
function validate() {
|
||||
if (!FormRef.value) return
|
||||
return FormRef.value.validate((valid: any) => {
|
||||
return valid
|
||||
})
|
||||
}
|
||||
|
||||
function getModel() {
|
||||
loading.value = true
|
||||
model
|
||||
.asyncGetModel({ model_type: 'EMBEDDING' })
|
||||
.then((res: any) => {
|
||||
modelOptions.value = groupBy(res?.data, 'provider')
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getModel()
|
||||
})
|
||||
onUnmounted(() => {
|
||||
form.value = {
|
||||
name: '',
|
||||
desc: '',
|
||||
embedding_model_id: '',
|
||||
}
|
||||
FormRef.value?.clearValidate()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
validate,
|
||||
form,
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="$t('views.paragraph.editParagraph')"
|
||||
v-model="dialogVisible"
|
||||
width="80%"
|
||||
destroy-on-close
|
||||
class="paragraph-dialog"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<el-row v-if="isConnect">
|
||||
<el-col :span="18" class="p-24">
|
||||
<ParagraphForm ref="paragraphFormRef" :data="detail" :isEdit="true" />
|
||||
</el-col>
|
||||
<el-col :span="6" class="border-l" style="width: 300px">
|
||||
<p class="bold title p-24" style="padding-bottom: 0">
|
||||
<span class="flex align-center">
|
||||
<span>{{ $t('views.paragraph.relatedProblem.title') }}</span>
|
||||
<el-divider direction="vertical" class="mr-4" />
|
||||
<el-button text @click="addProblem">
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-button>
|
||||
</span>
|
||||
</p>
|
||||
<el-scrollbar height="500px">
|
||||
<div class="p-24" style="padding-top: 16px">
|
||||
<el-input
|
||||
v-if="isAddProblem"
|
||||
v-model="problemValue"
|
||||
:placeholder="$t('views.paragraph.relatedProblem.placeholder')"
|
||||
@change="addProblemHandle"
|
||||
@blur="isAddProblem = false"
|
||||
ref="inputRef"
|
||||
class="mb-8"
|
||||
/>
|
||||
|
||||
<template v-for="(item, index) in detail.problem_list" :key="index">
|
||||
<TagEllipsis
|
||||
@close="delProblemHandle(item, index)"
|
||||
class="question-tag"
|
||||
type="info"
|
||||
effect="plain"
|
||||
closable
|
||||
>
|
||||
<auto-tooltip :content="item.content">
|
||||
{{ item.content }}
|
||||
</auto-tooltip>
|
||||
</TagEllipsis>
|
||||
</template>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div v-else class="p-24">
|
||||
<ParagraphForm ref="paragraphFormRef" :data="detail" :isEdit="true" />
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
|
||||
<el-button type="primary" @click="submitHandle"> {{ $t('common.save') }} </el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, nextTick } from 'vue'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import ParagraphForm from '@/views/paragraph/component/ParagraphForm.vue'
|
||||
|
||||
const props = defineProps({
|
||||
isConnect: Boolean
|
||||
})
|
||||
|
||||
const emit = defineEmits(['updateContent'])
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
||||
const detail = ref<any>({})
|
||||
|
||||
const paragraphFormRef = ref()
|
||||
const inputRef = ref()
|
||||
|
||||
const isAddProblem = ref(false)
|
||||
|
||||
const problemValue = ref('')
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
detail.value = {}
|
||||
}
|
||||
})
|
||||
|
||||
const open = (data: any) => {
|
||||
detail.value = cloneDeep(data)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
function delProblemHandle(item: any, index: number) {
|
||||
detail.value.problem_list.splice(index, 1)
|
||||
}
|
||||
function addProblemHandle() {
|
||||
if (problemValue.value.trim()) {
|
||||
if (
|
||||
!detail.value?.problem_list.some((item: any) => item.content === problemValue.value.trim())
|
||||
) {
|
||||
detail.value?.problem_list?.push({
|
||||
content: problemValue.value.trim()
|
||||
})
|
||||
}
|
||||
|
||||
problemValue.value = ''
|
||||
isAddProblem.value = false
|
||||
}
|
||||
}
|
||||
function addProblem() {
|
||||
isAddProblem.value = true
|
||||
nextTick(() => {
|
||||
inputRef.value?.focus()
|
||||
})
|
||||
}
|
||||
|
||||
const submitHandle = async () => {
|
||||
if (await paragraphFormRef.value?.validate()) {
|
||||
emit('updateContent', {
|
||||
problem_list: detail.value.problem_list,
|
||||
...paragraphFormRef.value?.form
|
||||
})
|
||||
dialogVisible.value = false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
<template>
|
||||
<el-avatar v-if="type === 1" class="avatar-purple" shape="square" :size="32">
|
||||
<img src="@/assets/knowledge/icon_web.svg" style="width: 58%" alt="" />
|
||||
</el-avatar>
|
||||
<el-avatar
|
||||
v-else-if="type === 2"
|
||||
class="avatar-purple"
|
||||
shape="square"
|
||||
:size="32"
|
||||
style="background: none"
|
||||
>
|
||||
<img src="@/assets/knowledge/logo_lark.svg" style="width: 100%" alt="" />
|
||||
</el-avatar>
|
||||
<el-avatar v-else class="avatar-blue" shape="square" :size="32">
|
||||
<img src="@/assets/knowledge/icon_document.svg" style="width: 58%" alt="" />
|
||||
</el-avatar>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: [String, Number],
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<InfiniteScroll
|
||||
:size="paragraph_list.length"
|
||||
:total="modelValue.length"
|
||||
:page_size="page_size"
|
||||
v-model:current_page="current_page"
|
||||
@load="next()"
|
||||
:loading="loading"
|
||||
>
|
||||
<el-card
|
||||
v-for="(child, cIndex) in paragraph_list"
|
||||
:key="cIndex"
|
||||
shadow="never"
|
||||
class="card-never mb-16"
|
||||
>
|
||||
<div class="flex-between">
|
||||
<span>{{ child.title || '-' }}</span>
|
||||
<div>
|
||||
<!-- 编辑分段按钮 -->
|
||||
<el-button link @click="editHandle(child, cIndex)">
|
||||
<el-icon><EditPen /></el-icon>
|
||||
</el-button>
|
||||
<!-- 删除分段按钮 -->
|
||||
<el-button link @click="deleteHandle(child, cIndex)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lighter mt-12">
|
||||
{{ child.content }}
|
||||
</div>
|
||||
<div class="lighter mt-12">
|
||||
<el-text type="info">
|
||||
{{ child.content.length }} {{ $t('views.paragraph.character_count') }}
|
||||
</el-text>
|
||||
</div>
|
||||
</el-card>
|
||||
</InfiniteScroll>
|
||||
|
||||
<EditParagraphDialog
|
||||
ref="EditParagraphDialogRef"
|
||||
@updateContent="updateContent"
|
||||
:isConnect="isConnect"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { ref, computed } from 'vue'
|
||||
import EditParagraphDialog from './EditParagraphDialog.vue'
|
||||
import { MsgConfirm } from '@/utils/message'
|
||||
import { t } from '@/locales'
|
||||
const page_size = ref<number>(30)
|
||||
const current_page = ref<number>(1)
|
||||
const currentCIndex = ref<number>(0)
|
||||
const EditParagraphDialogRef = ref()
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const loading = ref<boolean>(false)
|
||||
const editHandle = (item: any, cIndex: number) => {
|
||||
currentCIndex.value = cIndex
|
||||
EditParagraphDialogRef.value.open(item)
|
||||
}
|
||||
|
||||
const props = defineProps<{ modelValue: Array<any>; isConnect: boolean }>()
|
||||
|
||||
const paragraph_list = computed(() => {
|
||||
return props.modelValue.slice(0, page_size.value * (current_page.value - 1) + page_size.value)
|
||||
})
|
||||
|
||||
const next = () => {
|
||||
loading.value = true
|
||||
current_page.value += 1
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const updateContent = (data: any) => {
|
||||
const new_value = [...props.modelValue]
|
||||
if (
|
||||
props.isConnect &&
|
||||
data.title &&
|
||||
!data?.problem_list.some((item: any) => item.content === data.title.trim())
|
||||
) {
|
||||
data['problem_list'].push({
|
||||
content: data.title.trim()
|
||||
})
|
||||
}
|
||||
new_value[currentCIndex.value] = cloneDeep(data)
|
||||
emit('update:modelValue', new_value)
|
||||
}
|
||||
|
||||
const deleteHandle = (item: any, cIndex: number) => {
|
||||
MsgConfirm(
|
||||
`${t('views.paragraph.delete.confirmTitle')}${item.title || '-'} ?`,
|
||||
t('views.paragraph.delete.confirmMessage'),
|
||||
{
|
||||
confirmButtonText: t('common.confirm'),
|
||||
confirmButtonClass: 'danger'
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
const new_value = [...props.modelValue]
|
||||
new_value.splice(cIndex, 1)
|
||||
emit('update:modelValue', new_value)
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
<template>
|
||||
<el-tabs v-model="activeName" class="paragraph-tabs">
|
||||
<template v-for="(item, index) in data" :key="index">
|
||||
<el-tab-pane :label="item.name" :name="index">
|
||||
<template #label>
|
||||
<div class="flex-center">
|
||||
<img :src="getImgUrl(item && item?.name)" alt="" height="16" />
|
||||
<span class="ml-4">{{ item?.name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="mb-16">
|
||||
<el-text type="info"
|
||||
>{{ item.content.length }} {{ $t('views.paragraph.title') }}</el-text
|
||||
>
|
||||
</div>
|
||||
<div class="paragraph-list" v-if="activeName == index">
|
||||
<el-scrollbar>
|
||||
<ParagraphList v-model="item.content" :isConnect="isConnect"> </ParagraphList>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
</el-tabs>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { getImgUrl } from '@/utils/utils'
|
||||
import ParagraphList from './ParagraphList.vue'
|
||||
|
||||
defineProps({
|
||||
data: {
|
||||
type: Array<any>,
|
||||
default: () => []
|
||||
},
|
||||
isConnect: Boolean
|
||||
})
|
||||
|
||||
const activeName = ref(0)
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.paragraph-tabs {
|
||||
:deep(.el-tabs__item) {
|
||||
background: var(--app-text-color-light-1);
|
||||
margin: 4px;
|
||||
border-radius: 4px;
|
||||
padding: 5px 10px 5px 8px !important;
|
||||
height: auto;
|
||||
&:nth-child(2) {
|
||||
margin-left: 0;
|
||||
}
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
&.is-active {
|
||||
border: 1px solid var(--el-color-primary);
|
||||
background: var(--el-color-primary-light-9);
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
:deep(.el-tabs__nav-wrap::after) {
|
||||
display: none;
|
||||
}
|
||||
:deep(.el-tabs__active-bar) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.paragraph-list {
|
||||
height: calc(var(--create-dataset-height) - 101px);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="$t('views.knowledge.syncWeb.title')"
|
||||
v-model="dialogVisible"
|
||||
width="600px"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<p class="mb-8">{{ $t('views.knowledge.syncWeb.syncMethod') }}</p>
|
||||
<el-radio-group v-model="method" class="card__radio">
|
||||
<el-card shadow="never" class="mb-16" :class="method === 'replace' ? 'active' : ''">
|
||||
<el-radio value="replace" size="large">
|
||||
<p class="mb-4">{{ $t('views.knowledge.syncWeb.replace') }}</p>
|
||||
<el-text type="info">{{ $t('views.knowledge.syncWeb.replaceText') }}</el-text>
|
||||
</el-radio>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="mb-16" :class="method === 'complete' ? 'active' : ''">
|
||||
<el-radio value="complete" size="large">
|
||||
<p class="mb-4">{{ $t('views.knowledge.syncWeb.complete') }}</p>
|
||||
<el-text type="info">{{ $t('views.knowledge.syncWeb.completeText') }}</el-text>
|
||||
</el-radio>
|
||||
</el-card>
|
||||
</el-radio-group>
|
||||
<p class="danger">{{ $t('views.knowledge.syncWeb.tip') }}</p>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
|
||||
<el-button type="primary" @click="submit" :loading="loading">
|
||||
{{ $t('common.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import knowledgeApi from '@/api/system-shared/knowledge'
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
const loading = ref<boolean>(false)
|
||||
const method = ref('replace')
|
||||
const knowledgeId = ref('')
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
method.value = 'replace'
|
||||
}
|
||||
})
|
||||
|
||||
const open = (id: string) => {
|
||||
knowledgeId.value = id
|
||||
dialogVisible.value = true
|
||||
}
|
||||
const submit = () => {
|
||||
knowledgeApi.putSyncWebKnowledge(knowledgeId.value, method.value, loading).then((res: any) => {
|
||||
emit('refresh', res.data)
|
||||
dialogVisible.value = false
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.select-provider {
|
||||
font-size: 16px;
|
||||
color: rgba(100, 106, 115, 1);
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
.active-breadcrumb {
|
||||
font-size: 16px;
|
||||
color: rgba(31, 35, 41, 1);
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="$t('views.knowledge.knowledgeType.createGeneralKnowledge')"
|
||||
v-model="dialogVisible"
|
||||
width="720"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<!-- 基本信息 -->
|
||||
<BaseForm ref="BaseFormRef" v-if="dialogVisible" />
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false" :loading="loading">
|
||||
{{ $t('common.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="submitHandle" :loading="loading">
|
||||
{{ $t('common.create') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, reactive } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import BaseForm from '@/views/shared/knowledge-shared/component/BaseForm.vue'
|
||||
import KnowledgeApi from '@/api/system-shared/knowledge'
|
||||
import { MsgSuccess, MsgAlert } from '@/utils/message'
|
||||
import { t } from '@/locales'
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const router = useRouter()
|
||||
const BaseFormRef = ref()
|
||||
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const currentFolder = ref<any>(null)
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
currentFolder.value = null
|
||||
}
|
||||
})
|
||||
|
||||
const open = (folder: string) => {
|
||||
currentFolder.value = folder
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const submitHandle = async () => {
|
||||
if (await BaseFormRef.value?.validate()) {
|
||||
const obj = {
|
||||
folder_id: currentFolder.value?.id,
|
||||
...BaseFormRef.value.form,
|
||||
}
|
||||
KnowledgeApi.postKnowledge(obj, loading).then((res) => {
|
||||
MsgSuccess(t('common.createSuccess'))
|
||||
// router.push({ path: `/knowledge/${res.data.id}/${currentFolder.value.id}/document` })
|
||||
emit('refresh')
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,161 +0,0 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="$t('views.knowledge.knowledgeType.createLarkKnowledge')"
|
||||
v-model="dialogVisible"
|
||||
width="720"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<!-- 基本信息 -->
|
||||
<BaseForm ref="BaseFormRef" v-if="dialogVisible" />
|
||||
<el-form
|
||||
ref="KnowledgeFormRef"
|
||||
:rules="rules"
|
||||
:model="datasetForm"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
>
|
||||
<el-form-item label="App ID" prop="app_id">
|
||||
<el-input
|
||||
v-model="datasetForm.app_id"
|
||||
:placeholder="$t('views.application.applicationAccess.larkSetting.appIdPlaceholder')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="App Secret" prop="app_secret">
|
||||
<el-input
|
||||
v-model="datasetForm.app_secret"
|
||||
type="password"
|
||||
show-password
|
||||
:placeholder="$t('views.application.applicationAccess.larkSetting.appSecretPlaceholder')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="Folder Token" prop="folder_token">
|
||||
<el-input
|
||||
v-model="datasetForm.folder_token"
|
||||
:placeholder="
|
||||
$t('views.application.applicationAccess.larkSetting.folderTokenPlaceholder')
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false" :loading="loading">
|
||||
{{ $t('common.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="submitHandle" :loading="loading">
|
||||
{{ $t('common.create') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, reactive } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import BaseForm from '@/views/shared/knowledge-shared/component/BaseForm.vue'
|
||||
import KnowledgeApi from '@/api/system-shared/knowledge'
|
||||
import { MsgSuccess, MsgAlert } from '@/utils/message'
|
||||
import { t } from '@/locales'
|
||||
import { ComplexPermission } from '@/utils/permission/type'
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const router = useRouter()
|
||||
const BaseFormRef = ref()
|
||||
const KnowledgeFormRef = ref()
|
||||
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
||||
const datasetForm = ref<any>({
|
||||
type: '0',
|
||||
source_url: '',
|
||||
selector: '',
|
||||
app_id: '',
|
||||
app_secret: '',
|
||||
folder_token: '',
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
source_url: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.knowledge.form.source_url.requiredMessage'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
app_id: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.application.applicationAccess.larkSetting.appIdPlaceholder'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
app_secret: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.application.applicationAccess.larkSetting.appSecretPlaceholder'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
folder_token: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.application.applicationAccess.larkSetting.folderTokenPlaceholder'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
user_id: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.knowledge.form.user_id.requiredMessage'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
token: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.knowledge.form.token.requiredMessage'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
datasetForm.value = {
|
||||
type: '0',
|
||||
source_url: '',
|
||||
selector: '',
|
||||
}
|
||||
KnowledgeFormRef.value?.clearValidate()
|
||||
}
|
||||
})
|
||||
|
||||
const open = () => {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const submitHandle = async () => {
|
||||
if (await BaseFormRef.value?.validate()) {
|
||||
await KnowledgeFormRef.value.validate((valid: any) => {
|
||||
if (valid) {
|
||||
const obj = { ...BaseFormRef.value.form, ...datasetForm.value }
|
||||
KnowledgeApi.postLarkKnowledge(obj, loading).then((res: any) => {
|
||||
MsgSuccess(t('common.createSuccess'))
|
||||
router.push({ path: `/knowledge/${res.data.id}/document` })
|
||||
emit('refresh')
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="$t('views.knowledge.knowledgeType.createGeneralKnowledge')"
|
||||
v-model="dialogVisible"
|
||||
width="720"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<!-- 基本信息 -->
|
||||
<BaseForm ref="BaseFormRef" v-if="dialogVisible" />
|
||||
<el-form
|
||||
ref="KnowledgeFormRef"
|
||||
:rules="rules"
|
||||
:model="knowledgeForm"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
>
|
||||
<el-form-item :label="$t('views.knowledge.form.source_url.label')" prop="source_url">
|
||||
<el-input
|
||||
v-model="knowledgeForm.source_url"
|
||||
:placeholder="$t('views.knowledge.form.source_url.placeholder')"
|
||||
@blur="knowledgeForm.source_url = knowledgeForm.source_url.trim()"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('views.knowledge.form.selector.label')">
|
||||
<el-input
|
||||
v-model="knowledgeForm.selector"
|
||||
:placeholder="$t('views.knowledge.form.selector.placeholder')"
|
||||
@blur="knowledgeForm.selector = knowledgeForm.selector.trim()"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false" :loading="loading">
|
||||
{{ $t('common.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="submitHandle" :loading="loading">
|
||||
{{ $t('common.create') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, reactive } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import BaseForm from '@/views/shared/knowledge-shared/component/BaseForm.vue'
|
||||
import KnowledgeApi from '@/api/system-shared/knowledge'
|
||||
import { MsgSuccess, MsgAlert } from '@/utils/message'
|
||||
import { t } from '@/locales'
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const router = useRouter()
|
||||
const BaseFormRef = ref()
|
||||
const KnowledgeFormRef = ref()
|
||||
|
||||
const loading = ref(false)
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
||||
const knowledgeForm = ref<any>({
|
||||
source_url: '',
|
||||
selector: '',
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
source_url: [
|
||||
{
|
||||
required: true,
|
||||
message: t('views.knowledge.form.source_url.requiredMessage'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const currentFolder = ref<any>(null)
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
currentFolder.value = null
|
||||
knowledgeForm.value = {
|
||||
source_url: '',
|
||||
selector: '',
|
||||
}
|
||||
KnowledgeFormRef.value?.clearValidate()
|
||||
}
|
||||
})
|
||||
|
||||
const open = (folder: string) => {
|
||||
currentFolder.value = folder
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const submitHandle = async () => {
|
||||
if (await BaseFormRef.value?.validate()) {
|
||||
await KnowledgeFormRef.value.validate((valid: any) => {
|
||||
if (valid) {
|
||||
const obj = {
|
||||
folder_id: currentFolder.value?.id,
|
||||
...BaseFormRef.value.form,
|
||||
...knowledgeForm.value,
|
||||
}
|
||||
KnowledgeApi.postWebKnowledge(obj, loading).then((res) => {
|
||||
MsgSuccess(t('common.createSuccess'))
|
||||
router.push({
|
||||
path: `/knowledge/system/${res.data.id}/documentShared`,
|
||||
})
|
||||
emit('refresh')
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,464 +0,0 @@
|
|||
<template>
|
||||
<div class="knowledge-shared">
|
||||
<ContentContainer>
|
||||
<template #header>
|
||||
<div class="shared-header">
|
||||
<span class="title">{{ t('views.system.shared.shared_resources') }}</span>
|
||||
<el-icon size="12">
|
||||
<rightOutlined></rightOutlined>
|
||||
</el-icon>
|
||||
<span class="sub-title">{{ t('views.knowledge.title') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #search>
|
||||
<div class="flex">
|
||||
<div class="flex-between complex-search">
|
||||
<el-select
|
||||
class="complex-search__left"
|
||||
v-model="search_type"
|
||||
style="width: 120px"
|
||||
@change="search_type_change"
|
||||
>
|
||||
<el-option :label="$t('common.creator')" value="create_user" />
|
||||
|
||||
<el-option :label="$t('common.name')" value="name" />
|
||||
</el-select>
|
||||
<el-input
|
||||
v-if="search_type === 'name'"
|
||||
v-model="search_form.name"
|
||||
@change="getList"
|
||||
:placeholder="$t('common.searchBar.placeholder')"
|
||||
style="width: 220px"
|
||||
clearable
|
||||
/>
|
||||
<el-select
|
||||
v-else-if="search_type === 'create_user'"
|
||||
v-model="search_form.create_user"
|
||||
@change="getList"
|
||||
clearable
|
||||
style="width: 220px"
|
||||
>
|
||||
<el-option v-for="u in user_options" :key="u.id" :value="u.id" :label="u.username" />
|
||||
</el-select>
|
||||
</div>
|
||||
<el-dropdown trigger="click">
|
||||
<el-button type="primary" class="ml-8">
|
||||
{{ $t('common.create') }}
|
||||
<el-icon class="el-icon--right">
|
||||
<arrow-down />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu class="create-dropdown">
|
||||
<el-dropdown-item @click="openCreateDialog(CreateKnowledgeDialog)">
|
||||
<div class="flex">
|
||||
<el-avatar class="avatar-blue mt-4" shape="square" :size="32">
|
||||
<img src="@/assets/knowledge/icon_document.svg" style="width: 58%" alt="" />
|
||||
</el-avatar>
|
||||
<div class="pre-wrap ml-8">
|
||||
<div class="lighter">
|
||||
{{ $t('views.knowledge.knowledgeType.generalKnowledge') }}
|
||||
</div>
|
||||
<el-text type="info" size="small"
|
||||
>{{ $t('views.knowledge.knowledgeType.generalInfo') }}
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="openCreateDialog(CreateWebKnowledgeDialog)">
|
||||
<div class="flex">
|
||||
<el-avatar class="avatar-purple mt-4" shape="square" :size="32">
|
||||
<img src="@/assets/knowledge/icon_web.svg" style="width: 58%" alt="" />
|
||||
</el-avatar>
|
||||
<div class="pre-wrap ml-8">
|
||||
<div class="lighter">
|
||||
{{ $t('views.knowledge.knowledgeType.webKnowledge') }}
|
||||
</div>
|
||||
<el-text type="info" size="small"
|
||||
>{{ $t('views.knowledge.knowledgeType.webInfo') }}
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="openCreateDialog(CreateLarkKnowledgeDialog)">
|
||||
<div class="flex">
|
||||
<el-avatar
|
||||
class="avatar-purple mt-4"
|
||||
shape="square"
|
||||
:size="32"
|
||||
style="background: none"
|
||||
>
|
||||
<img src="@/assets/knowledge/logo_lark.svg" alt="" />
|
||||
</el-avatar>
|
||||
<div class="pre-wrap ml-8">
|
||||
<div class="lighter">
|
||||
{{ $t('views.knowledge.knowledgeType.larkKnowledge') }}
|
||||
</div>
|
||||
<el-text type="info" size="small"
|
||||
>{{ $t('views.knowledge.knowledgeType.larkInfo') }}
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>
|
||||
<div class="flex">
|
||||
<el-avatar
|
||||
class="avatar-purple mt-4"
|
||||
shape="square"
|
||||
:size="32"
|
||||
style="background: none"
|
||||
>
|
||||
<img src="@/assets/knowledge/logo_yuque.svg" alt="" />
|
||||
</el-avatar>
|
||||
<div class="pre-wrap ml-8">
|
||||
<div class="lighter">
|
||||
{{ $t('views.knowledge.knowledgeType.yuqueKnowledge') }}
|
||||
</div>
|
||||
<el-text type="info" size="small"
|
||||
>{{ $t('views.knowledge.knowledgeType.yuqueInfo') }}
|
||||
</el-text>
|
||||
</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
<div v-loading.fullscreen.lock="paginationConfig.current_page === 1 && loading">
|
||||
<InfiniteScroll
|
||||
:size="knowledgeList.length"
|
||||
:total="paginationConfig.total"
|
||||
:page_size="paginationConfig.page_size"
|
||||
v-model:current_page="paginationConfig.current_page"
|
||||
@load="getList"
|
||||
:loading="loading"
|
||||
>
|
||||
<el-row v-if="knowledgeList.length > 0" :gutter="15">
|
||||
<template v-for="(item, index) in knowledgeList" :key="index">
|
||||
<el-col
|
||||
v-if="item.resource_type === 'folder'"
|
||||
:xs="24"
|
||||
:sm="12"
|
||||
:md="12"
|
||||
:lg="8"
|
||||
:xl="6"
|
||||
class="mb-16"
|
||||
>
|
||||
<CardBox
|
||||
:title="item.name"
|
||||
:description="item.desc || $t('common.noData')"
|
||||
class="cursor"
|
||||
>
|
||||
<template #icon>
|
||||
<el-avatar shape="square" :size="32" style="background: none">
|
||||
<AppIcon iconName="app-folder" style="font-size: 32px"></AppIcon>
|
||||
</el-avatar>
|
||||
</template>
|
||||
<template #subTitle>
|
||||
<el-text class="color-secondary lighter" size="small">
|
||||
{{ $t('common.creator') }}: {{ item.nick_name }}
|
||||
</el-text>
|
||||
</template>
|
||||
</CardBox>
|
||||
</el-col>
|
||||
<el-col v-else :xs="24" :sm="12" :md="12" :lg="8" :xl="6" class="mb-16">
|
||||
<CardBox
|
||||
:title="item.name"
|
||||
:description="item.desc"
|
||||
isShared
|
||||
class="cursor"
|
||||
@click="
|
||||
router.push({
|
||||
path: `/knowledge/system/${item.id}/documentShared`,
|
||||
})
|
||||
"
|
||||
>
|
||||
<template #icon>
|
||||
<KnowledgeIcon :type="item.type" />
|
||||
</template>
|
||||
<template #subTitle>
|
||||
<el-text class="color-secondary" size="small">
|
||||
{{ $t('common.creator') }}: {{ item.nick_name }}
|
||||
</el-text>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div class="footer-content flex-between">
|
||||
<div>
|
||||
<span class="bold mr-4">{{ item?.document_count || 0 }}</span>
|
||||
<span class="color-secondary">{{
|
||||
$t('views.knowledge.document_count')
|
||||
}}</span>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="bold mr-4">{{ numberFormat(item?.char_length) || 0 }}</span>
|
||||
<span class="color-secondary">{{ $t('common.character') }}</span>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="bold mr-4">{{ item?.application_mapping_count || 0 }}</span>
|
||||
<span class="color-secondary">{{
|
||||
$t('views.knowledge.relatedApp_count')
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #mouseEnter>
|
||||
<div @click.stop>
|
||||
<el-dropdown trigger="click">
|
||||
<el-button text @click.stop>
|
||||
<el-icon>
|
||||
<MoreFilled />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item
|
||||
icon="Refresh"
|
||||
@click.stop="syncKnowledge(item)"
|
||||
v-if="item.type === 1"
|
||||
>{{ $t('views.knowledge.setting.sync') }}
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click.stop="reEmbeddingKnowledge(item)">
|
||||
<AppIcon iconName="app-vectorization"></AppIcon>
|
||||
{{ $t('views.knowledge.setting.vectorization') }}
|
||||
</el-dropdown-item>
|
||||
|
||||
<el-dropdown-item
|
||||
icon="Connection"
|
||||
@click.stop="openGenerateDialog(item)"
|
||||
>{{ $t('views.document.generateQuestion.title') }}</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item
|
||||
icon="Lock"
|
||||
@click.stop="openAuthorizedWorkspaceDialog(item)"
|
||||
>{{ $t('views.system.shared.authorized_workspace') }}</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item
|
||||
icon="Setting"
|
||||
@click.stop="
|
||||
router.push({
|
||||
path: `/knowledge/system/${item.id}/settingShared`,
|
||||
})
|
||||
"
|
||||
>
|
||||
{{ $t('common.setting') }}</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item @click.stop="exportKnowledge(item)">
|
||||
<AppIcon iconName="app-export"></AppIcon
|
||||
>{{ $t('views.document.setting.export') }} Excel</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item @click.stop="exportZipKnowledge(item)">
|
||||
<AppIcon iconName="app-export"></AppIcon
|
||||
>{{ $t('views.document.setting.export') }} ZIP</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item icon="Delete" @click.stop="deleteKnowledge(item)">{{
|
||||
$t('common.delete')
|
||||
}}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
</CardBox>
|
||||
</el-col>
|
||||
</template>
|
||||
</el-row>
|
||||
<el-empty :description="$t('common.noData')" v-else />
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</ContentContainer>
|
||||
|
||||
<component :is="currentCreateDialog" ref="CreateKnowledgeDialogRef" />
|
||||
<CreateFolderDialog ref="CreateFolderDialogRef" @refresh="refreshFolder" />
|
||||
<GenerateRelatedDialog ref="GenerateRelatedDialogRef" />
|
||||
<AuthorizedWorkspace ref="AuthorizedWorkspaceDialogRef"></AuthorizedWorkspace>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, reactive, shallowRef, nextTick } from 'vue'
|
||||
import KnowledgeIcon from '@/views/shared/knowledge-shared/component/KnowledgeIcon.vue'
|
||||
import CreateKnowledgeDialog from './create-component/CreateKnowledgeDialog.vue'
|
||||
import CreateWebKnowledgeDialog from './create-component/CreateWebKnowledgeDialog.vue'
|
||||
import CreateLarkKnowledgeDialog from './create-component/CreateLarkKnowledgeDialog.vue'
|
||||
import CreateFolderDialog from '@/components/folder-tree/CreateFolderDialog.vue'
|
||||
import GenerateRelatedDialog from '@/components/generate-related-shared-dialog/index.vue'
|
||||
import KnowledgeApi from '@/api/system-shared/knowledge'
|
||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||
import useStore from '@/stores/modules-shared-system'
|
||||
import { numberFormat } from '@/utils/common'
|
||||
import iconMap from '@/components/app-icon/icons/common'
|
||||
import { t } from '@/locales'
|
||||
import { useRouter } from 'vue-router'
|
||||
import AuthorizedWorkspace from '@/views/system-shared/AuthorizedWorkspaceDialog.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const { folder } = useStore()
|
||||
|
||||
const loading = ref(false)
|
||||
const rightOutlined = iconMap['right-outlined'].iconReader()
|
||||
|
||||
const search_type = ref('name')
|
||||
const search_form = ref<any>({
|
||||
name: '',
|
||||
create_user: '',
|
||||
})
|
||||
|
||||
const user_options = ref<any[]>([])
|
||||
|
||||
const paginationConfig = reactive({
|
||||
current_page: 1,
|
||||
page_size: 30,
|
||||
total: 0,
|
||||
})
|
||||
|
||||
const folderList = ref<any[]>([])
|
||||
const knowledgeList = ref<any[]>([])
|
||||
const currentFolder = ref<any>({})
|
||||
|
||||
const CreateKnowledgeDialogRef = ref()
|
||||
const currentCreateDialog = shallowRef<any>(null)
|
||||
const AuthorizedWorkspaceDialogRef = ref()
|
||||
function openCreateDialog(data: any) {
|
||||
currentCreateDialog.value = data
|
||||
nextTick(() => {
|
||||
CreateKnowledgeDialogRef.value.open(currentFolder.value)
|
||||
})
|
||||
|
||||
// common.asyncGetValid(ValidType.Dataset, ValidCount.Dataset, loading).then(async (res: any) => {
|
||||
// if (res?.data) {
|
||||
// CreateDatasetDialogRef.value.open()
|
||||
// } else if (res?.code === 400) {
|
||||
// MsgConfirm(t('common.tip'), t('views.knowledge.tip.professionalMessage'), {
|
||||
// cancelButtonText: t('common.confirm'),
|
||||
// confirmButtonText: t('common.professional'),
|
||||
// })
|
||||
// .then(() => {
|
||||
// window.open('https://maxkb.cn/pricing.html', '_blank')
|
||||
// })
|
||||
// .catch(() => {})
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
function reEmbeddingKnowledge(row: any) {
|
||||
KnowledgeApi.putReEmbeddingKnowledge(row.id).then(() => {
|
||||
MsgSuccess(t('common.submitSuccess'))
|
||||
})
|
||||
}
|
||||
|
||||
const SyncWebDialogRef = ref()
|
||||
|
||||
function syncKnowledge(row: any) {
|
||||
SyncWebDialogRef.value.open(row.id)
|
||||
}
|
||||
|
||||
const search_type_change = () => {
|
||||
search_form.value = { name: '', create_user: '' }
|
||||
}
|
||||
|
||||
function getList() {
|
||||
const params = {
|
||||
folder_id: currentFolder.value?.id || 'default',
|
||||
[search_type.value]: search_form.value[search_type.value],
|
||||
}
|
||||
|
||||
KnowledgeApi.getKnowledgeListPage(paginationConfig, params, loading).then((res: any) => {
|
||||
paginationConfig.total = res.data.total
|
||||
knowledgeList.value = [...knowledgeList.value, ...res.data.records]
|
||||
})
|
||||
}
|
||||
|
||||
function getFolder() {
|
||||
const params = {}
|
||||
folder.asyncGetFolder('KNOWLEDGE', params, loading).then((res: any) => {
|
||||
folderList.value = res.data
|
||||
currentFolder.value = res.data?.[0] || {}
|
||||
getList()
|
||||
})
|
||||
}
|
||||
|
||||
function folderClickHandel(row: any) {
|
||||
currentFolder.value = row
|
||||
knowledgeList.value = []
|
||||
getList()
|
||||
}
|
||||
|
||||
const CreateFolderDialogRef = ref()
|
||||
|
||||
function openCreateFolder() {
|
||||
CreateFolderDialogRef.value.open('KNOWLEDGE', currentFolder.value.id)
|
||||
}
|
||||
|
||||
const GenerateRelatedDialogRef = ref<InstanceType<typeof GenerateRelatedDialog>>()
|
||||
function openGenerateDialog(row: any) {
|
||||
if (GenerateRelatedDialogRef.value) {
|
||||
GenerateRelatedDialogRef.value.open([], 'knowledge', row.id)
|
||||
}
|
||||
}
|
||||
|
||||
function openAuthorizedWorkspaceDialog(row: any) {
|
||||
if (AuthorizedWorkspaceDialogRef.value) {
|
||||
AuthorizedWorkspaceDialogRef.value.open(row)
|
||||
}
|
||||
}
|
||||
|
||||
const exportKnowledge = (item: any) => {
|
||||
KnowledgeApi.exportKnowledge(item.name, item.id, loading).then(() => {
|
||||
MsgSuccess(t('common.exportSuccess'))
|
||||
})
|
||||
}
|
||||
const exportZipKnowledge = (item: any) => {
|
||||
KnowledgeApi.exportZipKnowledge(item.name, item.id, loading).then(() => {
|
||||
MsgSuccess(t('common.exportSuccess'))
|
||||
})
|
||||
}
|
||||
|
||||
function deleteKnowledge(row: any) {
|
||||
MsgConfirm(
|
||||
`${t('views.knowledge.delete.confirmTitle')}${row.name} ?`,
|
||||
`${t('views.knowledge.delete.confirmMessage1')} ${row.application_mapping_count} ${t('views.knowledge.delete.confirmMessage2')}`,
|
||||
{
|
||||
confirmButtonText: t('common.confirm'),
|
||||
confirmButtonClass: 'color-danger',
|
||||
},
|
||||
)
|
||||
.then(() => {
|
||||
KnowledgeApi.delKnowledge(row.id, loading).then(() => {
|
||||
const index = knowledgeList.value.findIndex((v) => v.id === row.id)
|
||||
knowledgeList.value.splice(index, 1)
|
||||
MsgSuccess(t('common.deleteSuccess'))
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
function refreshFolder() {
|
||||
getFolder()
|
||||
getList()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.knowledge-shared {
|
||||
padding-left: 8px;
|
||||
.shared-header {
|
||||
color: #646a73;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
:deep(.el-icon i) {
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
color: #1f2329;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,182 +0,0 @@
|
|||
<template>
|
||||
<el-card
|
||||
shadow="hover"
|
||||
class="paragraph-box cursor"
|
||||
@mouseenter="cardEnter()"
|
||||
@mouseleave="cardLeave()"
|
||||
>
|
||||
<h2 class="mb-16">{{ data.title || '-' }}</h2>
|
||||
<el-card
|
||||
v-show="show"
|
||||
class="paragraph-box-operation mt-8 mr-8"
|
||||
shadow="always"
|
||||
style="--el-card-padding: 8px 12px; --el-card-border-radius: 8px"
|
||||
>
|
||||
<el-switch
|
||||
:loading="changeStateloading"
|
||||
v-model="data.is_active"
|
||||
:before-change="() => changeState(data)"
|
||||
size="small"
|
||||
/>
|
||||
|
||||
<el-divider direction="vertical" />
|
||||
<span class="mr-8">
|
||||
<el-button link @click="editParagraph(data)">
|
||||
<el-icon :size="16" :title="$t('views.applicationWorkflow.control.zoomOut')">
|
||||
<EditPen />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</span>
|
||||
<span class="mr-8">
|
||||
<el-button link>
|
||||
<el-icon :size="16" :title="$t('views.applicationWorkflow.control.zoomOut')">
|
||||
<el-icon><CirclePlus /></el-icon>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</span>
|
||||
<el-dropdown trigger="click" :teleported="false">
|
||||
<el-button text>
|
||||
<el-icon><MoreFilled /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="openGenerateDialog(data)">
|
||||
<el-icon><Connection /></el-icon>
|
||||
{{ $t('views.document.generateQuestion.title') }}</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item @click="openSelectDocumentDialog(data)">
|
||||
<AppIcon iconName="app-migrate"></AppIcon>
|
||||
{{ $t('views.document.setting.migration') }}</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item icon="Delete" @click.stop="deleteParagraph(data)">{{
|
||||
$t('common.delete')
|
||||
}}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-card>
|
||||
<MdPreview
|
||||
ref="editorRef"
|
||||
editorId="preview-only"
|
||||
:modelValue="data.content"
|
||||
class="maxkb-md"
|
||||
/>
|
||||
|
||||
<ParagraphDialog ref="ParagraphDialogRef" :title="title" @refresh="refresh" />
|
||||
<SelectDocumentDialog ref="SelectDocumentDialogRef" @refresh="refreshMigrateParagraph" />
|
||||
<GenerateRelatedDialog ref="GenerateRelatedDialogRef" @refresh="refresh" />
|
||||
</el-card>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, useSlots } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { t } from '@/locales'
|
||||
import useStore from '@/stores'
|
||||
import GenerateRelatedDialog from '@/components/generate-related-shared-dialog/index.vue'
|
||||
import ParagraphDialog from '@/views/paragraph/component/ParagraphDialog.vue'
|
||||
import SelectDocumentDialog from '@/views/paragraph/component/SelectDocumentDialog.vue'
|
||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||
|
||||
const { paragraph } = useStore()
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id, documentId },
|
||||
} = route as any
|
||||
const props = defineProps<{
|
||||
data: any
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['changeState', 'deleteParagraph'])
|
||||
const loading = ref(false)
|
||||
const changeStateloading = ref(false)
|
||||
const show = ref(false)
|
||||
// card上面存在dropdown菜单
|
||||
const subHovered = ref(false)
|
||||
function cardEnter() {
|
||||
show.value = true
|
||||
subHovered.value = false
|
||||
}
|
||||
|
||||
function cardLeave() {
|
||||
show.value = subHovered.value
|
||||
}
|
||||
|
||||
function changeState(row: any) {
|
||||
const obj = {
|
||||
is_active: !row.is_active,
|
||||
}
|
||||
paragraph
|
||||
.asyncPutParagraph(id, documentId, row.id, obj, changeStateloading)
|
||||
.then((res) => {
|
||||
emit('changeState', row.id)
|
||||
return true
|
||||
})
|
||||
.catch(() => {
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
const GenerateRelatedDialogRef = ref<InstanceType<typeof GenerateRelatedDialog>>()
|
||||
function openGenerateDialog(row: any) {
|
||||
if (GenerateRelatedDialogRef.value) {
|
||||
GenerateRelatedDialogRef.value.open([], 'paragraph', row.id)
|
||||
}
|
||||
}
|
||||
function openSelectDocumentDialog(row?: any) {
|
||||
// if (row) {
|
||||
// multipleSelection.value = [row.id]
|
||||
// }
|
||||
// SelectDocumentDialogRef.value.open(multipleSelection.value)
|
||||
}
|
||||
|
||||
function deleteParagraph(row: any) {
|
||||
MsgConfirm(
|
||||
`${t('views.paragraph.delete.confirmTitle')} ${row.title || '-'} ?`,
|
||||
t('views.paragraph.delete.confirmMessage'),
|
||||
{
|
||||
confirmButtonText: t('common.confirm'),
|
||||
confirmButtonClass: 'danger',
|
||||
},
|
||||
)
|
||||
.then(() => {
|
||||
paragraph.asyncDelParagraph(id, documentId, row.id, loading).then(() => {
|
||||
emit('deleteParagraph', row.id)
|
||||
MsgSuccess(t('common.deleteSuccess'))
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
const SelectDocumentDialogRef = ref()
|
||||
const ParagraphDialogRef = ref()
|
||||
const title = ref('')
|
||||
function editParagraph(row: any) {
|
||||
title.value = t('views.paragraph.paragraphDetail')
|
||||
ParagraphDialogRef.value.open(row)
|
||||
}
|
||||
|
||||
function refresh() {}
|
||||
|
||||
function refreshMigrateParagraph() {}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.paragraph-box {
|
||||
background: var(--app-layout-bg-color);
|
||||
border: 1px solid #ffffff;
|
||||
box-shadow: none !important;
|
||||
position: relative;
|
||||
overflow: inherit;
|
||||
&:hover {
|
||||
background: rgba(31, 35, 41, 0.1);
|
||||
border: 1px solid #dee0e3;
|
||||
}
|
||||
.paragraph-box-operation {
|
||||
position: absolute;
|
||||
overflow: inherit;
|
||||
right: 0;
|
||||
top: 0;
|
||||
border: 1px solid #dee0e3;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,152 +0,0 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="title"
|
||||
v-model="dialogVisible"
|
||||
width="80%"
|
||||
class="paragraph-dialog"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<el-row v-loading="loading">
|
||||
<el-col :span="18">
|
||||
<el-scrollbar height="500" wrap-class="paragraph-scrollbar">
|
||||
<div class="p-24" style="padding-bottom: 8px">
|
||||
<div style="position: absolute; right: 20px; top: 20px; ">
|
||||
<el-button text @click="isEdit = true" v-if="problemId && !isEdit">
|
||||
<el-icon><EditPen /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<ParagraphForm ref="paragraphFormRef" :data="detail" :isEdit="isEdit" />
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<div class="text-right p-24 pt-0" v-if="problemId && isEdit">
|
||||
<el-button @click.prevent="cancelEdit"> {{$t('common.cancel')}} </el-button>
|
||||
<el-button type="primary" :disabled="loading" @click="handleDebounceClick">
|
||||
{{$t('common.save')}}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6" class="border-l" style="width: 300px">
|
||||
<!-- 关联问题 -->
|
||||
<ProblemComponent
|
||||
:problemId="problemId"
|
||||
:docId="document_id"
|
||||
:knowledgeId="dataset_id"
|
||||
ref="ProblemRef"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer v-if="!problemId">
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> {{$t('common.cancel')}} </el-button>
|
||||
<el-button :disabled="loading" type="primary" @click="handleDebounceClick">
|
||||
{{$t('common.submit')}}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, nextTick } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { cloneDeep, debounce } from 'lodash'
|
||||
import ParagraphForm from '@/views/paragraph/component/ParagraphForm.vue'
|
||||
import ProblemComponent from '@/views/paragraph/component/ProblemComponent.vue'
|
||||
import paragraphApi from '@/api/system-shared/paragraph'
|
||||
import useStore from '@/stores'
|
||||
|
||||
const props = defineProps({
|
||||
title: String
|
||||
})
|
||||
|
||||
const { paragraph } = useStore()
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id, documentId }
|
||||
} = route as any
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const ProblemRef = ref()
|
||||
const paragraphFormRef = ref<any>()
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
||||
const loading = ref(false)
|
||||
const problemId = ref('')
|
||||
const detail = ref<any>({})
|
||||
const isEdit = ref(false)
|
||||
const document_id = ref('')
|
||||
const dataset_id = ref('')
|
||||
const cloneData = ref(null)
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
problemId.value = ''
|
||||
detail.value = {}
|
||||
isEdit.value = false
|
||||
document_id.value = ''
|
||||
dataset_id.value = ''
|
||||
cloneData.value = null
|
||||
}
|
||||
})
|
||||
|
||||
const cancelEdit = () => {
|
||||
isEdit.value = false
|
||||
detail.value = cloneDeep(cloneData.value)
|
||||
}
|
||||
|
||||
const open = (data: any) => {
|
||||
if (data) {
|
||||
detail.value.title = data.title
|
||||
detail.value.content = data.content
|
||||
cloneData.value = cloneDeep(detail.value)
|
||||
problemId.value = data.id
|
||||
document_id.value = data.document_id
|
||||
dataset_id.value = data.dataset_id || id
|
||||
} else {
|
||||
isEdit.value = true
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
const submitHandle = async () => {
|
||||
if (await paragraphFormRef.value?.validate()) {
|
||||
loading.value = true
|
||||
if (problemId.value) {
|
||||
paragraph
|
||||
.asyncPutParagraph(
|
||||
dataset_id.value,
|
||||
documentId || document_id.value,
|
||||
problemId.value,
|
||||
paragraphFormRef.value?.form,
|
||||
loading
|
||||
)
|
||||
.then((res: any) => {
|
||||
isEdit.value = false
|
||||
emit('refresh', res.data)
|
||||
})
|
||||
} else {
|
||||
const obj =
|
||||
ProblemRef.value.problemList.length > 0
|
||||
? {
|
||||
problem_list: ProblemRef.value.problemList,
|
||||
...paragraphFormRef.value?.form
|
||||
}
|
||||
: paragraphFormRef.value?.form
|
||||
paragraphApi.postParagraph(id, documentId, obj, loading).then((res) => {
|
||||
dialogVisible.value = false
|
||||
emit('refresh')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
const handleDebounceClick = debounce(() => {
|
||||
submitHandle()
|
||||
}, 200)
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
<template>
|
||||
<el-form
|
||||
ref="paragraphFormRef"
|
||||
:model="form"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
:rules="rules"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item :label="$t('views.paragraph.form.paragraphTitle.label')">
|
||||
<el-input
|
||||
v-if="isEdit"
|
||||
v-model="form.title"
|
||||
:placeholder="$t('views.paragraph.form.paragraphTitle.placeholder')"
|
||||
maxlength="256"
|
||||
show-word-limit
|
||||
>
|
||||
</el-input>
|
||||
<span class="lighter" v-else>{{ form.title || '-' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('views.paragraph.form.content.label')" prop="content">
|
||||
<MdEditor
|
||||
v-if="isEdit"
|
||||
v-model="form.content"
|
||||
:placeholder="$t('views.paragraph.form.content.placeholder')"
|
||||
:maxLength="100000"
|
||||
:preview="false"
|
||||
:toolbars="toolbars"
|
||||
style="height: 300px"
|
||||
@onUploadImg="onUploadImg"
|
||||
:footers="footers"
|
||||
>
|
||||
<template #defFooters>
|
||||
<span style="margin-left: -6px">/ 100000</span>
|
||||
</template>
|
||||
</MdEditor>
|
||||
<MdPreview
|
||||
v-else
|
||||
ref="editorRef"
|
||||
editorId="preview-only"
|
||||
:modelValue="form.content"
|
||||
class="maxkb-md"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onUnmounted, watch } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import imageApi from '@/api/image'
|
||||
import { t } from '@/locales'
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
isEdit: Boolean
|
||||
})
|
||||
|
||||
const toolbars = [
|
||||
'bold',
|
||||
'underline',
|
||||
'italic',
|
||||
'-',
|
||||
'title',
|
||||
'strikeThrough',
|
||||
'sub',
|
||||
'sup',
|
||||
'quote',
|
||||
'unorderedList',
|
||||
'orderedList',
|
||||
'task',
|
||||
'-',
|
||||
'codeRow',
|
||||
'code',
|
||||
'link',
|
||||
'image',
|
||||
'table',
|
||||
'mermaid',
|
||||
'katex',
|
||||
'-',
|
||||
'revoke',
|
||||
'next',
|
||||
'=',
|
||||
'pageFullscreen',
|
||||
'preview',
|
||||
'htmlPreview'
|
||||
] as any[]
|
||||
|
||||
const footers = ['markdownTotal', 0, '=', 1, 'scrollSwitch']
|
||||
|
||||
const editorRef = ref()
|
||||
|
||||
const form = ref<any>({
|
||||
title: '',
|
||||
content: ''
|
||||
})
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
content: [
|
||||
{ required: true, message: t('views.paragraph.form.content.requiredMessage1'), trigger: 'blur' },
|
||||
{ max: 100000, message: t('views.paragraph.form.content.requiredMessage2'), trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
const paragraphFormRef = ref<FormInstance>()
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(value) => {
|
||||
if (value && JSON.stringify(value) !== '{}') {
|
||||
form.value.title = value.title
|
||||
form.value.content = value.content
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => props.isEdit,
|
||||
(value) => {
|
||||
if (!value) {
|
||||
paragraphFormRef.value?.clearValidate()
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
/*
|
||||
表单校验
|
||||
*/
|
||||
function validate() {
|
||||
if (!paragraphFormRef.value) return
|
||||
return paragraphFormRef.value.validate((valid: any) => {
|
||||
return valid
|
||||
})
|
||||
}
|
||||
|
||||
const onUploadImg = async (files: any, callback: any) => {
|
||||
const res = await Promise.all(
|
||||
files.map((file: any) => {
|
||||
return new Promise((rev, rej) => {
|
||||
const fd = new FormData()
|
||||
fd.append('file', file)
|
||||
|
||||
imageApi
|
||||
.postImage(fd)
|
||||
.then((res: any) => {
|
||||
rev(res)
|
||||
})
|
||||
.catch((error) => rej(error))
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
callback(res.map((item) => item.data))
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
form.value = {
|
||||
title: '',
|
||||
content: ''
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
validate,
|
||||
form
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
@ -1,200 +0,0 @@
|
|||
<template>
|
||||
<p class="bold title p-24" style="padding-bottom: 0">
|
||||
<span class="flex align-center">
|
||||
<span>{{ $t('views.paragraph.relatedProblem.title') }}</span>
|
||||
<el-divider direction="vertical" class="mr-4" />
|
||||
<el-button text @click="addProblem">
|
||||
<el-icon><Plus /></el-icon>
|
||||
</el-button>
|
||||
</span>
|
||||
</p>
|
||||
<div v-loading="loading">
|
||||
<el-scrollbar height="500px">
|
||||
<div class="p-24" style="padding-top: 16px">
|
||||
<el-select
|
||||
v-if="isAddProblem"
|
||||
v-model="problemValue"
|
||||
filterable
|
||||
allow-create
|
||||
default-first-option
|
||||
:reserve-keyword="false"
|
||||
:placeholder="$t('views.paragraph.relatedProblem.placeholder')"
|
||||
remote
|
||||
:remote-method="remoteMethod"
|
||||
:loading="optionLoading"
|
||||
@change="addProblemHandle"
|
||||
@blur="isAddProblem = false"
|
||||
class="mb-16"
|
||||
popper-class="select-popper"
|
||||
:popper-append-to-body="false"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in problemOptions"
|
||||
:key="item.id"
|
||||
:label="item.content"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.content }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
<template v-for="(item, index) in problemList" :key="index">
|
||||
<TagEllipsis
|
||||
@close="delProblemHandle(item, index)"
|
||||
class="question-tag"
|
||||
type="info"
|
||||
effect="plain"
|
||||
closable
|
||||
>
|
||||
<auto-tooltip :content="item.content">
|
||||
{{ item.content }}
|
||||
</auto-tooltip>
|
||||
</TagEllipsis>
|
||||
</template>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, nextTick, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import paragraphApi from '@/api/system-shared/paragraph'
|
||||
import useStore from '@/stores'
|
||||
|
||||
const props = defineProps({
|
||||
problemId: String,
|
||||
docId: String,
|
||||
knowledgeId: String,
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id, documentId }, // id为knowledgeId
|
||||
} = route as any
|
||||
|
||||
const { problem, paragraph } = useStore()
|
||||
const inputRef = ref()
|
||||
const loading = ref(false)
|
||||
const isAddProblem = ref(false)
|
||||
|
||||
const problemValue = ref('')
|
||||
const problemList = ref<any[]>([])
|
||||
|
||||
const problemOptions = ref<any[]>([])
|
||||
const optionLoading = ref(false)
|
||||
|
||||
watch(
|
||||
() => props.problemId,
|
||||
(value) => {
|
||||
if (value) {
|
||||
getProblemList()
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
)
|
||||
|
||||
function delProblemHandle(item: any, index: number) {
|
||||
if (item.id) {
|
||||
paragraph
|
||||
.asyncDisassociationProblem(
|
||||
props.knowledgeId || id,
|
||||
documentId || props.docId,
|
||||
props.problemId || '',
|
||||
item.id,
|
||||
loading,
|
||||
)
|
||||
.then((res: any) => {
|
||||
getProblemList()
|
||||
})
|
||||
} else {
|
||||
problemList.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
function getProblemList() {
|
||||
loading.value = true
|
||||
paragraphApi
|
||||
.getParagraphProblem(props.knowledgeId || id, documentId || props.docId, props.problemId || '')
|
||||
.then((res) => {
|
||||
problemList.value = res.data
|
||||
loading.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function addProblem() {
|
||||
isAddProblem.value = true
|
||||
nextTick(() => {
|
||||
inputRef.value?.focus()
|
||||
})
|
||||
}
|
||||
function addProblemHandle(val: string) {
|
||||
if (props.problemId) {
|
||||
const api = problemOptions.value.some((option) => option.id === val)
|
||||
? paragraph.asyncAssociationProblem(
|
||||
props.knowledgeId || id,
|
||||
documentId || props.docId,
|
||||
props.problemId,
|
||||
val,
|
||||
loading,
|
||||
)
|
||||
: paragraphApi.postParagraphProblem(
|
||||
props.knowledgeId || id,
|
||||
documentId || props.docId,
|
||||
props.problemId,
|
||||
{
|
||||
content: val,
|
||||
},
|
||||
loading,
|
||||
)
|
||||
api.then(() => {
|
||||
getProblemList()
|
||||
problemValue.value = ''
|
||||
isAddProblem.value = false
|
||||
})
|
||||
} else {
|
||||
const problem = problemOptions.value.find((option) => option.id === val)
|
||||
const content = problem ? problem.content : val
|
||||
if (!problemList.value.some((item) => item.content === content)) {
|
||||
problemList.value.push({ content: content })
|
||||
}
|
||||
|
||||
problemValue.value = ''
|
||||
isAddProblem.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const remoteMethod = (query: string) => {
|
||||
getProblemOption(query)
|
||||
}
|
||||
|
||||
function getProblemOption(filterText?: string) {
|
||||
return problem
|
||||
.asyncGetProblem(
|
||||
props.knowledgeId || (id as string),
|
||||
{ current_page: 1, page_size: 100 },
|
||||
filterText && { content: filterText },
|
||||
optionLoading,
|
||||
)
|
||||
.then((res: any) => {
|
||||
problemOptions.value = res.data.records
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getProblemOption()
|
||||
})
|
||||
onUnmounted(() => {
|
||||
problemList.value = []
|
||||
problemValue.value = ''
|
||||
isAddProblem.value = false
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
problemList,
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
@ -1,181 +0,0 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="`${$t('views.chatLog.selectKnowledge')}/${$t('common.fileUpload.document')}`"
|
||||
v-model="dialogVisible"
|
||||
width="500"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
:rules="rules"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item :label="$t('views.chatLog.selectKnowledge')" prop="knowledge_id">
|
||||
<el-select
|
||||
v-model="form.knowledge_id"
|
||||
filterable
|
||||
:placeholder="$t('views.chatLog.selectKnowledgePlaceholder')"
|
||||
:loading="optionLoading"
|
||||
@change="changeknowledge"
|
||||
>
|
||||
<el-option v-for="item in knowledgeList" :key="item.id" :label="item.name" :value="item.id">
|
||||
<span class="flex align-center">
|
||||
<el-avatar
|
||||
v-if="!item.knowledge_id && item.type === '1'"
|
||||
class="mr-12 avatar-purple"
|
||||
shape="square"
|
||||
:size="24"
|
||||
>
|
||||
<img src="@/assets/knowledge/icon_web.svg" style="width: 58%" alt="" />
|
||||
</el-avatar>
|
||||
<el-avatar
|
||||
v-else-if="!item.knowledge_id && item.type === '2'"
|
||||
class="mr-12 avatar-purple"
|
||||
shape="square"
|
||||
:size="24"
|
||||
style="background: none"
|
||||
>
|
||||
<img src="@/assets/knowledge/logo_lark.svg" style="width: 100%" alt="" />
|
||||
</el-avatar>
|
||||
<el-avatar
|
||||
v-else-if="!item.knowledge_id && item.type === '0'"
|
||||
class="mr-12 avatar-blue"
|
||||
shape="square"
|
||||
:size="24"
|
||||
>
|
||||
<img src="@/assets/knowledge/icon_document.svg" style="width: 58%" alt="" />
|
||||
</el-avatar>
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('views.chatLog.saveToDocument')" prop="document_id">
|
||||
<el-select
|
||||
v-model="form.document_id"
|
||||
filterable
|
||||
:placeholder="$t('views.chatLog.documentPlaceholder')"
|
||||
:loading="optionLoading"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in documentList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
>
|
||||
{{ item.name }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
|
||||
<el-button type="primary" @click="submitForm(formRef)" :loading="loading">
|
||||
{{ $t('views.document.setting.migration') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, reactive } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import paragraphApi from '@/api/system-shared/paragraph'
|
||||
import useStore from '@/stores'
|
||||
import { t } from '@/locales'
|
||||
const { knowledge, document } = useStore()
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id, documentId }
|
||||
} = route as any
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
const formRef = ref()
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const loading = ref(false)
|
||||
|
||||
const form = ref<any>({
|
||||
knowledge_id: '',
|
||||
document_id: ''
|
||||
})
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
knowledge_id: [
|
||||
{ required: true, message: t('views.chatLog.selectknowledgePlaceholder'), trigger: 'change' }
|
||||
],
|
||||
document_id: [{ required: true, message: t('views.chatLog.documentPlaceholder'), trigger: 'change' }]
|
||||
})
|
||||
|
||||
const knowledgeList = ref<any[]>([])
|
||||
const documentList = ref<any[]>([])
|
||||
const optionLoading = ref(false)
|
||||
const paragraphList = ref<string[]>([])
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
form.value = {
|
||||
knowledge_id: '',
|
||||
document_id: ''
|
||||
}
|
||||
knowledgeList.value = []
|
||||
documentList.value = []
|
||||
paragraphList.value = []
|
||||
formRef.value?.clearValidate()
|
||||
}
|
||||
})
|
||||
|
||||
function changeknowledge(id: string) {
|
||||
form.value.document_id = ''
|
||||
getDocument(id)
|
||||
}
|
||||
|
||||
function getDocument(id: string) {
|
||||
document.asyncGetAllDocument(id, loading).then((res: any) => {
|
||||
documentList.value = res.data?.filter((v: any) => v.id !== documentId)
|
||||
})
|
||||
}
|
||||
|
||||
function getknowledge() {
|
||||
knowledge.asyncGetFolderKnowledge(loading).then((res: any) => {
|
||||
knowledgeList.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
const open = (list: any) => {
|
||||
paragraphList.value = list
|
||||
getknowledge()
|
||||
formRef.value?.clearValidate()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
const submitForm = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
paragraphApi
|
||||
.putMigrateMulParagraph(
|
||||
id,
|
||||
documentId,
|
||||
form.value.knowledge_id,
|
||||
form.value.document_id,
|
||||
paragraphList.value,
|
||||
loading
|
||||
)
|
||||
.then(() => {
|
||||
emit('refresh')
|
||||
dialogVisible.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,364 +0,0 @@
|
|||
<template>
|
||||
<div class="paragraph p-12-24">
|
||||
<div class="flex align-center" style="width: 78%">
|
||||
<back-button to="-1" style="margin-left: -4px"></back-button>
|
||||
<h3 style="display: inline-block">{{ documentDetail?.name }}</h3>
|
||||
<el-text type="info" v-if="documentDetail?.type === '1'"
|
||||
>({{ $t('views.document.form.source_url.label') }}:<el-link
|
||||
:href="documentDetail?.meta?.source_url"
|
||||
target="_blank"
|
||||
>
|
||||
<span class="break-all">{{ documentDetail?.meta?.source_url }} </span></el-link
|
||||
>)
|
||||
</el-text>
|
||||
</div>
|
||||
<div class="header-button">
|
||||
<el-button @click="batchSelectedHandle(true)" v-if="isBatch === false">
|
||||
{{ $t('views.paragraph.setting.batchSelected') }}
|
||||
</el-button>
|
||||
<el-button @click="batchSelectedHandle(false)" v-if="isBatch === true">
|
||||
{{ $t('views.paragraph.setting.cancelSelected') }}
|
||||
</el-button>
|
||||
<el-button @click="addParagraph" type="primary" :disabled="loading" v-if="isBatch === false">
|
||||
{{ $t('views.paragraph.addParagraph') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-card
|
||||
style="--el-card-padding: 0"
|
||||
class="paragraph__main mt-16"
|
||||
v-loading="(paginationConfig.current_page === 1 && loading) || changeStateloading"
|
||||
>
|
||||
<div class="flex-between p-12-16 border-b">
|
||||
<span>{{ paginationConfig.total }} {{ $t('views.paragraph.paragraph_count') }}</span>
|
||||
<el-input
|
||||
v-model="search"
|
||||
:placeholder="$t('common.search')"
|
||||
class="input-with-select"
|
||||
style="width: 260px"
|
||||
@change="searchHandle"
|
||||
clearable
|
||||
>
|
||||
<template #prepend>
|
||||
<el-select v-model="searchType" placeholder="Select" style="width: 80px">
|
||||
<el-option :label="$t('common.title')" value="title" />
|
||||
<el-option :label="$t('common.content')" value="content" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="paragraph-sidebar p-16 border-r">
|
||||
<el-anchor
|
||||
direction="vertical"
|
||||
type="default"
|
||||
:offset="130"
|
||||
container=".paragraph-scollbar"
|
||||
@click="handleClick"
|
||||
>
|
||||
<template v-for="(item, index) in paragraphDetail" :key="item.id">
|
||||
<el-anchor-link :href="`#${item.id}`" :title="item.title" v-if="item.title" />
|
||||
</template>
|
||||
</el-anchor>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<el-empty v-if="paragraphDetail.length == 0" :description="$t('common.noData')" />
|
||||
<div v-else>
|
||||
<el-scrollbar class="paragraph-scollbar">
|
||||
<div class="paragraph-detail">
|
||||
<InfiniteScroll
|
||||
:size="paragraphDetail.length"
|
||||
:total="paginationConfig.total"
|
||||
:page_size="paginationConfig.page_size"
|
||||
v-model:current_page="paginationConfig.current_page"
|
||||
@load="getParagraphList"
|
||||
:loading="loading"
|
||||
>
|
||||
<VueDraggable
|
||||
ref="el"
|
||||
v-bind:modelValue="paragraphDetail"
|
||||
:disabled="isBatch === true"
|
||||
handle=".handle"
|
||||
:animation="150"
|
||||
ghostClass="ghost"
|
||||
@end="onEnd"
|
||||
>
|
||||
<el-checkbox-group v-model="multipleSelection">
|
||||
<template v-for="(item, index) in paragraphDetail" :key="item.id">
|
||||
<!-- 批量操作 -->
|
||||
<div class="paragraph-card flex" :id="item.id" v-if="isBatch === true">
|
||||
<el-checkbox :value="item.id" />
|
||||
<ParagraphCard :data="item" class="mb-8 w-full" />
|
||||
</div>
|
||||
<!-- 非批量操作 -->
|
||||
<div class="handle paragraph-card flex" :id="item.id" v-else>
|
||||
<img
|
||||
src="@/assets/sort.svg"
|
||||
alt=""
|
||||
height="15"
|
||||
class="handle-img mr-8 mt-24 cursor"
|
||||
/>
|
||||
<ParagraphCard
|
||||
:data="item"
|
||||
class="mb-8 w-full"
|
||||
@changeState="changeState"
|
||||
@deleteParagraph="deleteParagraph"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</el-checkbox-group>
|
||||
</VueDraggable>
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mul-operation border-t w-full" v-if="isBatch === true">
|
||||
<el-button :disabled="multipleSelection.length === 0" @click="openGenerateDialog()">
|
||||
{{ $t('views.document.generateQuestion.title') }}
|
||||
</el-button>
|
||||
<el-button :disabled="multipleSelection.length === 0" @click="openSelectDocumentDialog()">
|
||||
{{ $t('views.document.setting.migration') }}
|
||||
</el-button>
|
||||
|
||||
<el-button :disabled="multipleSelection.length === 0" @click="deleteMulParagraph">
|
||||
{{ $t('common.delete') }}
|
||||
</el-button>
|
||||
<span class="ml-8">
|
||||
{{ $t('views.document.selected') }} {{ multipleSelection.length }}
|
||||
{{ $t('views.document.items') }}
|
||||
</span>
|
||||
</div>
|
||||
</el-card>
|
||||
<ParagraphDialog ref="ParagraphDialogRef" :title="title" @refresh="refresh" />
|
||||
<SelectDocumentDialog ref="SelectDocumentDialogRef" @refresh="refreshMigrateParagraph" />
|
||||
<GenerateRelatedDialog ref="GenerateRelatedDialogRef" @refresh="refresh" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onMounted, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import documentApi from '@/api/system-shared/document'
|
||||
import paragraphApi from '@/api/system-shared/paragraph'
|
||||
import ParagraphDialog from './component/ParagraphDialog.vue'
|
||||
import ParagraphCard from './component/ParagraphCard.vue'
|
||||
import SelectDocumentDialog from './component/SelectDocumentDialog.vue'
|
||||
import GenerateRelatedDialog from '@/components/generate-related-shared-dialog/index.vue'
|
||||
import { VueDraggable } from 'vue-draggable-plus'
|
||||
import { MsgSuccess, MsgConfirm } from '@/utils/message'
|
||||
import useStore from '@/stores'
|
||||
import { t } from '@/locales'
|
||||
const { paragraph } = useStore()
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id, documentId },
|
||||
} = route as any
|
||||
|
||||
const SelectDocumentDialogRef = ref()
|
||||
const ParagraphDialogRef = ref()
|
||||
const loading = ref(false)
|
||||
const changeStateloading = ref(false)
|
||||
const documentDetail = ref<any>({})
|
||||
const paragraphDetail = ref<any[]>([])
|
||||
const title = ref('')
|
||||
const search = ref('')
|
||||
const searchType = ref('title')
|
||||
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
// 批量操作
|
||||
const isBatch = ref(false)
|
||||
const multipleSelection = ref<any[]>([])
|
||||
|
||||
const paginationConfig = reactive({
|
||||
current_page: 1,
|
||||
page_size: 30,
|
||||
total: 0,
|
||||
})
|
||||
|
||||
function deleteParagraph(id: string) {
|
||||
const index = paragraphDetail.value.findIndex((v) => v.id === id)
|
||||
paragraphDetail.value.splice(index, 1)
|
||||
}
|
||||
|
||||
function changeState(id: string) {
|
||||
const index = paragraphDetail.value.findIndex((v) => v.id === id)
|
||||
paragraphDetail.value[index].is_active = !paragraphDetail.value[index].is_active
|
||||
}
|
||||
|
||||
function refreshMigrateParagraph() {
|
||||
paragraphDetail.value = paragraphDetail.value.filter(
|
||||
(v) => !multipleSelection.value.includes(v.id),
|
||||
)
|
||||
multipleSelection.value = []
|
||||
MsgSuccess(t('views.document.tip.migrationSuccess'))
|
||||
}
|
||||
|
||||
function openSelectDocumentDialog(row?: any) {
|
||||
if (row) {
|
||||
multipleSelection.value = [row.id]
|
||||
}
|
||||
SelectDocumentDialogRef.value.open(multipleSelection.value)
|
||||
}
|
||||
function deleteMulParagraph() {
|
||||
MsgConfirm(
|
||||
`${t('views.document.delete.confirmTitle1')} ${multipleSelection.value.length} ${t('views.document.delete.confirmTitle2')}`,
|
||||
t('views.paragraph.delete.confirmMessage'),
|
||||
{
|
||||
confirmButtonText: t('common.confirm'),
|
||||
confirmButtonClass: 'danger',
|
||||
},
|
||||
)
|
||||
.then(() => {
|
||||
paragraphApi
|
||||
.putMulParagraph(id, documentId, multipleSelection.value, changeStateloading)
|
||||
.then(() => {
|
||||
paragraphDetail.value = paragraphDetail.value.filter(
|
||||
(v) => !multipleSelection.value.includes(v.id),
|
||||
)
|
||||
multipleSelection.value = []
|
||||
MsgSuccess(t('views.document.delete.successMessage'))
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
function batchSelectedHandle(bool: boolean) {
|
||||
isBatch.value = bool
|
||||
multipleSelection.value = []
|
||||
}
|
||||
|
||||
function selectHandle(id: string) {
|
||||
if (multipleSelection.value.includes(id)) {
|
||||
multipleSelection.value.splice(multipleSelection.value.indexOf(id), 1)
|
||||
} else {
|
||||
multipleSelection.value.push(id)
|
||||
}
|
||||
}
|
||||
|
||||
function searchHandle() {
|
||||
paginationConfig.current_page = 1
|
||||
paragraphDetail.value = []
|
||||
getParagraphList()
|
||||
}
|
||||
|
||||
function addParagraph() {
|
||||
title.value = t('views.paragraph.addParagraph')
|
||||
ParagraphDialogRef.value.open()
|
||||
}
|
||||
|
||||
function getDetail() {
|
||||
documentApi.getDocumentDetail(id, documentId, loading).then((res) => {
|
||||
documentDetail.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
function getParagraphList() {
|
||||
paragraphApi
|
||||
.getParagraphPage(
|
||||
id,
|
||||
documentId,
|
||||
paginationConfig,
|
||||
search.value && { [searchType.value]: search.value },
|
||||
loading,
|
||||
)
|
||||
.then((res) => {
|
||||
paragraphDetail.value = [...paragraphDetail.value, ...res.data.records]
|
||||
paginationConfig.total = res.data.total
|
||||
})
|
||||
}
|
||||
|
||||
function refresh(data: any) {
|
||||
if (data) {
|
||||
const index = paragraphDetail.value.findIndex((v) => v.id === data.id)
|
||||
paragraphDetail.value.splice(index, 1, data)
|
||||
} else {
|
||||
paginationConfig.current_page = 1
|
||||
paragraphDetail.value = []
|
||||
getParagraphList()
|
||||
}
|
||||
}
|
||||
|
||||
const GenerateRelatedDialogRef = ref()
|
||||
function openGenerateDialog(row?: any) {
|
||||
const arr: string[] = []
|
||||
if (row) {
|
||||
arr.push(row.id)
|
||||
} else {
|
||||
multipleSelection.value.map((v) => {
|
||||
if (v) {
|
||||
arr.push(v)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
GenerateRelatedDialogRef.value.open(arr, 'paragraph')
|
||||
}
|
||||
|
||||
function onEnd(event?: any) {
|
||||
const { oldIndex, newIndex } = event
|
||||
if (oldIndex === undefined || newIndex === undefined) return
|
||||
const list = cloneDeep(paragraphDetail.value)
|
||||
if (oldIndex === list.length - 1 || newIndex === list.length - 1) {
|
||||
return
|
||||
}
|
||||
const newInstance = { ...list[oldIndex], type: list[newIndex].type, id: list[newIndex].id }
|
||||
const oldInstance = { ...list[newIndex], type: list[oldIndex].type, id: list[oldIndex].id }
|
||||
list[newIndex] = newInstance
|
||||
list[oldIndex] = oldInstance
|
||||
paragraphDetail.value = list
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDetail()
|
||||
getParagraphList()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.paragraph {
|
||||
position: relative;
|
||||
.header-button {
|
||||
position: absolute;
|
||||
right: calc(var(--app-base-px) * 3);
|
||||
top: calc(var(--app-base-px) + 4px);
|
||||
}
|
||||
.paragraph-sidebar {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.paragraph-detail {
|
||||
height: calc(100vh - 215px);
|
||||
max-width: 1000px;
|
||||
margin: 16px auto;
|
||||
}
|
||||
|
||||
&__main {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
.mul-operation {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 16px 24px;
|
||||
box-sizing: border-box;
|
||||
background: #ffffff;
|
||||
}
|
||||
}
|
||||
.paragraph-card {
|
||||
&.handle {
|
||||
.handle-img {
|
||||
visibility: hidden;
|
||||
}
|
||||
&:hover {
|
||||
.handle-img {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="$t('views.problem.createProblem')"
|
||||
v-model="dialogVisible"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
:destroy-on-close="true"
|
||||
>
|
||||
<el-form
|
||||
label-position="top"
|
||||
ref="problemFormRef"
|
||||
:rules="rules"
|
||||
:model="form"
|
||||
require-asterisk-position="right"
|
||||
>
|
||||
<el-form-item :label="$t('views.problem.title')" prop="data">
|
||||
<el-input
|
||||
v-model="form.data"
|
||||
:placeholder="$t('views.problem.tip.placeholder')"
|
||||
:rows="10"
|
||||
type="textarea"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
|
||||
<el-button type="primary" @click="submit(problemFormRef)" :loading="loading">
|
||||
{{ $t('common.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
import useStore from '@/stores/modules-shared-system'
|
||||
import { t } from '@/locales'
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id }
|
||||
} = route as any
|
||||
const { problem } = useStore()
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
const problemFormRef = ref()
|
||||
const loading = ref<boolean>(false)
|
||||
|
||||
const form = ref<any>({
|
||||
data: ''
|
||||
})
|
||||
|
||||
const rules = reactive({
|
||||
data: [{ required: true, message: t('views.problem.tip.requiredMessage'), trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
form.value = {
|
||||
data: ''
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const open = () => {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const submit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
const arr = form.value.data.split('\n').filter(function (item: string) {
|
||||
return item !== ''
|
||||
})
|
||||
problem.asyncPostProblem(id, arr, loading).then((res: any) => {
|
||||
MsgSuccess(t('common.createSuccess'))
|
||||
emit('refresh')
|
||||
dialogVisible.value = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -1,206 +0,0 @@
|
|||
<template>
|
||||
<el-drawer v-model="visible" size="60%" @close="closeHandle">
|
||||
<template #header>
|
||||
<h4>{{ $t('views.problem.detailProblem') }}</h4>
|
||||
</template>
|
||||
<div>
|
||||
<el-scrollbar>
|
||||
<div class="p-8">
|
||||
<el-form label-position="top" v-loading="loading" @submit.prevent>
|
||||
<el-form-item :label="$t('views.problem.title')">
|
||||
<ReadWrite
|
||||
@change="editName"
|
||||
:data="currentContent"
|
||||
:showEditIcon="true"
|
||||
:maxlength="256"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('views.problem.relateParagraph.title')">
|
||||
<template v-for="(item, index) in paragraphList" :key="index">
|
||||
<CardBox
|
||||
:title="item.title || '-'"
|
||||
class="paragraph-source-card cursor mb-8"
|
||||
:showIcon="false"
|
||||
@click.stop="editParagraph(item)"
|
||||
>
|
||||
<div class="active-button">
|
||||
<span class="mr-4">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('views.problem.setting.cancelRelated')"
|
||||
placement="top"
|
||||
>
|
||||
<el-button type="primary" text @click.stop="disassociation(item)">
|
||||
<AppIcon iconName="app-quxiaoguanlian"></AppIcon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
<template #description>
|
||||
<el-scrollbar height="80">
|
||||
{{ item.content }}
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="footer-content flex-between">
|
||||
<el-text>
|
||||
<el-icon>
|
||||
<Document />
|
||||
</el-icon>
|
||||
{{ item?.document_name }}
|
||||
</el-text>
|
||||
</div>
|
||||
</template>
|
||||
</CardBox>
|
||||
</template>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<ParagraphDialog
|
||||
ref="ParagraphDialogRef"
|
||||
:title="$t('views.paragraph.editParagraph')"
|
||||
@refresh="refresh"
|
||||
/>
|
||||
<RelateProblemDialog ref="RelateProblemDialogRef" @refresh="refresh" />
|
||||
</div>
|
||||
<template #footer>
|
||||
<div>
|
||||
<el-button @click="relateProblem">{{
|
||||
$t('views.problem.relateParagraph.title')
|
||||
}}</el-button>
|
||||
<el-button @click="pre" :disabled="pre_disable || loading">{{
|
||||
$t('views.log.buttons.prev')
|
||||
}}</el-button>
|
||||
<el-button @click="next" :disabled="next_disable || loading">{{
|
||||
$t('views.log.buttons.next')
|
||||
}}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import problemApi from '@/api/system-shared/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/modules-shared-system'
|
||||
import { t } from '@/locales'
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/**
|
||||
* 当前的id
|
||||
*/
|
||||
currentId: string
|
||||
currentContent: string
|
||||
/**
|
||||
* 下一条
|
||||
*/
|
||||
next: () => void
|
||||
/**
|
||||
* 上一条
|
||||
*/
|
||||
pre: () => void
|
||||
|
||||
pre_disable: boolean
|
||||
|
||||
next_disable: boolean
|
||||
}>(),
|
||||
{}
|
||||
)
|
||||
|
||||
const emit = defineEmits(['update:currentId', 'update:currentContent', 'refresh'])
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id }
|
||||
} = route
|
||||
|
||||
const { paragraph } = useStore()
|
||||
const RelateProblemDialogRef = ref()
|
||||
const ParagraphDialogRef = ref()
|
||||
const loading = ref(false)
|
||||
const visible = ref(false)
|
||||
const paragraphList = ref<any[]>([])
|
||||
|
||||
function disassociation(item: any) {
|
||||
paragraph
|
||||
.asyncDisassociationProblem(
|
||||
item.knowledge_id,
|
||||
item.document_id,
|
||||
item.id,
|
||||
props.currentId,
|
||||
loading
|
||||
)
|
||||
.then(() => {
|
||||
getRecord()
|
||||
})
|
||||
}
|
||||
|
||||
function relateProblem() {
|
||||
RelateProblemDialogRef.value.open([props.currentId])
|
||||
}
|
||||
|
||||
function editParagraph(row: any) {
|
||||
ParagraphDialogRef.value.open(row)
|
||||
}
|
||||
|
||||
function editName(val: string) {
|
||||
if (val) {
|
||||
const obj = {
|
||||
content: val
|
||||
}
|
||||
problemApi.putProblems(id as string, props.currentId, obj, loading).then(() => {
|
||||
emit('update:currentContent', val)
|
||||
MsgSuccess(t('common.modifySuccess'))
|
||||
})
|
||||
} else {
|
||||
MsgError(t('views.problem.tip.errorMessage'))
|
||||
}
|
||||
}
|
||||
|
||||
function closeHandle() {
|
||||
paragraphList.value = []
|
||||
}
|
||||
|
||||
function getRecord() {
|
||||
if (props.currentId && visible.value) {
|
||||
problemApi.getDetailProblems(id as string, props.currentId, loading).then((res) => {
|
||||
paragraphList.value = res.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
getRecord()
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.currentId,
|
||||
() => {
|
||||
paragraphList.value = []
|
||||
getRecord()
|
||||
}
|
||||
)
|
||||
|
||||
watch(visible, (bool) => {
|
||||
if (!bool) {
|
||||
emit('update:currentId', '')
|
||||
emit('update:currentContent', '')
|
||||
emit('refresh')
|
||||
}
|
||||
})
|
||||
|
||||
const open = () => {
|
||||
getRecord()
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
|
|
@ -1,330 +0,0 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="$t('views.problem.relateParagraph.title')"
|
||||
v-model="dialogVisible"
|
||||
width="80%"
|
||||
class="paragraph-dialog"
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<el-row v-loading="loading">
|
||||
<el-col :span="6">
|
||||
<el-scrollbar height="500" wrap-class="paragraph-scrollbar">
|
||||
<div class="bold title align-center p-24 pb-0">
|
||||
{{ $t('views.problem.relateParagraph.selectDocument') }}
|
||||
</div>
|
||||
<div class="p-8" style="padding-bottom: 8px">
|
||||
<el-input
|
||||
v-model="filterDoc"
|
||||
:placeholder="$t('views.problem.relateParagraph.placeholder')"
|
||||
prefix-icon="Search"
|
||||
clearable
|
||||
/>
|
||||
<common-list
|
||||
:data="documentList"
|
||||
class="mt-8"
|
||||
@click="clickDocumentHandle"
|
||||
:default-active="currentDocument"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span class="flex lighter align-center">
|
||||
<auto-tooltip :content="row.name">
|
||||
{{ row.name }}
|
||||
</auto-tooltip>
|
||||
<el-badge
|
||||
:value="associationCount(row.id)"
|
||||
type="primary"
|
||||
v-if="associationCount(row.id)"
|
||||
class="paragraph-badge ml-4"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
</common-list>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-col>
|
||||
<el-col :span="18" class="border-l">
|
||||
<el-scrollbar height="500" wrap-class="paragraph-scrollbar">
|
||||
<div class="p-24" style="padding-bottom: 8px; padding-top: 16px">
|
||||
<div class="flex-between mb-16">
|
||||
<div class="bold title align-center">
|
||||
{{ $t('components.selectParagraph.title') }}
|
||||
<el-text>
|
||||
({{ $t('views.problem.relateParagraph.selectedParagraph') }}:{{
|
||||
associationCount(currentDocument)
|
||||
}}
|
||||
{{ $t('views.problem.relateParagraph.count') }})
|
||||
</el-text>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="search"
|
||||
:placeholder="$t('common.search')"
|
||||
class="input-with-select"
|
||||
style="width: 260px"
|
||||
@change="searchHandle"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-select v-model="searchType" placeholder="Select" style="width: 80px">
|
||||
<el-option :label="$t('common.title')" value="title" />
|
||||
<el-option :label="$t('common.content')" value="content" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<el-empty v-if="paragraphList.length == 0" :description="$t('common.noData')" />
|
||||
|
||||
<InfiniteScroll
|
||||
v-else
|
||||
:size="paragraphList.length"
|
||||
:total="paginationConfig.total"
|
||||
:page_size="paginationConfig.page_size"
|
||||
v-model:current_page="paginationConfig.current_page"
|
||||
@load="getParagraphList"
|
||||
:loading="loading"
|
||||
>
|
||||
<template v-for="(item, index) in paragraphList" :key="index">
|
||||
<CardBox
|
||||
shadow="hover"
|
||||
:title="item.title || '-'"
|
||||
:description="item.content"
|
||||
class="paragraph-card cursor mb-16"
|
||||
:class="isAssociation(item.id) ? 'selected' : ''"
|
||||
:showIcon="false"
|
||||
@click="associationClick(item)"
|
||||
>
|
||||
</CardBox>
|
||||
</template>
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<template #footer v-if="isMul">
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false"> {{ $t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="mulAssociation"> {{ $t('common.confirm') }} </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, reactive } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import problemApi from '@/api/system-shared/problem'
|
||||
import paragraphApi from '@/api/system-shared/paragraph'
|
||||
import useStore from '@/stores/modules-shared-system'
|
||||
import { MsgSuccess } from '@/utils/message'
|
||||
import { t } from '@/locales'
|
||||
const { problem, document, paragraph } = useStore()
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id }, // knowledgeId
|
||||
} = route as any
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
|
||||
const loading = ref(false)
|
||||
const documentList = ref<any[]>([])
|
||||
const cloneDocumentList = ref<any[]>([])
|
||||
const paragraphList = ref<any[]>([])
|
||||
const currentProblemId = ref<string>('')
|
||||
const currentMulProblemId = ref<string[]>([])
|
||||
|
||||
// 回显
|
||||
const associationParagraph = ref<any[]>([])
|
||||
|
||||
const currentDocument = ref<string>('')
|
||||
const search = ref('')
|
||||
const searchType = ref('title')
|
||||
const filterDoc = ref('')
|
||||
// 批量
|
||||
const isMul = ref(false)
|
||||
|
||||
const paginationConfig = reactive({
|
||||
current_page: 1,
|
||||
page_size: 50,
|
||||
total: 0,
|
||||
})
|
||||
|
||||
function mulAssociation() {
|
||||
const data = {
|
||||
problem_id_list: currentMulProblemId.value,
|
||||
paragraph_list: associationParagraph.value.map((item) => ({
|
||||
paragraph_id: item.id,
|
||||
document_id: item.document_id,
|
||||
})),
|
||||
}
|
||||
problemApi.putMulAssociationProblem(id, data, loading).then(() => {
|
||||
MsgSuccess(t('views.problem.tip.relatedSuccess'))
|
||||
dialogVisible.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function associationClick(item: any) {
|
||||
if (isMul.value) {
|
||||
if (isAssociation(item.id)) {
|
||||
associationParagraph.value.splice(associationParagraph.value.indexOf(item.id), 1)
|
||||
} else {
|
||||
associationParagraph.value.push(item)
|
||||
}
|
||||
} else {
|
||||
if (isAssociation(item.id)) {
|
||||
paragraph
|
||||
.asyncDisassociationProblem(
|
||||
id,
|
||||
item.document_id,
|
||||
item.id,
|
||||
currentProblemId.value as string,
|
||||
loading,
|
||||
)
|
||||
.then(() => {
|
||||
getRecord(currentProblemId.value)
|
||||
})
|
||||
} else {
|
||||
paragraph
|
||||
.asyncAssociationProblem(
|
||||
id,
|
||||
item.document_id,
|
||||
item.id,
|
||||
currentProblemId.value as string,
|
||||
loading,
|
||||
)
|
||||
.then(() => {
|
||||
getRecord(currentProblemId.value)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function searchHandle() {
|
||||
paginationConfig.current_page = 1
|
||||
paragraphList.value = []
|
||||
currentDocument.value && getParagraphList(currentDocument.value)
|
||||
}
|
||||
|
||||
function clickDocumentHandle(item: any) {
|
||||
paginationConfig.current_page = 1
|
||||
paragraphList.value = []
|
||||
currentDocument.value = item.id
|
||||
getParagraphList(item.id)
|
||||
}
|
||||
|
||||
function getDocument() {
|
||||
document.asyncGetAllDocument(id, loading).then((res: any) => {
|
||||
cloneDocumentList.value = res.data
|
||||
documentList.value = res.data
|
||||
currentDocument.value = cloneDocumentList.value?.length > 0 ? cloneDocumentList.value[0].id : ''
|
||||
currentDocument.value && getParagraphList(currentDocument.value)
|
||||
})
|
||||
}
|
||||
|
||||
function getParagraphList(documentId: string) {
|
||||
paragraphApi
|
||||
.getParagraphPage(
|
||||
id,
|
||||
(documentId || currentDocument.value) as string,
|
||||
paginationConfig,
|
||||
search.value && { [searchType.value]: search.value },
|
||||
loading,
|
||||
)
|
||||
.then((res) => {
|
||||
paragraphList.value = [...paragraphList.value, ...res.data.records]
|
||||
paginationConfig.total = res.data.total
|
||||
})
|
||||
}
|
||||
|
||||
// 已关联分段
|
||||
function getRecord(problemId: string) {
|
||||
problemApi.getDetailProblems(id as string, problemId as string, loading).then((res) => {
|
||||
associationParagraph.value = res.data
|
||||
})
|
||||
}
|
||||
|
||||
function associationCount(documentId: string) {
|
||||
return associationParagraph.value.filter((item) => item.document_id === documentId).length
|
||||
}
|
||||
function isAssociation(paragraphId: string) {
|
||||
return associationParagraph.value.some((option) => option.id === paragraphId)
|
||||
}
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
documentList.value = []
|
||||
cloneDocumentList.value = []
|
||||
paragraphList.value = []
|
||||
associationParagraph.value = []
|
||||
isMul.value = false
|
||||
|
||||
currentDocument.value = ''
|
||||
search.value = ''
|
||||
searchType.value = 'title'
|
||||
emit('refresh')
|
||||
}
|
||||
})
|
||||
|
||||
watch(filterDoc, (val) => {
|
||||
paragraphList.value = []
|
||||
documentList.value = val
|
||||
? cloneDocumentList.value.filter((item) => item.name.includes(val))
|
||||
: cloneDocumentList.value
|
||||
currentDocument.value = documentList.value?.length > 0 ? documentList.value[0].id : ''
|
||||
})
|
||||
|
||||
const open = (problemId: any) => {
|
||||
getDocument()
|
||||
if (problemId.length == 1) {
|
||||
currentProblemId.value = problemId[0]
|
||||
getRecord(problemId)
|
||||
} else if (problemId.length > 1) {
|
||||
currentMulProblemId.value = problemId
|
||||
isMul.value = true
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.paragraph-card {
|
||||
position: relative;
|
||||
// card 选中样式
|
||||
&.selected {
|
||||
border: 1px solid var(--el-color-primary) !important;
|
||||
&: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);
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
.paragraph-badge {
|
||||
.el-badge__content {
|
||||
height: auto;
|
||||
display: table;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,388 +0,0 @@
|
|||
<template>
|
||||
<div class="document p-16-24">
|
||||
<h2 class="mb-16">{{ $t('views.problem.title') }}</h2>
|
||||
<el-card style="--el-card-padding: 0">
|
||||
<div class="main-calc-height">
|
||||
<div class="p-24">
|
||||
<div class="flex-between">
|
||||
<div>
|
||||
<el-button type="primary" @click="createProblem"
|
||||
>{{ $t('views.problem.createProblem') }}
|
||||
</el-button>
|
||||
<el-button @click="relateProblem()" :disabled="multipleSelection.length === 0"
|
||||
>{{ $t('views.problem.relateParagraph.title') }}
|
||||
</el-button>
|
||||
<el-button @click="deleteMulDocument" :disabled="multipleSelection.length === 0"
|
||||
>{{ $t('views.problem.setting.batchDelete') }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-input
|
||||
v-model="filterText"
|
||||
:placeholder="$t('views.problem.searchBar.placeholder')"
|
||||
prefix-icon="Search"
|
||||
class="w-240"
|
||||
@change="getList"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<app-table
|
||||
ref="multipleTableRef"
|
||||
class="mt-16"
|
||||
:data="problemData"
|
||||
:pagination-config="paginationConfig"
|
||||
quick-create
|
||||
:quickCreateName="$t('views.problem.quickCreateName')"
|
||||
:quickCreatePlaceholder="$t('views.problem.quickCreateProblem')"
|
||||
:quickCreateMaxlength="256"
|
||||
@sizeChange="handleSizeChange"
|
||||
@changePage="getList"
|
||||
@cell-mouse-enter="cellMouseEnter"
|
||||
@cell-mouse-leave="cellMouseLeave"
|
||||
@creatQuick="creatQuickHandle"
|
||||
@row-click="rowClickHandle"
|
||||
@selection-change="handleSelectionChange"
|
||||
:row-class-name="setRowClass"
|
||||
v-loading="loading"
|
||||
:row-key="(row: any) => row.id"
|
||||
>
|
||||
<el-table-column type="selection" width="55" :reserve-selection="true" />
|
||||
<el-table-column prop="content" :label="$t('views.problem.title')" min-width="280">
|
||||
<template #default="{ row }">
|
||||
<ReadWrite
|
||||
@change="editName($event, row.id)"
|
||||
:data="row.content"
|
||||
:showEditIcon="row.id === currentMouseId"
|
||||
:maxlength="256"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="paragraph_count"
|
||||
:label="$t('views.problem.table.paragraph_count')"
|
||||
align="right"
|
||||
min-width="100"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-link
|
||||
type="primary"
|
||||
@click.stop="rowClickHandle(row)"
|
||||
v-if="row.paragraph_count"
|
||||
>
|
||||
{{ row.paragraph_count }}
|
||||
</el-link>
|
||||
<span v-else>
|
||||
{{ row.paragraph_count }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="create_time" :label="$t('common.createTime')" width="170">
|
||||
<template #default="{ row }">
|
||||
{{ datetimeFormat(row.create_time) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="update_time"
|
||||
:label="$t('views.problem.table.updateTime')"
|
||||
width="170"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
{{ datetimeFormat(row.update_time) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('common.operation')" align="left" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<div>
|
||||
<span class="mr-4">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
:content="$t('views.problem.relateParagraph.title')"
|
||||
placement="top"
|
||||
>
|
||||
<el-button type="primary" text @click.stop="relateProblem(row)">
|
||||
<el-icon><Connection /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
<span>
|
||||
<el-tooltip effect="dark" :content="$t('common.delete')" placement="top">
|
||||
<el-button type="primary" text @click.stop="deleteProblem(row)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</app-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
<CreateProblemDialog ref="CreateProblemDialogRef" @refresh="refresh" />
|
||||
<DetailProblemDrawer
|
||||
:next="nextChatRecord"
|
||||
:pre="preChatRecord"
|
||||
ref="DetailProblemRef"
|
||||
v-model:currentId="currentClickId"
|
||||
v-model:currentContent="currentContent"
|
||||
:pre_disable="pre_disable"
|
||||
:next_disable="next_disable"
|
||||
@refresh="refreshRelate"
|
||||
/>
|
||||
<RelateProblemDialog ref="RelateProblemDialogRef" @refresh="refreshRelate" />
|
||||
</div>
|
||||
</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/system-shared/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/modules-shared-system'
|
||||
import { t } from '@/locales'
|
||||
|
||||
const route = useRoute()
|
||||
const {
|
||||
params: { id }, // 知识库id
|
||||
} = route as any
|
||||
|
||||
const { problem } = useStore()
|
||||
|
||||
const RelateProblemDialogRef = ref()
|
||||
const DetailProblemRef = ref()
|
||||
const CreateProblemDialogRef = ref()
|
||||
const loading = ref(false)
|
||||
|
||||
// 当前需要修改问题的id
|
||||
const currentMouseId = ref('')
|
||||
// 当前点击打开drawer的id
|
||||
const currentClickId = ref('')
|
||||
const currentContent = ref('')
|
||||
|
||||
const paginationConfig = reactive({
|
||||
current_page: 1,
|
||||
page_size: 10,
|
||||
total: 0,
|
||||
})
|
||||
|
||||
const filterText = ref('')
|
||||
const problemData = ref<any[]>([])
|
||||
const problemIndexMap = computed<Dict<number>>(() => {
|
||||
return problemData.value
|
||||
.map((row, index) => ({
|
||||
[row.id]: index,
|
||||
}))
|
||||
.reduce((pre, next) => ({ ...pre, ...next }), {})
|
||||
})
|
||||
|
||||
const multipleTableRef = ref<InstanceType<typeof ElTable>>()
|
||||
const multipleSelection = ref<any[]>([])
|
||||
|
||||
function relateProblem(row?: any) {
|
||||
const arr: string[] = []
|
||||
if (row) {
|
||||
arr.push(row.id)
|
||||
} else {
|
||||
multipleSelection.value.map((v) => {
|
||||
if (v) {
|
||||
arr.push(v.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RelateProblemDialogRef.value.open(arr)
|
||||
}
|
||||
|
||||
function createProblem() {
|
||||
CreateProblemDialogRef.value.open()
|
||||
}
|
||||
|
||||
const handleSelectionChange = (val: any[]) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
|
||||
/*
|
||||
快速创建空白文档
|
||||
*/
|
||||
function creatQuickHandle(val: string) {
|
||||
loading.value = true
|
||||
const obj = [val]
|
||||
problem
|
||||
.asyncPostProblem(id, obj)
|
||||
.then((res) => {
|
||||
getList()
|
||||
MsgSuccess(t('common.createSuccess'))
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function deleteMulDocument() {
|
||||
const arr: string[] = []
|
||||
multipleSelection.value.map((v) => {
|
||||
if (v) {
|
||||
arr.push(v.id)
|
||||
}
|
||||
})
|
||||
problemApi.putMulProblem(id, arr, loading).then(() => {
|
||||
MsgSuccess(t('views.document.delete.successMessage'))
|
||||
multipleTableRef.value?.clearSelection()
|
||||
getList()
|
||||
})
|
||||
}
|
||||
|
||||
function deleteProblem(row: any) {
|
||||
MsgConfirm(
|
||||
`${t('views.problem.delete.confirmTitle')} ${row.content} ?`,
|
||||
`${t('views.problem.delete.confirmMessage1')} ${row.paragraph_count} ${t('views.problem.delete.confirmMessage2')}`,
|
||||
{
|
||||
confirmButtonText: t('common.confirm'),
|
||||
confirmButtonClass: 'danger',
|
||||
},
|
||||
)
|
||||
.then(() => {
|
||||
problemApi.delProblems(id, row.id, loading).then(() => {
|
||||
MsgSuccess(t('common.deleteSuccess'))
|
||||
getList()
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
function editName(val: string, problemId: string) {
|
||||
if (val) {
|
||||
const obj = {
|
||||
content: val,
|
||||
}
|
||||
problemApi.putProblems(id, problemId, obj, loading).then(() => {
|
||||
getList()
|
||||
MsgSuccess(t('common.modifySuccess'))
|
||||
})
|
||||
} else {
|
||||
MsgError(t('views.problem.tip.errorMessage'))
|
||||
}
|
||||
}
|
||||
|
||||
function cellMouseEnter(row: any) {
|
||||
currentMouseId.value = row.id
|
||||
}
|
||||
|
||||
function cellMouseLeave() {
|
||||
currentMouseId.value = ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 下一页
|
||||
*/
|
||||
const nextChatRecord = () => {
|
||||
let index = problemIndexMap.value[currentClickId.value] + 1
|
||||
if (index >= problemData.value.length) {
|
||||
if (
|
||||
index + (paginationConfig.current_page - 1) * paginationConfig.page_size >=
|
||||
paginationConfig.total - 1
|
||||
) {
|
||||
return
|
||||
}
|
||||
paginationConfig.current_page = paginationConfig.current_page + 1
|
||||
getList().then(() => {
|
||||
index = 0
|
||||
currentClickId.value = problemData.value[index].id
|
||||
currentContent.value = problemData.value[index].content
|
||||
})
|
||||
} else {
|
||||
currentClickId.value = problemData.value[index].id
|
||||
currentContent.value = problemData.value[index].content
|
||||
}
|
||||
}
|
||||
const pre_disable = computed(() => {
|
||||
const index = problemIndexMap.value[currentClickId.value] - 1
|
||||
return index < 0 && paginationConfig.current_page <= 1
|
||||
})
|
||||
|
||||
const next_disable = computed(() => {
|
||||
const index = problemIndexMap.value[currentClickId.value] + 1
|
||||
return (
|
||||
index >= problemData.value.length &&
|
||||
index + (paginationConfig.current_page - 1) * paginationConfig.page_size >=
|
||||
paginationConfig.total - 1
|
||||
)
|
||||
})
|
||||
/**
|
||||
* 上一页
|
||||
*/
|
||||
const preChatRecord = () => {
|
||||
let index = problemIndexMap.value[currentClickId.value] - 1
|
||||
|
||||
if (index < 0) {
|
||||
if (paginationConfig.current_page <= 1) {
|
||||
return
|
||||
}
|
||||
paginationConfig.current_page = paginationConfig.current_page - 1
|
||||
getList().then((ok) => {
|
||||
index = paginationConfig.page_size - 1
|
||||
currentClickId.value = problemData.value[index].id
|
||||
currentContent.value = problemData.value[index].content
|
||||
})
|
||||
} else {
|
||||
currentClickId.value = problemData.value[index].id
|
||||
currentContent.value = problemData.value[index].content
|
||||
}
|
||||
}
|
||||
|
||||
function rowClickHandle(row: any, column?: any) {
|
||||
if (column && column.type === 'selection') {
|
||||
return
|
||||
}
|
||||
if (row.paragraph_count) {
|
||||
currentClickId.value = row.id
|
||||
currentContent.value = row.content
|
||||
DetailProblemRef.value.open()
|
||||
}
|
||||
}
|
||||
|
||||
const setRowClass = ({ row }: any) => {
|
||||
return currentClickId.value === row?.id ? 'highlight' : ''
|
||||
}
|
||||
|
||||
function handleSizeChange() {
|
||||
paginationConfig.current_page = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
function getList() {
|
||||
return problem
|
||||
.asyncGetProblem(
|
||||
id as string,
|
||||
paginationConfig,
|
||||
filterText.value && { content: filterText.value },
|
||||
loading,
|
||||
)
|
||||
.then((res: any) => {
|
||||
problemData.value = res.data.records
|
||||
paginationConfig.total = res.data.total
|
||||
})
|
||||
}
|
||||
|
||||
function refreshRelate() {
|
||||
getList()
|
||||
multipleTableRef.value?.clearSelection()
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
paginationConfig.current_page = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<div class="tool-shared">
|
||||
<ToolListContainer>
|
||||
<template #header>
|
||||
<el-breadcrumb separator-icon="ArrowRight">
|
||||
<el-breadcrumb-item>{{ t('views.system.shared.shared_resources') }}</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>
|
||||
<h5 class="ml-4 color-text-primary">{{ t('views.tool.title') }}</h5>
|
||||
</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</template>
|
||||
</ToolListContainer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, reactive, computed } from 'vue'
|
||||
|
||||
import ToolListContainer from '@/views/tool/component/ToolListContainer.vue'
|
||||
|
||||
import { t } from '@/locales'
|
||||
|
||||
onMounted(() => {})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -150,9 +150,7 @@
|
|||
:description="item.desc"
|
||||
class="cursor"
|
||||
@click.stop="openCreateDialog(item)"
|
||||
:disabled="
|
||||
permissionPrecise.edit()
|
||||
"
|
||||
:disabled="permissionPrecise.edit()"
|
||||
>
|
||||
<template #icon>
|
||||
<el-avatar
|
||||
|
|
@ -338,10 +336,7 @@ const permissionPrecise = computed(() => {
|
|||
|
||||
const InitParamDrawerRef = ref()
|
||||
const search_type = ref('name')
|
||||
const search_form = ref<{
|
||||
name: string
|
||||
create_user: string
|
||||
}>({
|
||||
const search_form = ref<any>({
|
||||
name: '',
|
||||
create_user: '',
|
||||
})
|
||||
|
|
@ -573,8 +568,11 @@ watch(
|
|||
{ deep: true, immediate: true },
|
||||
)
|
||||
function getList() {
|
||||
const params = {
|
||||
[search_type.value]: search_form.value[search_type.value],
|
||||
}
|
||||
tool
|
||||
.asyncGetToolListPage(paginationConfig, isShared.value, type.value, loading)
|
||||
.asyncGetToolListPage(paginationConfig, isShared.value, type.value, params, loading)
|
||||
.then((res: any) => {
|
||||
paginationConfig.total = res.data?.total
|
||||
tool.setToolList([...tool.toolList, ...res.data?.records])
|
||||
|
|
|
|||
Loading…
Reference in New Issue