Commit 942e65b9 authored by nick zheng's avatar nick zheng

feat: 新增知识库管理

parent 147e7f9d
......@@ -7,6 +7,7 @@ import Components from 'unplugin-vue-components/vite'
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
import UnoCSS from 'unocss/vite'
import VueJsx from '@vitejs/plugin-vue-jsx'
export function setupPlugins(isBuild: boolean, envConf: ViteEnv, pathResolve: (dir: string) => string): PluginOption[] {
const lifecycle = process.env.npm_lifecycle_event
......@@ -27,6 +28,7 @@ export function setupPlugins(isBuild: boolean, envConf: ViteEnv, pathResolve: (d
include: [pathResolve('./src/locales/langs/**')],
}),
UnoCSS(),
VueJsx(),
]
if (envConf.VITE_VITEST && !isBuild) {
......
......@@ -50,6 +50,7 @@
"@typescript-eslint/parser": "^7.18.0",
"@unocss/eslint-config": "^0.61.9",
"@vitejs/plugin-vue": "^4.6.2",
"@vitejs/plugin-vue-jsx": "^4.0.1",
"autoprefixer": "^10.4.20",
"eslint": "^9.10.0",
"eslint-config-prettier": "^9.1.0",
......
This diff is collapsed.
import { request } from '@/utils/request'
/* 知识库部分开始 */
/**
* @query trainStatus 训练状态
* @query search 搜索值
* @returns 获取知识库列表
*/
export function fetchGetKnowledgeList<T>(trainStatus: string, search: string, payload: object) {
return request.post<T>(`/knowledgeRest/getKnowledgeList.json?trainStatus=${trainStatus}&search=${search}`, payload)
}
/**
* @param payload { decs: string, knowledgeName: string }
* @returns 新建知识库
*/
export function fetchCreateKnowledge<T>(payload: object) {
return request.post<T>('/knowledgeRest/createKnowledge.json', payload)
}
/**
* @param payload { id:number, decs: string, knowledgeName: string }
* @returns 更新知识库
*/
export function fetchUpdateKnowledgeInfo<T>(payload: object) {
return request.post<T>('/knowledgeRest/updateKnowledgeInfo.json', payload)
}
/**
* @query knowledgeInfoId 知识库Id
* @returns 通过Id获取知识库详情
*/
export function fetchGetKnowledgeDetail<T>(knowledgeInfoId: number) {
return request.post<T>(`/knowledgeRest/getKnowledgeDetail.json?knowledgeInfoId=${knowledgeInfoId}`)
}
/**
* @query knowledgeInfoId 知识库Id
* @query isOpen 是否开启 Y | N
* @returns 开关知识库
*/
export function fetchEnableKnowledgeInfo(knowledgeInfoId: number, isOpen: string) {
return request.post(`/knowledgeRest/enableKnowledgeInfo.json?knowledgeInfoId=${knowledgeInfoId}&isOpen=${isOpen}`)
}
/**
* @query knowledgeInfoId 知识库Id
* @returns 批量删除知识库
*/
export function fetchDelKnowledgeById<T>(knowledgeInfoId: number) {
return request.post<T>(`/knowledgeRest/deleteKnowledgeInfo.json?knowledgeInfoId=${knowledgeInfoId}`)
}
/**
* @query formData 上传文档内容
* @returns 上传知识库文档
*/
export function fetchUploadKnowledgeDocument<T>(formData: FormData) {
return request.post<T>('/knowledgeRest/uploadDocument.json', formData, {
headers: {
'Content-Type': 'multipart/form-data',
},
timeout: 300000,
})
}
/**
* @params { knowledgeInfoId: number, kdIds: number[], segmentationConfig: SegmentationConfigInterface }
* @returns 训练知识库
*/
export function fetchTrainKnowledge<T>(payload: object) {
return request.post<T>('/knowledgeRest/trainKnowledge.json', payload)
}
/* 知识库部分结束 */
/* 知识库文档部分开始 */
/**
* @query knowledgeInfoId 知识库Id
* @query search 搜索值
* @returns 获取知识库文档列表
*/
export function fetchGetKnowledgeDocumentList<T>(knowledgeInfoId: number, search: string) {
return request.post<T>(`/knowledgeRest/searchDocuments.json?knowledgeInfoId=${knowledgeInfoId}&search=${search}`)
}
/**
* @query knowledgeInfoId 知识库Id
* @params kdIds 知识库文档Id
* @returns 批量删除知识库文档
*/
export function fetchBatchDelKnowledgeDocument(knowledgeInfoId: number, payload: number[]) {
return request.post(`/knowledgeRest/batchDelDocument.json?knowledgeInfoId=${knowledgeInfoId}`, payload)
}
/**
* @query knowledgeInfoId 知识库Id
* @query kdId 知识库文档Id
* @returns 删除知识库文档
*/
export function fetchDelKnowledgeDocument(knowledgeInfoId: number, kdId: number) {
return request.post(`/knowledgeRest/delDocument.json?knowledgeInfoId=${knowledgeInfoId}&kdId=${kdId}`)
}
/**
* @params kdIds 知识库文档Id
* @returns 根据kdIds获取知识库文档列表
*/
export function fetchGetKnowledgeDocumentListByKdIds<T>(kdIds: number[]) {
return request.post<T>(`/knowledgeRest/getListByKdIds.json`, kdIds)
}
/* 知识库文档片段开始 */
/**
* @query query 模糊搜索
* @query kdId 知识库文档Id
* @params payload { page: number, pageSize: number }
* @returns 获取知识库分片列表
*/
export function fetchGetKnowledgeChunkList<T>(query: string, kdId: number, payload: object) {
return request.post<T>(`/knowledgeRest/getChunks.json?query=${query}&kdIds=${kdId}`, payload)
}
/**
* @params payload { kdId: number, chunkContent: string, chunkSort: number }
* @returns 新增知识库分片
*/
export function fetchAddKnowledgeChunk<T>(payload: { kdId: number; chunkContent: string; chunkSort: number }) {
return request.post<T>('/knowledgeRest/addKnowledgeChunk.json', payload)
}
/**
* @params payload { kdId: number, chunkContent: string, chunkRelationId: string }
* @returns 更新知识库分片
*/
export function fetchUpdateKnowledgeChunk<T>(payload: { kdId: number; chunkContent: string; chunkRelationId: string }) {
return request.post<T>('/knowledgeRest/updateKnowledgeChunkDoc.json', payload)
}
/**
* @params payload { kdId: number, chunkRelationId: string }
* @returns 删除知识库分片
*/
export function fetchDeleteKnowledgeChunk<T>(payload: { kdId: number; chunkRelationId: string }) {
return request.post<T>('/knowledgeRest/deleteKnowledgeChunk.json', payload)
}
/**
* @params payload { kdId: number, chunkRelationId: string, isOpen: 'Y' | 'N' }
* @returns 开关知识库分片
*/
export function fetchOpenKnowledgeChunk<T>(payload: { kdId: number; chunkRelationId: string; isOpen: 'Y' | 'N' }) {
return request.post<T>('/knowledgeRest/openKnowledgeChunk.json', payload)
}
/* 知识库文档部分结束 */
<?xml version="1.0" encoding="UTF-8"?>
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组</title>
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="个人空间-删除切片" transform="translate(-831.000000, -492.000000)" fill-rule="nonzero">
<g id="编组" transform="translate(831.000000, 492.000000)">
<path d="M9,18 C6.612215,18.0030765 4.32175345,17.0539566 2.6359938,15.3628792 C0.945884088,13.6774855 -0.0027748396,11.3879687 0,9.00112665 C-0.00320129147,6.61331659 0.945937676,4.32280901 2.63712078,2.63712016 C4.3223293,0.946597065 6.61186896,-0.00247695124 8.99887335,0 C11.3866587,-0.00307708581 13.6771202,0.946042805 15.3628798,2.63712016 C17.05368,4.32260425 18.002777,6.61260485 18,9 C18.0030771,11.387785 17.0539572,13.6782466 15.3628798,15.3640062 C13.6771202,17.0550836 11.3866587,18.0042034 8.99887335,18.0011264 L9,18 Z" id="路径" fill="#F25744"></path>
<path d="M9.00000032,3.3797942 C9.31104437,3.3797942 9.74154737,3.65026729 9.74154737,4.01427899 L9.74154737,10.5879021 C9.74154737,10.9507869 9.31104435,11.2381645 9.00000032,11.2381645 C8.68895629,11.2381645 8.25845325,10.9507869 8.25845325,10.5879021 L8.25845325,4.01427899 C8.25845325,3.65026729 8.68895627,3.3797942 9.00000032,3.3797942 Z" id="路径" fill="#FFFFFF"></path>
<path d="M9.09917378,14.5818882 C8.47676476,14.5818882 7.97220256,14.077326 7.97220256,13.4549169 C7.97220256,12.8325079 8.47676476,12.3279457 9.09917378,12.3279457 C9.7215828,12.3279457 10.226145,12.8325079 10.226145,13.4549169 C10.226145,14.077326 9.7215828,14.5818882 9.09917378,14.5818882 L9.09917378,14.5818882 Z" id="路径" fill="#FFFFFF"></path>
</g>
</g>
</g>
</svg>
\ No newline at end of file
......@@ -34,8 +34,51 @@ export default [
},
component: () => import('@/views/personal-space/personal-app/personal-app.vue'),
},
{
path: '/personalSpace/knowledge',
name: 'PersonalSpaceKnowledge',
meta: {
rank: 1001,
title: 'router_title_module.knowledge',
belong: 'PersonalSpace',
},
component: () => import('@/views/personal-space/personal-knowledge/personal-knowledge.vue'),
},
],
},
{
path: '/personalSpace/knowledge/document/:id',
name: 'KnowledgeDocument',
meta: {
rank: 1001,
title: 'router_title_module.knowledge_document_list',
belong: 'PersonalSpace',
},
component: () => import('@/views/personal-space/personal-knowledge/personal-document.vue'),
},
{
path: '/personalSpace/knowledge/document/detail/:kdId',
name: 'KnowledgeDocumentDetail',
meta: {
rank: 1001,
title: 'router_title_module.knowledge_document_detail',
belong: 'PersonalSpace',
},
component: () => import('@/views/personal-space/personal-knowledge/document-detail.vue'),
},
{
path: '/personalSpace/knowledge/upload/:id/:type?',
name: 'UploadKnowledge',
meta: {
rank: 1001,
title: 'router_title_module.upload_knowledge_document',
belong: 'PersonalSpace',
},
component: () => import('@/views/personal-space/personal-knowledge/upload-knowledge/upload-knowledge.vue'),
},
],
},
{
......
import { NSwitch, NPopconfirm } from 'naive-ui'
import { KnowledgeDocumentItem, KnowledgeItem } from './knowledge-type'
import { formatDateTime } from '@/utils/date-formatter'
import i18n from '@/locales'
const t = i18n.global.t
export function createKnowledgeColumn(
handleKnowledgeTableAction: (actionType: string, knowledgeId: number, KnowledgeItem?: KnowledgeItem) => void,
) {
return [
{
title: () => <span>{t('personal_space_module.knowledge_module.knowledge_name')}</span>,
key: 'knowledgeName',
align: 'left',
ellipsis: {
tooltip: true,
},
width: 210,
fixed: 'left',
render(row: KnowledgeItem) {
return row.knowledgeName || '--'
},
},
{
title: () => <span>{t('personal_space_module.knowledge_module.knowledge_desc')}</span>,
key: 'knowledgeDesc',
align: 'left',
ellipsis: {
tooltip: true,
},
width: 380,
render(row: KnowledgeItem) {
return row.desc || '--'
},
},
{
title: () => <span>{t('common_module.modified_time')}</span>,
key: 'modifiedTime',
align: 'left',
ellipsis: {
tooltip: true,
},
width: 170,
render(row: KnowledgeItem) {
return formatDateTime(row.modifiedTime) || '--'
},
},
{
title: () => <span>{t('common_module.is_open')}</span>,
key: 'isOpen',
align: 'left',
ellipsis: {
tooltip: true,
},
width: 150,
render(row: KnowledgeItem) {
return (
<NSwitch
value={row.isOpen === 'Y'}
onUpdateValue={() => handleKnowledgeTableAction('updateOpen', row.id, row)}
/>
)
},
},
{
title: () => <span>{t('common_module.data_table_module.action')}</span>,
key: 'action',
align: 'left',
ellipsis: {
tooltip: true,
},
width: 190,
fixed: 'right',
render(row: KnowledgeItem) {
return (
<div>
<span
className='text-theme-color mr-5 cursor-pointer hover:opacity-80'
onClick={() => handleKnowledgeTableAction('view', row.id)}
>
{t('common_module.data_table_module.view')}
</span>
<NPopconfirm
negative-text={t('common_module.cancel_btn_text')}
positive-text={t('common_module.confirm_btn_text')}
onNegativeClick={() => {}}
onPositiveClick={() => handleKnowledgeTableAction('delete', row.id)}
>
{{
trigger: () => (
<span className='text-error-font-color mr-5 cursor-pointer hover:opacity-80'>
{t('common_module.data_table_module.delete')}
</span>
),
default: () => (
<span> {t('personal_space_module.knowledge_module.delete_knowledge_dialog_content')}</span>
),
icon: () => (
<div class='bg-background-color h-4 w-4 rounded-full bg-[url(@/assets/svgs/warning.svg)] bg-contain' />
),
}}
</NPopconfirm>
</div>
)
},
},
]
}
export function createKnowledgeDocumentColumn(
handleKnowledgeDocumentTableAction: (actionType: string, knowledgeDocumentItem: KnowledgeDocumentItem) => void,
) {
return [
{
type: 'selection',
fixed: 'left',
},
{
title: () => <span>{t('personal_space_module.knowledge_module.knowledge_document_name')}</span>,
key: 'documentName',
align: 'left',
ellipsis: {
tooltip: true,
},
width: 190,
render(row: KnowledgeDocumentItem) {
return row.documentName || '--'
},
},
{
title: () => <span>{t('personal_space_module.knowledge_module.knowledge_document_char_count')}</span>,
key: 'charCount',
align: 'left',
ellipsis: {
tooltip: true,
},
width: 330,
render(row: KnowledgeDocumentItem) {
return `${row.charCount || 0}` + t('common_module.char')
},
},
{
title: () => <span>{t('personal_space_module.knowledge_module.knowledge_document_format')}</span>,
key: 'format',
align: 'left',
ellipsis: {
tooltip: true,
},
width: 220,
render(row: KnowledgeDocumentItem) {
return row.documentName.split('.')[1].toUpperCase() || '--'
},
},
{
title: () => <span>{t('common_module.modified_time')}</span>,
key: 'uploadTime',
align: 'left',
ellipsis: {
tooltip: true,
},
width: 300,
render(row: KnowledgeDocumentItem) {
return formatDateTime(row.uploadTime) || '--'
},
},
{
title: () => <span>{t('common_module.is_open')}</span>,
key: 'trainStatus',
align: 'left',
width: 180,
render(row: KnowledgeDocumentItem) {
const trainStatusMap = {
Unopened: { text: 'common_module.studying', color: '#0B7DFF' },
Line: { text: 'common_module.studying', color: '#0B7DFF' },
Training: { text: 'common_module.studying', color: '#0B7DFF' },
Complete: { text: 'common_module.available', color: '#0DD623' },
Fail: { text: 'common_module.unavailable', color: '#F25744' },
}
const state = trainStatusMap[row.trainStatus]
return (
<div class='flex items-center'>
{['Unopened', 'Line', 'Training'].includes(row.trainStatus) && (
<i class='iconfont icon-study mr-1.5 text-[#0B7DFF]' />
)}
{['Complete', 'Fail'].includes(row.trainStatus) && (
<div class='mr-2.5 h-3 w-3 rounded-full' style={{ backgroundColor: state.color }} />
)}
<span>{t(state.text)}</span>
</div>
)
},
},
{
title: () => <span>{t('common_module.data_table_module.action')}</span>,
key: 'action',
align: 'left',
width: 250,
fixed: 'right',
render(row: KnowledgeDocumentItem) {
return (
<div>
{row.trainStatus !== 'Fail' && (
<span
class={[
row.trainStatus === 'Complete'
? 'mr-5 cursor-pointer text-[#0B7DFF] hover:opacity-80'
: 'text-gray-font-color pointer-events-none mr-5 cursor-not-allowed',
]}
onClick={() => handleKnowledgeDocumentTableAction('view', row)}
>
{t('personal_space_module.knowledge_module.view_knowledge_document_fragment')}
</span>
)}
{row.trainStatus === 'Fail' && (
<span
class='mr-5 cursor-pointer text-[#0B7DFF] hover:opacity-80'
onClick={() => handleKnowledgeDocumentTableAction('train', row)}
>
{t('personal_space_module.knowledge_module.training_knowledge_document')}
</span>
)}
<NPopconfirm
negative-text={t('common_module.cancel_btn_text')}
positive-text={t('common_module.confirm_btn_text')}
onNegativeClick={() => {}}
onPositiveClick={() => handleKnowledgeDocumentTableAction('delete', row)}
>
{{
trigger: () => (
<span
class={[
row.trainStatus === 'Complete'
? 'text-error-font-color mr-5 cursor-pointer hover:opacity-80'
: 'text-gray-font-color pointer-events-none mr-5 cursor-not-allowed',
]}
>
{t('common_module.data_table_module.delete')}
</span>
),
default: () => (
<span> {t('personal_space_module.knowledge_module.delete_knowledge_dialog_content')}</span>
),
icon: () => (
<div class='bg-background-color h-4 w-4 rounded-full bg-[url(@/assets/svgs/warning.svg)] bg-contain' />
),
}}
</NPopconfirm>
</div>
)
},
},
]
}
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { FormInst } from 'naive-ui'
import CustomModal from '@/components/custom-modal/custom-modal.vue'
import { KnowledgeChunkItem } from '../knowledge-type'
interface Props {
isShowModal: boolean
btnLoading: boolean
chunkItem: KnowledgeChunkItem
}
interface Emits {
(e: 'update:isShowModal', value: boolean): void
(e: 'confirm', chunkContent: string, chunkSort: number): void
}
const { t } = useI18n()
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const defaultKnowledgeChunkData: KnowledgeChunkItem = {
knowledgeId: '',
chunkContent: '',
chunkRelationId: '',
chunkSort: 0,
isOpen: 'Y',
}
const knowledgeChunkFormRef = ref<FormInst | null>(null)
const knowledgeChunkData = ref<KnowledgeChunkItem>({ ...defaultKnowledgeChunkData })
const knowledgeChunkRules = {
chunkContent: [
{
required: true,
message: () => t('personal_space_module.knowledge_module.knowledge_chunk_content_input_rule'),
trigger: 'blur',
},
],
}
const showModal = computed({
get() {
return props.isShowModal
},
set(value: boolean) {
emit('update:isShowModal', value)
},
})
const isDisabledConfirmBtn = computed(() => {
return !knowledgeChunkData.value.chunkContent
})
watch(
() => showModal.value,
(newVal) => {
newVal && (knowledgeChunkData.value = JSON.parse(JSON.stringify(props.chunkItem)))
},
)
function handleConfirm() {
knowledgeChunkFormRef.value?.validate((valid) => {
if (!valid) {
const { chunkContent, chunkSort } = knowledgeChunkData.value
emit('confirm', chunkContent, chunkSort)
}
})
}
</script>
<template>
<CustomModal
v-model:is-show="showModal"
:title="t('personal_space_module.knowledge_module.add_knowledge_chunk_modal_title')"
:width="808"
:height="710"
:btn-disabled="isDisabledConfirmBtn"
:btn-loading="btnLoading"
@confirm="handleConfirm"
>
<template #content>
<n-form
ref="knowledgeChunkFormRef"
:model="knowledgeChunkData"
:rules="knowledgeChunkRules"
:label-width="100"
label-placement="top"
>
<n-form-item :show-label="false" path="chunkContent">
<n-input
v-model:value="knowledgeChunkData.chunkContent"
type="textarea"
:placeholder="t('personal_space_module.knowledge_module.knowledge_chunk_content_input_placeholder')"
:rows="23"
/>
</n-form-item>
</n-form>
</template>
</CustomModal>
</template>
<script setup lang="ts">
import { computed, h, readonly, ref, watch } from 'vue'
import { FormInst } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { FileAddition } from '@icon-park/vue-next'
import CustomModal from '@/components/custom-modal/custom-modal.vue'
const { t } = useI18n()
export interface KnowledgeFormDataInterface {
knowledgeType: string
knowledgeName: string
knowledgeDesc: string
knowledgeImportType: string
}
interface Props {
isShowModal: boolean
btnLoading: boolean
}
interface Emits {
(e: 'update:isShowModal', value: boolean): void
(e: 'confirm', value: KnowledgeFormDataInterface): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const createKnowledgeFormRef = ref<FormInst | null>(null)
const defaultKnowledgeFormData = {
knowledgeType: 'text',
knowledgeName: '',
knowledgeDesc: '',
knowledgeImportType: 'local-document',
}
const createKnowledgeFormData = ref<KnowledgeFormDataInterface>({ ...defaultKnowledgeFormData })
const createKnowledgeFormDataRules = {
knowledgeType: [
{ required: true, message: () => t('personal_space_module.knowledge_module.knowledge_type_rule'), trigger: 'blur' },
],
knowledgeName: [
{
required: true,
message: () => t('personal_space_module.knowledge_module.knowledge_name_rule'),
trigger: 'blur',
},
],
knowledgeImportType: [
{
required: true,
message: () => t('personal_space_module.knowledge_module.knowledge_import_type_rule'),
trigger: 'blur',
},
],
}
const knowledgeTypeList = readonly([
{
id: 'KJ7rCkq3HNOk',
icon: 'https://gsst-poe-sit.gz.bcebos.com/data/20241012/1728700558225.png',
type: 'text',
name: 'personal_space_module.knowledge_module.knowledge_document_text_type',
},
])
const importKnowledgeFileList = readonly([
{
id: 'ewwBbe9di5Ym',
icon: () => h(FileAddition, { theme: 'outline', size: '16', fill: '#333' }),
type: 'local-document',
title: 'personal_space_module.knowledge_module.import_local_document_knowledge',
desc: 'personal_space_module.knowledge_module.import_local_document_knowledge_desc',
},
])
const showModal = computed({
get() {
return props.isShowModal
},
set(value: boolean) {
emit('update:isShowModal', value)
},
})
const isDisabledConfirmBtn = computed(() => {
return !createKnowledgeFormData.value.knowledgeName
})
watch(
() => props.isShowModal,
() => {
createKnowledgeFormData.value = { ...defaultKnowledgeFormData }
},
)
function handleNext() {
createKnowledgeFormRef.value?.validate((errors) => {
if (!errors) {
emit('confirm', createKnowledgeFormData.value)
}
})
}
</script>
<template>
<CustomModal
v-model:is-show="showModal"
:height="562"
:width="437"
:title="t('personal_space_module.knowledge_module.create_knowledge_modal_title')"
:confirm-btn-text="t('common_module.next_btn_text')"
:btn-disabled="isDisabledConfirmBtn"
:btn-loading="btnLoading"
@confirm="handleNext"
>
<template #content>
<NForm
ref="createKnowledgeFormRef"
:model="createKnowledgeFormData"
:rules="createKnowledgeFormDataRules"
:label-width="100"
label-placement="top"
>
<NFormItem :show-label="false" path="knowledgeType">
<div class="flex w-full flex-col">
<div class="grid grid-cols-3 gap-2">
<div
v-for="knowledgeTypeItem in knowledgeTypeList"
:key="knowledgeTypeItem.id"
:class="
createKnowledgeFormData.knowledgeType === knowledgeTypeItem.type
? 'border-theme-color text-theme-color bg-[#ecedfa]'
: 'text-font-color bg-white'
"
class="hover:border-theme-color hover:text-theme-color flex h-[66px] cursor-pointer flex-col items-center justify-center rounded-lg border"
>
<img :src="knowledgeTypeItem.icon" class="h-6 w-6" />
<span class="mt-2 text-sm leading-[14px]">{{ t(knowledgeTypeItem.name) }}</span>
</div>
</div>
</div>
</NFormItem>
<NFormItem :label="t('common_module.name')" show-require-mark path="knowledgeName">
<NInput
v-model:value="createKnowledgeFormData.knowledgeName"
maxlength="15"
show-count
type="text"
:placeholder="t('personal_space_module.knowledge_module.knowledge_name_input_placeholder')"
/>
</NFormItem>
<NFormItem :label="t('common_module.desc')">
<NInput
v-model:value="createKnowledgeFormData.knowledgeDesc"
maxlength="500"
show-count
type="textarea"
:placeholder="t('personal_space_module.knowledge_module.knowledge_desc_input_placeholder')"
/>
</NFormItem>
<NFormItem
:label="t('personal_space_module.knowledge_module.knowledge_import_type_label')"
:show-require-mark="false"
path="knowledgeImportType"
>
<div class="grid w-full grid-cols-2 gap-2">
<div
v-for="importKnowledgeFileItem in importKnowledgeFileList"
:key="importKnowledgeFileItem.id"
class="flex h-[64px] cursor-pointer flex-col items-start justify-center rounded-lg border px-[14px]"
:class="
createKnowledgeFormData.knowledgeImportType === importKnowledgeFileItem.type
? 'border-theme-color bg-[#ecedfa]'
: 'border-inactive-border-color bg-[#FAFAFA]'
"
>
<div class="flex items-center">
<component :is="importKnowledgeFileItem.icon()" />
<span class="text-font-color ml-1 line-clamp-1">{{ t(importKnowledgeFileItem.title) }}</span>
</div>
<NPopover trigger="hover">
<template #trigger>
<span class="text-gray-font-color mt-1.5 line-clamp-1 text-xs">
{{ t(importKnowledgeFileItem.desc) }}
</span>
</template>
<span>{{ t(importKnowledgeFileItem.desc) }}</span>
</NPopover>
</div>
</div>
</NFormItem>
</NForm>
</template>
</CustomModal>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { FormInst } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import CustomModal from '@/components/custom-modal/custom-modal.vue'
import { KnowledgeChunkItem } from '../knowledge-type'
interface Props {
isShowModal: boolean
btnLoading: boolean
totalChunk: number
chunkItem: KnowledgeChunkItem
}
interface Emits {
(e: 'update:isShowModal', value: boolean): void
(e: 'confirm', chunkRelationId: string, chunkContent: string): void
}
const { t } = useI18n()
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const defaultKnowledgeChunkData: KnowledgeChunkItem = {
knowledgeId: '',
chunkContent: '',
chunkRelationId: '',
chunkSort: 0,
isOpen: 'Y',
}
const knowledgeChunkFormRef = ref<FormInst | null>(null)
const knowledgeChunkData = ref<KnowledgeChunkItem>({ ...defaultKnowledgeChunkData })
const knowledgeChunkRules = {
chunkContent: [
{
required: true,
message: () => t('personal_space_module.knowledge_module.knowledge_chunk_content_input_rule'),
trigger: 'blur',
},
],
}
const showModal = computed({
get() {
return props.isShowModal
},
set(value: boolean) {
emit('update:isShowModal', value)
},
})
const isDisabledConfirmBtn = computed(() => {
return !knowledgeChunkData.value.chunkContent
})
watch(
() => showModal.value,
(newVal) => {
newVal && (knowledgeChunkData.value = JSON.parse(JSON.stringify(props.chunkItem)))
},
)
function handleConfirm() {
knowledgeChunkFormRef.value?.validate((valid) => {
if (!valid) {
const { chunkRelationId, chunkContent } = knowledgeChunkData.value
emit('confirm', chunkRelationId, chunkContent)
}
})
}
</script>
<template>
<CustomModal
v-model:is-show="showModal"
title=""
:width="808"
:height="710"
:btn-disabled="isDisabledConfirmBtn"
:btn-loading="btnLoading"
@confirm="handleConfirm"
>
<template #header>
<div>
<span class="bg-background-color rounded-full px-4 py-2 text-[15px]">
{{ knowledgeChunkData.chunkSort }} / {{ totalChunk }}
</span>
<span class="ml-[18px] text-[15px]">
{{ t('common_module.char') }}{{ knowledgeChunkData.chunkContent.length }}
</span>
</div>
</template>
<template #content>
<n-form
ref="knowledgeChunkFormRef"
:model="knowledgeChunkData"
:rules="knowledgeChunkRules"
:label-width="100"
label-placement="top"
>
<n-form-item :show-label="false" path="chunkContent">
<n-input
v-model:value="knowledgeChunkData.chunkContent"
type="textarea"
:placeholder="t('personal_space_module.knowledge_module.knowledge_chunk_content_input_placeholder')"
:rows="23"
/>
</n-form-item>
</n-form>
</template>
</CustomModal>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { FormInst } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import CustomModal from '@/components/custom-modal/custom-modal.vue'
const { t } = useI18n()
export interface KnowledgeFormDataInterface {
knowledgeName: string
desc: string
}
interface Props {
isShowModal: boolean
btnLoading: boolean
knowledgeData: KnowledgeFormDataInterface
}
interface Emits {
(e: 'update:isShowModal', value: boolean): void
(e: 'confirm', value: KnowledgeFormDataInterface): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const defaultKnowledgeFormData = {
knowledgeName: '',
desc: '',
}
const knowledgeFormRef = ref<FormInst | null>(null)
const knowledgeFormData = ref<KnowledgeFormDataInterface>({ ...defaultKnowledgeFormData })
const knowledgeFormDataRules = {
knowledgeName: [
{ required: true, message: () => t('personal_space_module.knowledge_module.knowledge_name_rule'), trigger: 'blur' },
],
}
const showModal = computed({
get() {
return props.isShowModal
},
set(value: boolean) {
emit('update:isShowModal', value)
},
})
const isDisabledConfirmBtn = computed(() => {
return !knowledgeFormData.value.knowledgeName
})
watch(
() => showModal.value,
(newVal) => {
newVal && (knowledgeFormData.value = JSON.parse(JSON.stringify(props.knowledgeData)))
},
)
function handleConfirm() {
knowledgeFormRef.value?.validate((errors) => {
if (!errors) {
emit('confirm', knowledgeFormData.value)
}
})
}
</script>
<template>
<CustomModal
v-model:is-show="showModal"
:height="364"
:width="437"
:title="t('personal_space_module.knowledge_module.edit_knowledge_modal_title')"
:btn-disabled="isDisabledConfirmBtn"
:btn-loading="btnLoading"
@confirm="handleConfirm"
>
<template #content>
<NForm
ref="knowledgeFormRef"
:model="knowledgeFormData"
:rules="knowledgeFormDataRules"
:label-width="100"
label-placement="top"
>
<NFormItem :label="t('common_module.name')" show-require-mark path="knowledgeName">
<NInput
v-model:value="knowledgeFormData.knowledgeName"
maxlength="15"
show-count
type="text"
:placeholder="t('personal_space_module.knowledge_module.knowledge_name_input_placeholder')"
/>
</NFormItem>
<NFormItem :label="t('common_module.desc')">
<NInput
v-model:value="knowledgeFormData.desc"
maxlength="500"
show-count
type="textarea"
:placeholder="t('personal_space_module.knowledge_module.knowledge_desc_input_placeholder')"
/>
</NFormItem>
</NForm>
</template>
</CustomModal>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { KnowledgeChunkItem } from '../knowledge-type'
interface Props {
chunkItem: KnowledgeChunkItem
totalChunk: number
}
const { t } = useI18n()
defineProps<Props>()
const emit = defineEmits<{
(e: 'edit', value: KnowledgeChunkItem): void
(e: 'upAddChunk', chunkSort: number): void
(e: 'downAddChunk', chunkSort: number): void
(e: 'delete', chunkRelationId: string): void
(e: 'updateOpen', value: KnowledgeChunkItem): void
}>()
const isShowKnowledgeChunkAction = ref(false)
</script>
<template>
<div class="group w-full rounded-[20px] bg-white px-5 py-[18px]" @mouseleave="isShowKnowledgeChunkAction = false">
<div
class="text-gray-font-color border-inactive-border-color flex h-10 select-none items-center justify-between border-b pb-[14px] text-[13px]"
>
<span>{{ chunkItem.chunkSort }} / {{ totalChunk }}</span>
<div
class="hidden items-center gap-[10px] group-hover:flex"
:class="isShowKnowledgeChunkAction ? 'flex' : 'hidden group-hover:flex'"
>
<n-popover trigger="hover">
<template #trigger>
<i
class="iconfont icon-edit-document hover:text-font-color hover:bg-background-color flex h-6 w-6 cursor-pointer items-center justify-center rounded-full text-sm"
@click="emit('edit', chunkItem)"
/>
</template>
<span> {{ t('common_module.data_table_module.edit') }}</span>
</n-popover>
<n-popover trigger="hover">
<template #trigger>
<i
class="iconfont icon-add-chunk-up hover:text-font-color hover:bg-background-color flex h-6 w-6 cursor-pointer items-center justify-center rounded-full text-sm"
@click="emit('upAddChunk', chunkItem.chunkSort)"
/>
</template>
<span> {{ t('personal_space_module.knowledge_module.add_chunk_up_message') }} </span>
</n-popover>
<n-popover trigger="hover">
<template #trigger>
<i
class="iconfont icon-add-chunk-up hover:text-font-color hover:bg-background-color flex h-6 w-6 rotate-180 cursor-pointer items-center justify-center rounded-full text-sm"
@click="emit('downAddChunk', chunkItem.chunkSort + 1)"
/>
</template>
<span> {{ t('personal_space_module.knowledge_module.add_chunk_down_message') }} </span>
</n-popover>
<n-popconfirm
placement="bottom"
:negative-text="t('common_module.cancel_btn_text')"
:positive-text="t('common_module.confirm_btn_text')"
@negative-click="() => {}"
@positive-click="emit('delete', chunkItem.chunkRelationId)"
@mouseenter="isShowKnowledgeChunkAction = true"
>
<template #icon>
<div class="bg-background-color h-4 w-4 rounded-full bg-[url(@/assets/svgs/warning.svg)] bg-contain" />
</template>
<template #trigger>
<n-popover trigger="hover">
<template #trigger>
<i
class="iconfont icon-delete hover:text-font-color hover:bg-background-color flex h-6 w-6 cursor-pointer items-center justify-center rounded-full text-sm"
/>
</template>
<span> {{ t('common_module.data_table_module.delete') }}</span>
</n-popover>
</template>
{{ t('personal_space_module.knowledge_module.delete_knowledge_chunk_content_message') }}
</n-popconfirm>
<n-switch :value="chunkItem.isOpen === 'Y'" @update:value="emit('updateOpen', chunkItem)" />
</div>
</div>
<div class="text-font-color mt-[14px] whitespace-pre-wrap">
{{ chunkItem.chunkContent }}
</div>
</div>
</template>
export enum TrainStatus {
UNOPENED = 'Unopened',
LINE = 'Line',
TRAINING = 'Training',
COMPLETE = 'Complete',
FAIL = 'Fail',
}
export interface KnowledgeItem {
id: number
knowledgeName: string
desc: string
trainStatus: TrainStatus
isOpen: 'Y' | 'N'
modifiedTime: Date
}
export interface SegmentationConfigInterface {
segmentationType: 'Default'
chunkSize: number
scrapSize: number
repetitionRate: number
relationInfo: string[]
regex: string
punctuations: string
}
export interface KnowledgeDocumentItem {
kdId: number
knowledgeType: string
documentName: string
documentUrl: string
charCount: number
uploadTime: Date
isEnable: 'Y' | 'N'
knowledgeId: string
trainStatus: TrainStatus
segmentationConfig: SegmentationConfigInterface
}
export interface KnowledgeChunkItem {
knowledgeId: string
chunkContent: string
chunkRelationId: string
chunkSort: number
isOpen: 'Y' | 'N'
}
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { Left, Search } from '@icon-park/vue-next'
import { useIntervalFn } from '@vueuse/core'
import { createKnowledgeDocumentColumn } from './columns'
import { KnowledgeDocumentItem, TrainStatus } from './knowledge-type'
import {
fetchBatchDelKnowledgeDocument,
fetchDelKnowledgeDocument,
fetchGetKnowledgeDetail,
fetchGetKnowledgeDocumentList,
fetchUpdateKnowledgeInfo,
} from '@/apis/knowledge'
import useTableScrollY from '@/composables/useTableScrollY'
import EditKnowledgeModal, { KnowledgeFormDataInterface } from './components/edit-knowledge-modal.vue'
const { t } = useI18n()
const router = useRouter()
const { pageContentWrapRef, tableContentY } = useTableScrollY(48 + 48)
const knowledgeDocumentColumn = createKnowledgeDocumentColumn(handleClickKnowledgeDocumentTableAction)
const knowledgeDocumentTableLoading = ref(false)
const knowledgeDocumentList = ref<KnowledgeDocumentItem[]>([])
const checkedKdIdList = ref<number[]>([])
const searchDocumentInputValue = ref('')
const currentKnowledgeId = ref(0)
const isSearchEmptyList = ref(false)
const isShowEditKnowledgeModal = ref(false)
const updateKnowledgeInfoBtnLoading = ref(false)
const currentKnowledgeData = ref<KnowledgeFormDataInterface>({
knowledgeName: '',
desc: '',
})
const isDisabledBatchDelBtn = computed(() => {
return checkedKdIdList.value.length <= 0
})
const isKnowledgeDocumentFinish = computed(() => {
return knowledgeDocumentList.value.every((documentItem) => {
return [TrainStatus.FAIL, TrainStatus.COMPLETE].includes(documentItem.trainStatus)
})
})
const emptyTableDataText = computed(() => {
return isSearchEmptyList.value
? t('common_module.search_empty_data')
: t('personal_space_module.knowledge_module.empty_knowledge_document_list')
})
watch(
() => isKnowledgeDocumentFinish.value,
(newVal) => {
newVal ? pauseIntervalFn() : resumeIntervalFn()
},
{ deep: true },
)
const { pause: pauseIntervalFn, resume: resumeIntervalFn } = useIntervalFn(
async () => {
if (knowledgeDocumentTableLoading.value) return
const res = await fetchGetKnowledgeDocumentList<KnowledgeDocumentItem[]>(
currentKnowledgeId.value,
searchDocumentInputValue.value,
)
if (res.code === 0) {
knowledgeDocumentList.value = res.data
isSearchEmptyList.value = !!searchDocumentInputValue.value && res.data.length === 0
}
},
2000,
{ immediateCallback: false, immediate: false },
)
onMounted(async () => {
if (!router.currentRoute.value.params.id) {
window.$message.warning(t('personal_space_module.knowledge_module.not_find_knowledge_message'))
router.replace({ name: 'PersonalSpaceKnowledge' })
return
}
currentKnowledgeId.value = Number(router.currentRoute.value.params.id)
await handleGetKnowledgeDetail()
await handleGetKnowledgeDocumentList()
})
async function handleGetKnowledgeDetail() {
const res = await fetchGetKnowledgeDetail<KnowledgeFormDataInterface>(currentKnowledgeId.value)
if (res.code === 0) {
currentKnowledgeData.value = res.data
}
}
async function handleGetKnowledgeDocumentList() {
knowledgeDocumentTableLoading.value = true
const res = await fetchGetKnowledgeDocumentList<KnowledgeDocumentItem[]>(
currentKnowledgeId.value,
searchDocumentInputValue.value,
)
if (res.code === 0) {
knowledgeDocumentList.value = res.data
knowledgeDocumentTableLoading.value = false
checkedKdIdList.value = []
isSearchEmptyList.value = !!searchDocumentInputValue.value && res.data.length === 0
}
}
function handleClickKnowledgeDocumentTableAction(actionType: string, knowledgeDocumentItem: KnowledgeDocumentItem) {
switch (actionType) {
case 'view':
handleToViewDocumentDetail(knowledgeDocumentItem.kdId)
break
case 'delete':
handleDeleteKnowledgeDocument(knowledgeDocumentItem.kdId)
break
}
}
function handleToViewDocumentDetail(kdId: number) {
router.push({
name: 'KnowledgeDocumentDetail',
params: { kdId },
})
}
async function handleDeleteKnowledgeDocument(kdId: number) {
const res = await fetchDelKnowledgeDocument(currentKnowledgeId.value, kdId)
if (res.code === 0) {
window.$message.success(t('common_module.delete_success_message'))
await handleGetKnowledgeDocumentList()
}
}
function handleShowEditKnowledgeModal() {
isShowEditKnowledgeModal.value = true
}
async function handleUpdateKnowledgeInfo(knowledgeData: KnowledgeFormDataInterface) {
updateKnowledgeInfoBtnLoading.value = true
const res = await fetchUpdateKnowledgeInfo<KnowledgeFormDataInterface>({
id: currentKnowledgeId.value,
knowledgeName: knowledgeData.knowledgeName,
desc: knowledgeData.desc,
}).finally(() => (updateKnowledgeInfoBtnLoading.value = false))
if (res.code === 0) {
window.$message.success(t('common_module.save_success_message'))
currentKnowledgeData.value = res.data
isShowEditKnowledgeModal.value = false
}
}
function handleUpdateCheckedKdId(kdIdList: number[]) {
checkedKdIdList.value = kdIdList
}
async function handleBatchDelDocument() {
const res = await fetchBatchDelKnowledgeDocument(currentKnowledgeId.value, checkedKdIdList.value)
if (res.code === 0) {
window.$message.success(t('common_module.delete_success_message'))
await handleGetKnowledgeDocumentList()
}
}
function handleToUploadDocument() {
router.push({
name: 'UploadKnowledge',
params: { id: router.currentRoute.value.params.id, type: 'text-local-document' },
})
}
function handleBackKnowledgeList() {
router.replace({ name: 'PersonalSpaceKnowledge' })
}
</script>
<template>
<div class="flex h-full w-full flex-col overflow-hidden">
<div class="my-3 flex h-8 items-center">
<Left theme="outline" size="20" fill="#333" class="cursor-pointer" @click="handleBackKnowledgeList" />
<div class="flex items-center">
<span class="text-font-color ml-[2px] h-8 select-none text-lg leading-[30px]">
{{ currentKnowledgeData.knowledgeName }}
</span>
<i
class="iconfont icon-edit text-gray-font-color ml-[5px] cursor-pointer text-[15px] leading-none"
@click="handleShowEditKnowledgeModal"
/>
</div>
</div>
<div class="mb-[18px] flex justify-end">
<NInput
v-model:value="searchDocumentInputValue"
:placeholder="t('personal_space_module.knowledge_module.search_knowledge_document_placeholder')"
class="w-[214px]!"
@keyup.enter="handleGetKnowledgeDocumentList"
>
<template #suffix>
<Search
theme="outline"
size="16"
fill="#999"
class="cursor-pointer"
@click="handleGetKnowledgeDocumentList"
/>
</template>
</NInput>
<NButton class="ml-[14px]!" type="info" :disabled="isDisabledBatchDelBtn" @click="handleBatchDelDocument">
{{ t('personal_space_module.knowledge_module.batch_delete_knowledge_document_btn_text') }}
</NButton>
<NButton type="primary" :bordered="false" class="ml-[14px]!" @click="handleToUploadDocument">
{{ t('personal_space_module.knowledge_module.upload_knowledge_document_btn_text') }}
</NButton>
</div>
<div ref="pageContentWrapRef" class="flex-1 overflow-hidden rounded-[20px] bg-white p-6">
<div :style="{ height: tableContentY + 48 + 'px' }">
<NDataTable
:loading="knowledgeDocumentTableLoading"
:columns="knowledgeDocumentColumn"
:bordered="false"
:single-line="false"
:data="knowledgeDocumentList"
:row-key="(row: KnowledgeDocumentItem) => row.kdId"
:max-height="tableContentY"
:scroll-x="1526"
:checked-row-keys="checkedKdIdList"
@update:checked-row-keys="handleUpdateCheckedKdId"
>
<template #empty>
<div :style="{ height: tableContentY + 'px' }" class="flex items-center justify-center">
<div class="flex flex-col items-center justify-center">
<div
class="mb-5 h-[68px] w-[68px] bg-contain"
:class="
isSearchEmptyList
? `bg-[url('@/assets/images/search-empty-list.png')]`
: `bg-[url('@/assets/images/empty-document-list.png')]`
"
/>
<p class="select-none text-[#84868c]">{{ emptyTableDataText }}</p>
</div>
</div>
</template>
</NDataTable>
</div>
</div>
<EditKnowledgeModal
v-model:is-show-modal="isShowEditKnowledgeModal"
:btn-loading="updateKnowledgeInfoBtnLoading"
:knowledge-data="currentKnowledgeData"
@confirm="handleUpdateKnowledgeInfo"
/>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { Search } from '@icon-park/vue-next'
import { createKnowledgeColumn } from './columns.tsx'
import { KnowledgeItem } from './knowledge-type.ts'
import CustomPagination, { PaginationInfo } from '@/components/custom-pagination/custom-pagination.vue'
import useTableScrollY from '@/composables/useTableScrollY.ts'
import { fetchDelKnowledgeById, fetchEnableKnowledgeInfo, fetchGetKnowledgeList } from '@/apis/knowledge.ts'
const { t } = useI18n()
const router = useRouter()
const { pageContentWrapRef, tableContentY } = useTableScrollY(48 + 32 + 16 + 16 + 28)
const knowledgeColumns = createKnowledgeColumn(handleClickKnowledgeTableAction)
const pagingInfo = ref<PaginationInfo>({
pageNo: 1,
pageSize: 10,
totalPages: 0,
totalRows: 0,
})
const knowledgeListTableLoading = ref(false)
const knowledgeList = ref<KnowledgeItem[]>([])
const searchKnowledgeInputValue = ref('')
const isSearchEmptyList = ref(false)
const isLoadingPagination = computed(() => {
return tableContentY.value > 0
})
const emptyTableDataText = computed(() => {
return isSearchEmptyList.value ? t('common_module.search_empty_data') : t('common_module.empty_data')
})
onMounted(async () => {
await handleGetKnowledgeList()
})
async function handleGetKnowledgeList() {
knowledgeListTableLoading.value = true
const res = await fetchGetKnowledgeList<KnowledgeItem[]>('', searchKnowledgeInputValue.value, {
pagingInfo: pagingInfo.value,
})
if (res.code === 0) {
knowledgeList.value = res.data
pagingInfo.value = res.pagingInfo as PaginationInfo
knowledgeListTableLoading.value = false
isSearchEmptyList.value = !!searchKnowledgeInputValue.value && pagingInfo.value.totalRows === 0
}
}
function handleClickKnowledgeTableAction(actionType: string, knowledgeId: number, knowledgeItem?: KnowledgeItem) {
switch (actionType) {
case 'updateOpen':
handleUpdateKnowledgeOpen(knowledgeId, knowledgeItem!)
break
case 'view':
handleViewDocumentList(knowledgeId)
break
case 'delete':
handleDeleteKnowledge(knowledgeId)
break
}
}
async function handleUpdateKnowledgeOpen(knowledgeId: number, knowledgeItem: KnowledgeItem) {
const isOpen = knowledgeItem.isOpen === 'Y' ? 'N' : 'Y'
const res = await fetchEnableKnowledgeInfo(knowledgeId, isOpen)
if (res.code === 0) {
knowledgeItem.isOpen = isOpen
await handleGetKnowledgeList()
}
}
function handleViewDocumentList(knowledgeId: number) {
router.push({
name: 'KnowledgeDocument',
params: { id: knowledgeId },
})
}
async function handleDeleteKnowledge(knowledgeId: number) {
const res = await fetchDelKnowledgeById(knowledgeId)
if (res.code === 0) {
window.$message.success(t('common_module.delete_success_message'))
await handleGetKnowledgeList()
}
}
function handleSearchKnowledge() {
pagingInfo.value.pageNo = 1
handleGetKnowledgeList()
}
async function handleGetKnowledgeListUpdatePageNo(pageNo: number) {
pagingInfo.value.pageNo = pageNo
await handleGetKnowledgeList()
}
async function handleGetKnowledgeListUpdatePageSize(pageSize: number) {
pagingInfo.value.pageNo = 1
pagingInfo.value.pageSize = pageSize
await handleGetKnowledgeList()
}
</script>
<template>
<div ref="pageContentWrapRef" class="h-full">
<div class="mb-4 flex justify-end">
<NInput
v-model:value="searchKnowledgeInputValue"
:placeholder="t('personal_space_module.knowledge_module.search_knowledge_placeholder')"
class="w-[256px]!"
@keyup.enter="handleSearchKnowledge"
>
<template #suffix>
<Search theme="outline" size="16" fill="#999" class="cursor-pointer" @click="handleSearchKnowledge" />
</template>
</NInput>
</div>
<div class="mb-4" :style="{ height: tableContentY + 48 + 'px' }">
<NDataTable
:loading="knowledgeListTableLoading"
:bordered="true"
:bottom-bordered="true"
:single-line="false"
:data="knowledgeList"
:columns="knowledgeColumns"
:max-height="tableContentY"
:scroll-x="1100"
>
<template #empty>
<div :style="{ height: tableContentY + 'px' }" class="flex items-center justify-center">
<div class="flex flex-col items-center justify-center">
<div
class="mb-5 h-[68px] w-[68px] bg-contain"
:class="
isSearchEmptyList
? `bg-[url('@/assets/images/search-empty-list.png')]`
: `bg-[url('@/assets/images/empty-list.png')]`
"
/>
<p class="text-gray-font-color select-none">{{ emptyTableDataText }}</p>
</div>
</div>
</template>
</NDataTable>
</div>
<footer v-show="isLoadingPagination" class="flex justify-end">
<CustomPagination
:paging-info="pagingInfo"
@update-page-no="handleGetKnowledgeListUpdatePageNo"
@update-page-size="handleGetKnowledgeListUpdatePageSize"
/>
</footer>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { Left } from '@icon-park/vue-next'
import UploadLocalDocument from './upload-local-document/index.vue'
import { fetchGetKnowledgeDetail, fetchUpdateKnowledgeInfo } from '@/apis/knowledge'
import EditKnowledgeModal, { KnowledgeFormDataInterface } from '../components/edit-knowledge-modal.vue'
const { t } = useI18n()
const router = useRouter()
const currentKnowledgeId = ref(0)
const uploadKnowledgeType = ref('')
const isShowEditKnowledgeModal = ref(false)
const updateKnowledgeInfoBtnLoading = ref(false)
const currentKnowledgeData = ref<KnowledgeFormDataInterface>({
knowledgeName: '',
desc: '',
})
onMounted(async () => {
if (!router.currentRoute.value.params.id) {
window.$message.warning(t('personal_space_module.knowledge_module.not_find_knowledge_message'))
router.replace({ name: 'PersonalSpaceKnowledge' })
return
}
uploadKnowledgeType.value = router.currentRoute.value.params.type as string
currentKnowledgeId.value = Number(router.currentRoute.value.params.id)
await handleGetKnowledgeDetail()
})
async function handleGetKnowledgeDetail() {
const res = await fetchGetKnowledgeDetail<KnowledgeFormDataInterface>(currentKnowledgeId.value)
currentKnowledgeData.value = res.data
}
function handleBackKnowledgeList() {
router.back()
}
function handleShowEditKnowledgeModal() {
isShowEditKnowledgeModal.value = true
}
async function handleUpdateKnowledgeInfo(knowledgeData: KnowledgeFormDataInterface) {
updateKnowledgeInfoBtnLoading.value = true
const res = await fetchUpdateKnowledgeInfo<KnowledgeFormDataInterface>({
id: currentKnowledgeId.value,
knowledgeName: knowledgeData.knowledgeName,
desc: knowledgeData.desc,
}).finally(() => (updateKnowledgeInfoBtnLoading.value = false))
if (res.code === 0) {
window.$message.success(t('common_module.save_success_message'))
currentKnowledgeData.value = res.data
isShowEditKnowledgeModal.value = false
}
}
</script>
<template>
<div class="flex h-full w-full flex-col overflow-hidden">
<div class="my-3 flex h-8 items-center">
<Left theme="outline" size="20" fill="#333" class="cursor-pointer" @click="handleBackKnowledgeList" />
<div class="flex items-center">
<span class="text-font-color ml-[2px] h-8 select-none text-lg leading-[30px]">
{{ currentKnowledgeData.knowledgeName }}
</span>
<i
class="iconfont icon-edit text-gray-font-color ml-[5px] cursor-pointer text-[15px] leading-none"
@click="handleShowEditKnowledgeModal"
/>
</div>
</div>
<UploadLocalDocument v-if="uploadKnowledgeType === 'text-local-document'" />
<EditKnowledgeModal
v-model:is-show-modal="isShowEditKnowledgeModal"
:btn-loading="updateKnowledgeInfoBtnLoading"
:knowledge-data="currentKnowledgeData"
@confirm="handleUpdateKnowledgeInfo"
/>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useIntervalFn } from '@vueuse/core'
import { fetchGetKnowledgeDocumentListByKdIds } from '@/apis/knowledge'
interface Props {
kdIds: number[]
}
interface Emit {
(e: 'confirm'): void
}
enum TrainStatus {
UNOPENED = 'Unopened',
LINE = 'Line',
TRAINING = 'Training',
COMPLETE = 'Complete',
FAIL = 'Fail',
}
interface TrainFileItem {
kdId: number
documentName: string
documentUrl: string
trainStatus: TrainStatus
knowledgeId: string
documentSize: string
}
const { t } = useI18n()
const props = defineProps<Props>()
const emit = defineEmits<Emit>()
const trainFileList = ref<TrainFileItem[]>([])
onMounted(() => {
!!props.kdIds.length && handleGetKnowledgeDocumentList()
})
const trainFileText = computed(() => (status: TrainStatus) => {
switch (status) {
case TrainStatus.COMPLETE:
return 'personal_space_module.knowledge_module.upload_document_module.process_success'
case TrainStatus.FAIL:
return 'personal_space_module.knowledge_module.upload_document_module.process_fail'
default:
return 'personal_space_module.knowledge_module.upload_document_module.processed'
}
})
const trainFileFontColor = computed(() => (status: TrainStatus) => {
switch (status) {
case TrainStatus.COMPLETE:
return '#0B7DFF'
case TrainStatus.FAIL:
return '#F25744'
default:
return '#333333'
}
})
const trainFileIcon = computed(() => (documentUrl: string) => {
const type = documentUrl.substring(documentUrl.lastIndexOf('.') + 1)
return `https://gsst-poe-sit.gz.bcebos.com/icon/${type}.png`
})
const isTrainFileListFinish = computed(() => {
return trainFileList.value.every((trainFileItem) => {
return [TrainStatus.FAIL, TrainStatus.COMPLETE].includes(trainFileItem.trainStatus)
})
})
watch(
() => isTrainFileListFinish.value,
(newVal) => {
newVal ? pauseIntervalFn() : resumeIntervalFn()
},
{ deep: true },
)
const { pause: pauseIntervalFn, resume: resumeIntervalFn } = useIntervalFn(
async () => {
handleGetKnowledgeDocumentList()
},
2000,
{ immediateCallback: false, immediate: false },
)
async function handleGetKnowledgeDocumentList() {
const res = await fetchGetKnowledgeDocumentListByKdIds<TrainFileItem[]>(props.kdIds)
if (res.code === 0) {
trainFileList.value = res.data
}
}
function handleConfirm() {
emit('confirm')
}
</script>
<template>
<ul class="grid gap-[18px]">
<li
v-for="trainFileItem in trainFileList"
:key="trainFileItem.kdId"
class="border-inactive-border-color relative h-[90px] select-none overflow-hidden rounded-[10px] border bg-white p-6"
>
<div class="flex h-full items-center justify-between">
<div class="flex items-center">
<img :src="trainFileIcon(trainFileItem.documentUrl)" class="mr-[15px] h-9 w-9" />
<div class="flex h-9 flex-col justify-between">
<span class="text-font-color line-clamp-1 break-all text-sm leading-[14px]">
{{ trainFileItem.documentName }}
</span>
<span class="text-gray-font-color line-clamp-1 break-all text-[13px] leading-[13px]">
{{ trainFileItem.documentSize }}
</span>
</div>
</div>
<span :style="{ color: trainFileFontColor(trainFileItem.trainStatus) }">
{{ t(trainFileText(trainFileItem.trainStatus)) }}
</span>
</div>
<div
v-show="![TrainStatus.COMPLETE, TrainStatus.FAIL].includes(trainFileItem.trainStatus)"
class="animate-training absolute left-0 top-[-1px] h-[90px] w-full bg-gradient-to-r from-transparent via-[#9EA0FF] to-transparent"
/>
</li>
</ul>
<div class="mt-14 flex items-center justify-end">
<span class="text-gray-font-color mr-2.5 select-none text-xs">
{{ t('personal_space_module.knowledge_module.upload_document_module.data_process_tip_message') }}
</span>
<NButton type="primary" :bordered="false" @click="handleConfirm">
{{ t('common_module.confirm_btn_text') }}
</NButton>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import CustomIcon from '@/components/custom-icon/custom-icon.vue'
import UploadFile from './upload-file.vue'
import SegmentSetting from './segment-setting.vue'
import DataProcess from './data-process.vue'
import { fetchTrainKnowledge } from '@/apis/knowledge'
interface StepItem {
stepId: number
title: string
}
const { t } = useI18n()
const router = useRouter()
const currentKnowledgeId = ref(0)
const currentStepId = ref(1) // 当前步骤Id
const currentKdIdList = ref<number[]>([])
const stepList = ref<StepItem[]>([
{
stepId: 1,
title: 'common_module.upload',
},
{
stepId: 2,
title: 'personal_space_module.knowledge_module.upload_document_module.segment_setting',
},
{
stepId: 3,
title: 'personal_space_module.knowledge_module.upload_document_module.data_process',
},
])
onMounted(() => {
if (!router.currentRoute.value.params.id) {
window.$message.warning('知识库不存在')
router.replace({ name: 'PersonalSpaceKnowledge' })
return
}
currentKnowledgeId.value = Number(router.currentRoute.value.params.id)
})
function handleToUploadFile() {
currentStepId.value = 1
}
function handleToSegmentSetting(kdId: number[]) {
currentStepId.value = 2
currentKdIdList.value = kdId
}
async function handleToDataProcess() {
const res = await fetchTrainKnowledge({
knowledgeInfoId: currentKnowledgeId.value,
kdIds: currentKdIdList.value,
segmentationConfig: {
segmentationType: 'DEFAULT',
chunkSize: 300,
scrapSize: 80,
repetitionRate: 5,
relationInfo: [''],
regex: '',
punctuations: [''],
},
})
if (res.code === 0) {
currentStepId.value = 3
}
}
function handleToKnowledgeList() {
router.replace({ name: 'KnowledgeDocument', params: { id: currentKnowledgeId.value } })
}
</script>
<template>
<div class="flex flex-1 flex-col overflow-hidden">
<ul class="mx-auto my-10 flex w-[870px] shrink-0 duration-500 ease-in">
<li
v-for="(stepItem, index) in stepList"
:key="stepItem.stepId"
class="relative flex flex-1 shrink-0 flex-col items-center justify-center"
>
<div
v-show="stepItem.stepId !== 1"
class="absolute left-0 top-[24px] w-[calc((100%-48px)/2)] border-t"
:class="[currentStepId >= stepItem.stepId ? 'border-[#9EA3FF]' : 'border-inactive-border-color']"
/>
<div class="flex flex-col items-center justify-center">
<div
class="relative mb-3 flex h-12 w-12 items-center justify-center rounded-full border text-lg"
:class="{
'border-[#6F77FF] bg-[#6F77FF]': stepItem.stepId === currentStepId,
'border-inactive-border-color text-gray-font-color': stepItem.stepId > currentStepId,
'bg-[#9ea3ff]': stepItem.stepId < currentStepId,
}"
>
<span
v-show="currentStepId <= stepItem.stepId"
class="select-none text-xl"
:class="stepItem.stepId === currentStepId ? 'text-white' : 'text-gray-font-color'"
>
{{ index + 1 }}
</span>
<CustomIcon
v-show="currentStepId > stepItem.stepId"
icon="mingcute:check-fill"
class="text-[22px] text-white"
/>
</div>
<span
class="text-base"
:class="[currentStepId === stepItem.stepId ? 'text-theme-color' : 'text-gray-font-color']"
>
{{ t(stepItem.title) }}
</span>
</div>
<div
v-show="index < stepList.length - 1"
class="absolute right-0 top-[24px] w-[calc((100%-48px)/2)] border-t"
:class="[currentStepId > stepItem.stepId ? 'border-[#9EA3FF]' : 'border-inactive-border-color']"
/>
</li>
</ul>
<div class="mx-10 flex-1 overflow-hidden">
<NScrollbar class="max-h-full! px-10">
<div v-show="currentStepId === 1">
<UploadFile @next="handleToSegmentSetting" />
</div>
<div v-show="currentStepId === 2">
<SegmentSetting @prev="handleToUploadFile" @next="handleToDataProcess" />
</div>
<div v-if="currentStepId === 3">
<DataProcess :kd-ids="currentKdIdList" @confirm="handleToKnowledgeList" />
</div>
</NScrollbar>
</div>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
interface Emit {
(e: 'prev'): void
(e: 'next'): void
}
const { t } = useI18n()
const emit = defineEmits<Emit>()
function handlePrev() {
emit('prev')
}
function handleNext() {
emit('next')
}
</script>
<template>
<div>
<div>
<div
class="border-theme-color bg-active-color flex h-[90px] w-full cursor-pointer flex-col justify-between rounded-[10px] border px-6 py-5"
>
<span class="text-font-color">
{{ t('personal_space_module.knowledge_module.upload_document_module.default_segment_setting_title') }}
</span>
<span class="text-gray-font-color text-[13px]">
{{ t('personal_space_module.knowledge_module.upload_document_module.default_segment_setting_desc') }}
</span>
</div>
</div>
<div class="mt-14 flex justify-end">
<NButton @click="handlePrev">{{ t('common_module.prev_btn_text') }}</NButton>
<NButton type="primary" class="ml-5!" :bordered="false" @click="handleNext">
{{ t('common_module.next_btn_text') }}
</NButton>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { UploadFileInfo } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import { UploadOne } from '@icon-park/vue-next'
import CustomIcon from '@/components/custom-icon/custom-icon.vue'
import { fetchUploadKnowledgeDocument } from '@/apis/knowledge'
interface Emit {
(e: 'next', value: number[]): void
}
enum FileItemStatus {
PENDING = 'pending',
UPLOADING = 'uploading',
FINISHED = 'finished',
REMOVED = 'removed',
ERROR = 'error',
}
interface FileItem {
id: string
name: string
size: number
status: FileItemStatus
kdId: number
type?: string
}
const { t } = useI18n()
const emit = defineEmits<Emit>()
const uploadFileList = ref<FileItem[]>([])
const uploadFileIcon = (type: string) => {
return `https://gsst-poe-sit.gz.bcebos.com/icon/${type}.png`
}
const isDisabledNextBtn = computed(() => {
return (
uploadFileList.value.length > 0 && uploadFileList.value.every((item) => item.status === FileItemStatus.FINISHED)
)
})
const uploadFileSize = computed(() => (fileSize: number) => {
if (fileSize == 0) return '0B'
const binarySize = 1024
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
const unit = Math.floor(Math.log(fileSize) / Math.log(binarySize))
return (fileSize / Math.pow(binarySize, unit)).toPrecision(3) + ' ' + sizes[unit]
})
// 上传文件前限制
function handleLimitUpload(data: { file: UploadFileInfo }) {
const allowTypeList = ['md', 'doc', 'docx', 'pdf', 'txt']
if (data.file.file && !allowTypeList.includes(data.file.file?.name.split('.')[1].toLowerCase())) {
window.$message.error(
t('personal_space_module.knowledge_module.upload_document_module.upload_format_error_message'),
)
return false
}
if (data.file.file && data.file.file?.size === 0) {
window.$message.error(
t('personal_space_module.knowledge_module.upload_document_module.empty_document_content_message'),
)
const fileData = {
id: data.file.id,
name: data.file.name,
status: FileItemStatus.ERROR,
size: data.file.file?.size || 0,
type: data.file.file?.name.split('.')[1].toLowerCase(),
kdId: 0,
}
uploadFileList.value.push(fileData)
return false
}
if (data.file.file && data.file.file?.size > 5 * 1024 * 1024) {
window.$message.error(t('personal_space_module.knowledge_module.upload_document_module.upload_size_error_message'))
const fileData = {
id: data.file.id,
name: data.file.name,
status: FileItemStatus.ERROR,
size: data.file.file?.size || 0,
type: data.file.file?.name.split('.')[1].toLowerCase(),
kdId: 0,
}
uploadFileList.value.push(fileData)
return false
}
return true
}
// 上传文件
async function handleUpload(file: any) {
if (file.event) {
const formData = new FormData()
formData.append('documentFiles', file.file.file)
const fileData = {
id: file.file.id,
name: file.file.name,
status: FileItemStatus.UPLOADING,
size: file.file?.file?.size || 0,
type: file.file?.name.split('.')[1].toLowerCase(),
kdId: 0,
}
uploadFileList.value.push(fileData)
fetchUploadKnowledgeDocument<{ kdId: number }[]>(formData)
.then((res) => {
if (res.code === 0) {
uploadFileList.value.forEach((fileItem) => {
if (fileItem.id === file.file.id) {
fileItem.status = FileItemStatus.FINISHED
fileItem.kdId = res.data?.[0]?.kdId
}
})
}
})
.catch(() => {
uploadFileList.value.forEach((fileItem) => {
if (fileItem.id === file.file.id) {
fileItem.status = FileItemStatus.ERROR
}
})
})
}
}
function handleRemoveFile(id: string) {
uploadFileList.value = uploadFileList.value.filter((item) => item.id !== id)
}
function handleNextStep() {
const kdIds = uploadFileList.value.map((item) => item.kdId)
emit('next', kdIds)
}
</script>
<template>
<div class="w-full">
<NUpload
multiple
directory-dnd
:show-file-list="false"
:disabled="uploadFileList.length >= 5"
accept=".doc, .pdf, .docx, .txt, .md"
@before-upload="handleLimitUpload"
@change="handleUpload"
>
<NUploadDragger>
<div class="mb-3 flex justify-center">
<UploadOne theme="outline" size="36" fill="#333" />
</div>
<NText class="text-base">
{{ t('personal_space_module.knowledge_module.upload_document_module.upload_action_tip_message') }}
</NText>
<p class="text-gray-font-color mt-3 text-sm">
{{ t('personal_space_module.knowledge_module.upload_document_module.upload_limit_tip_message') }}
</p>
</NUploadDragger>
</NUpload>
<div class="mt-[18px]">
<ul class="grid grid-cols-2 gap-4">
<li
v-for="uploadFileItem in uploadFileList"
:key="uploadFileItem.id"
class="group relative h-[92px] w-full overflow-hidden rounded-[10px] border bg-white p-6"
:class="uploadFileItem.status === 'error' ? 'border-error-font-color' : 'border-transparent'"
>
<div class="flex items-center justify-between">
<div class="flex items-center">
<img :src="uploadFileIcon(uploadFileItem.type!)" class="h-9 w-9" />
<div class="mx-4 flex flex-col">
<NPopover trigger="hover">
<template #trigger>
<span class="text-font-color mb-2 line-clamp-1 select-none break-all text-base leading-4">
{{ uploadFileItem.name }}
</span>
</template>
<span>{{ uploadFileItem.name }}</span>
</NPopover>
<div
v-if="uploadFileItem.status === FileItemStatus.FINISHED"
class="text-gray-font-color line-clamp-1 select-none break-all"
>
<span class="inline-block group-hover:hidden">{{ uploadFileSize(uploadFileItem.size) }}</span>
<span class="hidden group-hover:inline-block">{{ t('common_module.upload_success_message') }}</span>
</div>
<div
v-else-if="uploadFileItem.status === FileItemStatus.UPLOADING"
class="text-gray-font-color line-clamp-1 select-none break-all"
>
<span>{{ t('common_module.uploading') }}</span>
</div>
<span v-else class="text-error-font-color line-clamp-1 select-none break-all">
{{ t('personal_space_module.knowledge_module.upload_document_module.upload_error_message') }}
</span>
</div>
</div>
<div
v-show="[FileItemStatus.FINISHED, FileItemStatus.ERROR].includes(uploadFileItem.status)"
class="hidden group-hover:block"
>
<NPopover trigger="hover">
<template #trigger>
<CustomIcon
icon="material-symbols:close"
class="cursor-pointer text-base outline-none hover:opacity-80"
:class="
uploadFileItem.status === FileItemStatus.ERROR ? 'text-error-font-color' : 'text-font-color'
"
@click="handleRemoveFile(uploadFileItem.id)"
/>
</template>
<span>{{ t('common_module.delete') }}</span>
</NPopover>
</div>
</div>
<div
v-show="[FileItemStatus.UPLOADING].includes(uploadFileItem.status)"
class="animate-training absolute left-0 top-[-1px] h-[92px] w-full bg-gradient-to-r from-transparent via-[#9EA0FF] to-transparent"
/>
</li>
</ul>
</div>
<div class="mt-14 flex justify-end">
<NButton type="primary" :disabled="!isDisabledNextBtn" :bordered="false" @click="handleNextStep">
{{ t('common_module.next_btn_text') }}
</NButton>
</div>
</div>
</template>
......@@ -2,11 +2,11 @@
import { h, readonly, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { Plus, ApplicationTwo } from '@icon-park/vue-next'
// import CreateKnowledgeModal, {
// KnowledgeFormDataInterface,
// } from './personal-knowledge/components/create-knowledge-modal.vue'
// import { fetchCreateKnowledge } from '@/apis/knowledge'
import { Plus, ApplicationTwo, NotebookOne } from '@icon-park/vue-next'
import CreateKnowledgeModal, {
KnowledgeFormDataInterface,
} from './personal-knowledge/components/create-knowledge-modal.vue'
import { fetchCreateKnowledge } from '@/apis/knowledge'
const { t } = useI18n()
......@@ -20,10 +20,10 @@ const personalSpaceModuleList = [
routeName: 'PersonalSpaceApp',
label: 'common_module.agent',
},
// {
// routeName: 'PersonalSpaceKnowledge',
// label: 'common_module.knowledge',
// },
{
routeName: 'PersonalSpaceKnowledge',
label: 'common_module.knowledge',
},
]
const addPersonalSpaceOptions = readonly([
......@@ -33,15 +33,15 @@ const addPersonalSpaceOptions = readonly([
icon: () => h(ApplicationTwo, { theme: 'outline', size: 16, fill: '#333' }),
},
// {
// label: () => h('span', {}, t('common_module.knowledge')),
// key: 'addKnowledge',
// icon: () => h(NotebookOne, { theme: 'outline', size: 16, fill: '#333' }),
// },
{
label: () => h('span', {}, t('common_module.knowledge')),
key: 'addKnowledge',
icon: () => h(NotebookOne, { theme: 'outline', size: 16, fill: '#333' }),
},
])
const showCreateKnowledgeModal = ref(false)
// const createKnowledgeBtnLoading = ref(false)
const createKnowledgeBtnLoading = ref(false)
watch(
() => currentRoute.fullPath,
......@@ -65,26 +65,26 @@ function handleSelectAddType(type: string) {
}
}
// async function handleCreateKnowledgeNextStep(createKnowledgeData: KnowledgeFormDataInterface) {
// createKnowledgeBtnLoading.value = true
// const res = await fetchCreateKnowledge<{ id: number }>({
// knowledgeName: createKnowledgeData.knowledgeName,
// desc: createKnowledgeData.knowledgeDesc,
// })
// if (res.code === 0) {
// router.push({
// name: 'UploadKnowledge',
// params: {
// id: res.data.id,
// type: createKnowledgeData.knowledgeType + '-' + createKnowledgeData.knowledgeImportType,
// },
// })
// showCreateKnowledgeModal.value = false
// createKnowledgeBtnLoading.value = false
// }
// }
async function handleCreateKnowledgeNextStep(createKnowledgeData: KnowledgeFormDataInterface) {
createKnowledgeBtnLoading.value = true
const res = await fetchCreateKnowledge<{ id: number }>({
knowledgeName: createKnowledgeData.knowledgeName,
desc: createKnowledgeData.knowledgeDesc,
})
if (res.code === 0) {
router.push({
name: 'UploadKnowledge',
params: {
id: res.data.id,
type: createKnowledgeData.knowledgeType + '-' + createKnowledgeData.knowledgeImportType,
},
})
showCreateKnowledgeModal.value = false
createKnowledgeBtnLoading.value = false
}
}
</script>
<template>
......@@ -131,11 +131,11 @@ function handleSelectAddType(type: string) {
</RouterView>
</div>
<!-- <CreateKnowledgeModal
<CreateKnowledgeModal
v-model:is-show-modal="showCreateKnowledgeModal"
:btn-loading="createKnowledgeBtnLoading"
@confirm="handleCreateKnowledgeNextStep"
/> -->
/>
</div>
</template>
......
......@@ -7,5 +7,8 @@
{
"path": "./tsconfig.node.json"
}
]
],
"compilerOptions": {
"jsx": "preserve",
}
}
......@@ -6,6 +6,14 @@ declare interface Window {
$notification: import('naive-ui').NotificationProviderInst
}
declare namespace JSX {
interface IntrinsicElements {
div: DivElement
span: SpanElement
i: IElement
}
}
type Recordable<T = any> = Record<string, T>
declare type AnyObject = { [k: string]: any }
......@@ -22,6 +22,8 @@ export default defineConfig({
'font-color': '#333333',
'gray-font-color': '#999999',
'error-font-color': '#F25744',
'inactive-border-color': '#CCCCCC',
'background-color': '#F3F5F8',
},
height: {
navbar: '56px',
......@@ -33,15 +35,18 @@ export default defineConfig({
animation: {
keyframes: {
'card-reverse': `{ 0% { transform: rotateY(0deg); } 100% { transform: rotateY(1turn); } }`,
training: `{ 0% { transform: translateX(-100%) } 100% { transform: translateX(100%); } }`,
},
durations: {
'card-reverse': '1s',
training: '1.2s',
},
timingFns: {
'card-reverse': 'ease-in-out',
},
counts: {
'card-reverse': '1',
training: 'infinite',
},
},
},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment