feat: knowledg

This commit is contained in:
wangdan-fit2cloud 2025-06-24 18:44:36 +08:00
parent 0d06343dc3
commit 7210e5d1d5
44 changed files with 567 additions and 8199 deletions

View File

@ -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[]

View File

@ -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 }, // idknowledgeID
} = 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('') // documentparagraph
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>

View File

@ -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',

View File

@ -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)
// })
// },
},
})

View File

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

View File

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

View File

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

View File

@ -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'

View File

@ -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 }, // idknowledgeIDid folder_tokentoken
} = 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>

View File

@ -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 }, // idknowledgeIDid
} = 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>

View File

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

View File

@ -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: web0:
//
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>

View File

@ -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 }, // idknowledgeID
} = 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>

View File

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

View File

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

View File

@ -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 }, // idknowledgeID
} = 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>

View File

@ -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 }, // idknowledgeID
} = 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>

View File

@ -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') }}XLSXLSXCSVZIP</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') }}XLSXLSXCSV</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')
}}TXTMarkdownPDFDOCXHTMLXLSXLSXCSVZIP
</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) => {
//1100M
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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
// carddropdown
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>

View File

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

View File

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

View File

@ -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 }, // idknowledgeId
} = 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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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('')
// drawerid
const currentClickId = ref('')
const currentContent = ref('')
const paginationConfig = reactive({
current_page: 1,
page_size: 10,
total: 0,
})
const filterText = ref('')
const problemData = ref<any[]>([])
const problemIndexMap = computed<Dict<number>>(() => {
return problemData.value
.map((row, index) => ({
[row.id]: index,
}))
.reduce((pre, next) => ({ ...pre, ...next }), {})
})
const multipleTableRef = ref<InstanceType<typeof ElTable>>()
const multipleSelection = ref<any[]>([])
function relateProblem(row?: any) {
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>

View File

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

View File

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