Commit ed3948ed authored by nick zheng's avatar nick zheng

feat: 知识库支持问答知识库

parent f9bb6cc4
import { request } from '@/utils/request' import { request } from '@/utils/request'
import qs from 'qs'
/* 知识库部分开始 */ /* 知识库部分开始 */
...@@ -164,3 +165,86 @@ export function fetchOpenKnowledgeChunk<T>(payload: { kdId: number; chunkRelatio ...@@ -164,3 +165,86 @@ export function fetchOpenKnowledgeChunk<T>(payload: { kdId: number; chunkRelatio
return request.post<T>('/knowledgeRest/openKnowledgeChunk.json', payload) return request.post<T>('/knowledgeRest/openKnowledgeChunk.json', payload)
} }
/* 知识库文档部分结束 */ /* 知识库文档部分结束 */
/* 问答知识库文档片段开始 */
/**
* @query kdId 知识库文档Id
* @returns 获取问答知识库分片结构
*/
export function fetchQAKnowledgeChunkStruct<T>(kdId: number) {
return request.post<T>(`/qAKnowledgeRest/getKnowledgeStruct.json?kdId=${kdId}`)
}
/**
* @query query 模糊搜索
* @query kdId 知识库文档Id
* @params payload { page: number, pageSize: number }
* @returns 获取问答知识库分片列表
*/
export function fetchQAKnowledgeChunkList<T>(query: string, kdId: number, payload: object) {
return request.post<T>(`/qAKnowledgeRest/getQAKnowledgeChunks.json?query=${query}&kdId=${kdId + ''}`, payload)
}
/**
* @query kdId 知识库文档Id structId 结构Id structName 结构名称 isIndex 是否开启索引
* @returns 修改问答知识库表结构
*/
export function fetchUpdateQAKnowledgeStruct<T>(payload: object) {
return request.post<T>(
'/qAKnowledgeRest/updateKnowledgeChunk.json',
{},
{
params: payload,
paramsSerializer: function (params) {
return qs.stringify(params, { arrayFormat: 'repeat' })
},
},
)
}
/**
* @params { kdId 知识库文档Id chunkInfos 分片内容 chunkSort 文档顺序 }
* @returns 添加问答知识库分片
*/
export function fetchAddQAKnowledgeChunk<T>(payload: object) {
return request.post<T>('/qAKnowledgeRest/addKnowledgeChunk.json', payload)
}
/**
* @params { kdId 知识库文档Id chunkRelationId 分片Id chunkInfos 分片内容 chunkSort 文档顺序 }
* @returns 编辑问答知识库分片
*/
export function fetchEditQAKnowledgeChunk<T>(payload: object) {
return request.post<T>('/qAKnowledgeRest/batchUpdateKnowledgeChunkDoc.json', payload, {
timeout: 15000,
})
}
/**
* @payload kdId 知识库文档Id chunkRelationId 分片Id
* @returns 删除问答知识库分片
*/
export function fetchDeleteQAKnowledgeChunk<T>(payload: { kdId: number; chunkRelationId: string }) {
return request.post<T>('/qAKnowledgeRest/deleteKnowledgeChunk.json', payload)
}
/**
* @payload kdId 知识库文档Id chunkRelationId 分片Id isOpen 开启状态
* @returns 问答知识库分片开启
*/
export function fetchOpenQAKnowledgeChunk<T>(payload: { kdId: number; chunkRelationId: string; isOpen: 'Y' | 'N' }) {
return request.post<T>('/qAKnowledgeRest/openKnowledgeChunk.json', payload)
}
/**
* @query kdId 知识库文档Id chunkRelationIds 分片Id数组
* @returns 批量删除问答知识库分片
*/
export function fetchBatchDeleteQAKnowledgeChunks<T>(kdId: number, chunkRelationIds: string[]) {
return request.post<T>(
`/qAKnowledgeRest/batchDeleteKnowledgeChunks.json?kdId=${kdId}&chunkRelationIds=${chunkRelationIds}`,
)
}
/* 知识库文档部分结束 */
export enum KnowledgeTypeIcon {
Base = 'https://gsst-poe-sit.gz.bcebos.com/icon/knowledge-Base.svg',
QA = 'https://gsst-poe-sit.gz.bcebos.com/icon/knowledge-QA.svg',
}
...@@ -25,7 +25,7 @@ common_module: ...@@ -25,7 +25,7 @@ common_module:
available: 'Usable' available: 'Usable'
studying: 'Studying' studying: 'Studying'
config: 'Configure' config: 'Configure'
agent: 'Apply' agent: 'Agent'
knowledge: 'Knowledge base' knowledge: 'Knowledge base'
custom: 'Custom' custom: 'Custom'
char: 'Character' char: 'Character'
...@@ -34,7 +34,7 @@ common_module: ...@@ -34,7 +34,7 @@ common_module:
upload_success_message: 'Upload successfully' upload_success_message: 'Upload successfully'
empty_data: 'No data available' empty_data: 'No data available'
search_empty_data: 'No related content found' search_empty_data: 'No related content found'
create_agent_btn_text: 'Create an application' create_agent_btn_text: 'Create agent'
copy_success_message: 'Successful replication' copy_success_message: 'Successful replication'
delete_success_message: 'Successfully deleted' delete_success_message: 'Successfully deleted'
save_success_message: 'Save successfully' save_success_message: 'Save successfully'
...@@ -238,7 +238,7 @@ home_module: ...@@ -238,7 +238,7 @@ home_module:
personal_space_module: personal_space_module:
title: 'Personal space' title: 'Personal space'
create_btn_text: 'Newly build' create_btn_text: 'Create'
agent_module: agent_module:
agent_list_module: agent_list_module:
...@@ -465,9 +465,11 @@ personal_space_module: ...@@ -465,9 +465,11 @@ personal_space_module:
knowledge_type_rule: 'Please select the data set type' knowledge_type_rule: 'Please select the data set type'
knowledge_name_rule: 'Please enter a data set name' knowledge_name_rule: 'Please enter a data set name'
knowledge_import_type_rule: 'Select a data set import method' knowledge_import_type_rule: 'Select a data set import method'
knowledge_document_text_type: 'Text format' base_knowledge_type: 'Text format'
import_local_document_knowledge: 'Local document' import_local_document_knowledge: 'Local document'
import_local_document_knowledge_desc: 'Supports uploading local files in TXT, MD, PDF, DOC, DOCX formats' import_local_document_knowledge_desc: 'Supports uploading local files in TXT, MD, PDF, DOC, DOCX formats'
QA_knowledge_type: 'Q&A knowledge'
import_QA_local_document_knowledge_desc: 'Upload local documents in xls and xlsx formats, suitable for Q&A scenarios'
segment: 'Segment' segment: 'Segment'
auto_segment: 'Automatic segmentation' auto_segment: 'Automatic segmentation'
...@@ -489,6 +491,17 @@ personal_space_module: ...@@ -489,6 +491,17 @@ personal_space_module:
English_exclamation_mark: 'English exclamation mark' English_exclamation_mark: 'English exclamation mark'
ellipsis: 'Ellipsis' ellipsis: 'Ellipsis'
index: 'Index'
no: 'No'
add_QA: 'Add Q&A'
add_QA_data: 'Add Q&A data'
edit_QA_data: 'Edit Q&A data'
setting_index_desc: 'The index is an important key field of the knowledge base search slice. Select at least one field as the index.'
select_at_least_one_field_as_the_index: 'Select at least one field as the index'
setting_index: 'Setting Index'
field_name: 'Field name'
please_enter_the_content: 'Please enter the content'
upload_document_module: upload_document_module:
segment_setting: 'Segmented processing' segment_setting: 'Segmented processing'
data_process: 'Data processing' data_process: 'Data processing'
...@@ -514,6 +527,10 @@ personal_space_module: ...@@ -514,6 +527,10 @@ personal_space_module:
segment_overlap_word_proportion_tip: 'The ratio of the "number of overlapping characters" between the current slice and the previous slice to the set "Maximum length of slice". If there are incomplete sentences in the overlapping part, this slice deletes the sentence, the larger the proportion, the more overlapping characters in the adjacent slice, the smaller the proportion, the fewer overlapping characters, and the maximum bit 25' segment_overlap_word_proportion_tip: 'The ratio of the "number of overlapping characters" between the current slice and the previous slice to the set "Maximum length of slice". If there are incomplete sentences in the overlapping part, this slice deletes the sentence, the larger the proportion, the more overlapping characters in the adjacent slice, the smaller the proportion, the fewer overlapping characters, and the maximum bit 25'
please_input_segment_overlap_word_proportion: 'Please input segment overlap word proportion' please_input_segment_overlap_word_proportion: 'Please input segment overlap word proportion'
data_process_tip_message: 'Click confirm does not affect the data processing, after processing can be referenced' data_process_tip_message: 'Click confirm does not affect the data processing, after processing can be referenced'
upload_QA_limit_tip_message: 'Support XLSX\XLS, up to 5 files can be uploaded, each file does not exceed 10MB'
upload_QA_suggest_tip_message: 'It is recommended to use the "Q&A Template" to upload, otherwise the parsing will fail. Click below to download the template'
upload_QA_format_error_message: 'Only xls and xlsx files can be uploaded. Please upload them again'
download_QA_template: 'Download Q&A Template'
share_agent_module: share_agent_module:
please: 'Please first' please: 'Please first'
......
...@@ -463,9 +463,11 @@ personal_space_module: ...@@ -463,9 +463,11 @@ personal_space_module:
knowledge_type_rule: '请选择数据集类型' knowledge_type_rule: '请选择数据集类型'
knowledge_name_rule: '请输入数据集名称' knowledge_name_rule: '请输入数据集名称'
knowledge_import_type_rule: '请选择数据集导入方式' knowledge_import_type_rule: '请选择数据集导入方式'
knowledge_document_text_type: '文本格式' base_knowledge_type: '文本格式'
import_local_document_knowledge: '本地文档' import_local_document_knowledge: '本地文档'
import_local_document_knowledge_desc: '支持上传TXT、MD、PDF、DOC、DOCX格式的本地文件' import_local_document_knowledge_desc: '支持上传TXT、MD、PDF、DOC、DOCX格式的本地文件'
QA_knowledge_type: '问答知识库'
import_QA_local_document_knowledge_desc: '支持上传xls,xlsx格式的本地文档,适合问答场景使用'
segment: '分段' segment: '分段'
auto_segment: '自动分段' auto_segment: '自动分段'
...@@ -487,6 +489,17 @@ personal_space_module: ...@@ -487,6 +489,17 @@ personal_space_module:
English_exclamation_mark: '英文感叹号' English_exclamation_mark: '英文感叹号'
ellipsis: '省略号' ellipsis: '省略号'
index: '索引'
no: '序列号'
add_QA: '新建问答'
add_QA_data: '添加问答数据'
edit_QA_data: '编辑问答数据'
setting_index_desc: '索引是知识库检索切片的重要关键字段,至少选择1个字段作为索引'
select_at_least_one_field_as_the_index: '至少选择1个字段作为索引'
setting_index: '设置索引'
field_name: '字段名称'
please_enter_the_content: '请输入内容'
upload_document_module: upload_document_module:
segment_setting: '分段处理' segment_setting: '分段处理'
data_process: '数据处理' data_process: '数据处理'
...@@ -512,6 +525,10 @@ personal_space_module: ...@@ -512,6 +525,10 @@ personal_space_module:
segment_overlap_word_proportion_tip: '当前切片与前后切片的“重叠部分字符数”相较于设置的“切片最大长度”的比例,如果重叠部分存在不完整的句子,则此切片舍去该句,占比越大,相邻切片重叠字符越多,占比越少,重叠字符越少,最大值位25' segment_overlap_word_proportion_tip: '当前切片与前后切片的“重叠部分字符数”相较于设置的“切片最大长度”的比例,如果重叠部分存在不完整的句子,则此切片舍去该句,占比越大,相邻切片重叠字符越多,占比越少,重叠字符越少,最大值位25'
please_input_segment_overlap_word_proportion: '请输入分段重叠字数占比' please_input_segment_overlap_word_proportion: '请输入分段重叠字数占比'
data_process_tip_message: '点击确认不影响数据处理,处理完毕后可进行引用' data_process_tip_message: '点击确认不影响数据处理,处理完毕后可进行引用'
upload_QA_limit_tip_message: '支持XLSX\XLS,最多可上传5个文件,每个文件不超过10MB'
upload_QA_suggest_tip_message: '建议使用【问答模版】上传,否则会解析失败,点击下方下载模版'
upload_QA_format_error_message: '只能上传xls,xlsx格式文件,请重新上传'
download_QA_template: '下载问答模版'
share_agent_module: share_agent_module:
please: '请先' please: '请先'
...@@ -697,4 +714,3 @@ editor_module: ...@@ -697,4 +714,3 @@ editor_module:
center_align: '居中对齐' center_align: '居中对齐'
justify_right: '右对齐' justify_right: '右对齐'
align_both_ends: '两端对齐' align_both_ends: '两端对齐'
...@@ -463,9 +463,11 @@ personal_space_module: ...@@ -463,9 +463,11 @@ personal_space_module:
knowledge_type_rule: '請選擇數據集類型' knowledge_type_rule: '請選擇數據集類型'
knowledge_name_rule: '請輸入數據集名稱' knowledge_name_rule: '請輸入數據集名稱'
knowledge_import_type_rule: '請選擇數據集導入方式' knowledge_import_type_rule: '請選擇數據集導入方式'
knowledge_document_text_type: '文本格式' base_knowledge_type: '文本格式'
import_local_document_knowledge: '本地文檔' import_local_document_knowledge: '本地文檔'
import_local_document_knowledge_desc: '支持上傳TXT、MD、PDF、DOC、DOCX格式的本地文件' import_local_document_knowledge_desc: '支持上傳TXT、MD、PDF、DOC、DOCX格式的本地文件'
QA_knowledge_type: '問答知識庫'
import_QA_local_document_knowledge_desc: '支持上傳xls,xlsx格式的本地文檔,適合問答場景使用'
segment: '分段' segment: '分段'
auto_segment: '自動分段' auto_segment: '自動分段'
...@@ -487,6 +489,17 @@ personal_space_module: ...@@ -487,6 +489,17 @@ personal_space_module:
English_exclamation_mark: '英文感嘆號' English_exclamation_mark: '英文感嘆號'
ellipsis: '省略號' ellipsis: '省略號'
index: '索引'
no: '序列號'
add_QA: '新建問答'
add_QA_data: '添加問答數據'
edit_QA_data: '編輯問答數據'
setting_index_desc: '索引是知識庫檢索切片的重要關鍵字段,至少選擇1個字段作為索引'
select_at_least_one_field_as_the_index: '至少選擇1個字段作為索引'
setting_index: '設置索引'
field_name: '字段名稱'
please_enter_the_content: '請輸入內容'
upload_document_module: upload_document_module:
segment_setting: '分段處理' segment_setting: '分段處理'
data_process: '數據處理' data_process: '數據處理'
...@@ -512,6 +525,10 @@ personal_space_module: ...@@ -512,6 +525,10 @@ personal_space_module:
segment_overlap_word_proportion_tip: '當前切片與前後切片的“重疊部分字符數”相較於設置的“切片最大長度”的比例,如果重疊部分存在不完整的句子,則此切片捨去該句,佔比越大,相鄰切片重疊字符越多,佔比越少,重疊字符越少,最大值位25' segment_overlap_word_proportion_tip: '當前切片與前後切片的“重疊部分字符數”相較於設置的“切片最大長度”的比例,如果重疊部分存在不完整的句子,則此切片捨去該句,佔比越大,相鄰切片重疊字符越多,佔比越少,重疊字符越少,最大值位25'
please_input_segment_overlap_word_proportion: '請輸入分段重疊字數佔比' please_input_segment_overlap_word_proportion: '請輸入分段重疊字數佔比'
data_process_tip_message: '點擊確認不影響數據處理,處理完畢後可進行引用' data_process_tip_message: '點擊確認不影響數據處理,處理完畢後可進行引用'
upload_QA_limit_tip_message: '支持XLSX\XLS,最多可上傳5個文件,每個文件不超過10MB'
upload_QA_suggest_tip_message: '建議使用【問答模版】上傳,否則會解析失敗,點擊下方下載模版'
upload_QA_format_error_message: '只能上傳xls,xlsx格式文件,請重新上傳'
download_QA_template: '下載問答模版'
share_agent_module: share_agent_module:
please: '請先' please: '請先'
......
...@@ -67,7 +67,7 @@ export default [ ...@@ -67,7 +67,7 @@ export default [
title: 'router_title_module.knowledge_document_detail', title: 'router_title_module.knowledge_document_detail',
belong: 'PersonalSpace', belong: 'PersonalSpace',
}, },
component: () => import('@/views/personal-space/personal-knowledge/document-detail.vue'), component: () => import('@/views/personal-space/personal-knowledge/knowledge-detail/knowledge-detail.vue'),
}, },
{ {
......
export function downloadFile(fileUrl: string, name: string) {
const a = document.createElement('a')
a.download = name || 'file'
a.href = fileUrl
a.click()
a.remove()
}
...@@ -12,6 +12,7 @@ import { formatDateTime } from '@/utils/date-formatter' ...@@ -12,6 +12,7 @@ import { formatDateTime } from '@/utils/date-formatter'
import CreateKnowledgeModal, { import CreateKnowledgeModal, {
KnowledgeFormDataInterface, KnowledgeFormDataInterface,
} from '@/views/personal-space/personal-knowledge/components/create-knowledge-modal.vue' } from '@/views/personal-space/personal-knowledge/components/create-knowledge-modal.vue'
import { KnowledgeTypeIcon } from '@/enums/knowledge'
interface Props { interface Props {
isShowModal: boolean isShowModal: boolean
...@@ -202,7 +203,7 @@ async function handleCreateKnowledgeNextStep(createKnowledgeData: KnowledgeFormD ...@@ -202,7 +203,7 @@ async function handleCreateKnowledgeNextStep(createKnowledgeData: KnowledgeFormD
key-field="id" key-field="id"
items-style="padding-right: 12px;" items-style="padding-right: 12px;"
> >
<template #default="{ item }"> <template #default="{ item }: { item: KnowledgeItem }">
<div <div
:key="item.id" :key="item.id"
class="border-inactive-border-color hover:border-theme-color mb-4.5 flex h-[118px] w-full cursor-pointer items-center gap-4 rounded-[10px] border px-4 py-3.5" class="border-inactive-border-color hover:border-theme-color mb-4.5 flex h-[118px] w-full cursor-pointer items-center gap-4 rounded-[10px] border px-4 py-3.5"
...@@ -211,7 +212,8 @@ async function handleCreateKnowledgeNextStep(createKnowledgeData: KnowledgeFormD ...@@ -211,7 +212,8 @@ async function handleCreateKnowledgeNextStep(createKnowledgeData: KnowledgeFormD
<div class="flex flex-1 flex-col overflow-hidden"> <div class="flex flex-1 flex-col overflow-hidden">
<div class="flex flex-1"> <div class="flex flex-1">
<div <div
class="mt-[3px] h-9 w-9 flex-shrink-0 bg-[url(https://gsst-poe-sit.gz.bcebos.com/data/20241106/1730873304749.png)] bg-contain" class="mt-[3px] h-9 w-9 flex-shrink-0 bg-contain"
:style="{ backgroundImage: `url(${KnowledgeTypeIcon[item.knowledgeType]})` }"
/> />
<div class="ml-3.5 flex flex-col items-start overflow-hidden"> <div class="ml-3.5 flex flex-col items-start overflow-hidden">
......
...@@ -4,6 +4,7 @@ import { FormInst } from 'naive-ui' ...@@ -4,6 +4,7 @@ import { FormInst } from 'naive-ui'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { FileAddition } from '@icon-park/vue-next' import { FileAddition } from '@icon-park/vue-next'
import CustomModal from '@/components/custom-modal/custom-modal.vue' import CustomModal from '@/components/custom-modal/custom-modal.vue'
import { KnowledgeTypeIcon } from '@/enums/knowledge'
const { t } = useI18n() const { t } = useI18n()
...@@ -30,7 +31,7 @@ const emit = defineEmits<Emits>() ...@@ -30,7 +31,7 @@ const emit = defineEmits<Emits>()
const createKnowledgeFormRef = ref<FormInst | null>(null) const createKnowledgeFormRef = ref<FormInst | null>(null)
const defaultKnowledgeFormData = { const defaultKnowledgeFormData = {
knowledgeType: 'text', knowledgeType: 'Base',
knowledgeName: '', knowledgeName: '',
knowledgeDesc: '', knowledgeDesc: '',
knowledgeImportType: 'local-document', knowledgeImportType: 'local-document',
...@@ -61,9 +62,15 @@ const createKnowledgeFormDataRules = { ...@@ -61,9 +62,15 @@ const createKnowledgeFormDataRules = {
const knowledgeTypeList = readonly([ const knowledgeTypeList = readonly([
{ {
id: 'KJ7rCkq3HNOk', id: 'KJ7rCkq3HNOk',
icon: 'https://gsst-poe-sit.gz.bcebos.com/data/20241012/1728700558225.png', icon: KnowledgeTypeIcon.Base,
type: 'text', type: 'Base',
name: 'personal_space_module.knowledge_module.knowledge_document_text_type', name: t('personal_space_module.knowledge_module.base_knowledge_type'),
},
{
id: 'O7Y9BsSm5J5j',
icon: KnowledgeTypeIcon.QA,
type: 'QA',
name: t('personal_space_module.knowledge_module.QA_knowledge_type'),
}, },
]) ])
...@@ -71,12 +78,25 @@ const importKnowledgeFileList = readonly([ ...@@ -71,12 +78,25 @@ const importKnowledgeFileList = readonly([
{ {
id: 'ewwBbe9di5Ym', id: 'ewwBbe9di5Ym',
icon: () => h(FileAddition, { theme: 'outline', size: '16', fill: '#333' }), icon: () => h(FileAddition, { theme: 'outline', size: '16', fill: '#333' }),
knowledgeType: 'Base',
type: 'local-document',
title: t('personal_space_module.knowledge_module.import_local_document_knowledge'),
desc: t('personal_space_module.knowledge_module.import_local_document_knowledge_desc'),
},
{
id: 'M8BMXe2tOIlB',
icon: () => h(FileAddition, { theme: 'outline', size: '16', fill: '#333' }),
knowledgeType: 'QA',
type: 'local-document', type: 'local-document',
title: 'personal_space_module.knowledge_module.import_local_document_knowledge', title: t('personal_space_module.knowledge_module.import_local_document_knowledge'),
desc: 'personal_space_module.knowledge_module.import_local_document_knowledge_desc', desc: t('personal_space_module.knowledge_module.import_QA_local_document_knowledge_desc'),
}, },
]) ])
const currentKnowledgeTypeList = computed(() => {
return importKnowledgeFileList.filter((item) => item.knowledgeType === createKnowledgeFormData.value.knowledgeType)
})
const showModal = computed({ const showModal = computed({
get() { get() {
return props.isShowModal return props.isShowModal
...@@ -133,13 +153,14 @@ function handleNext() { ...@@ -133,13 +153,14 @@ function handleNext() {
:key="knowledgeTypeItem.id" :key="knowledgeTypeItem.id"
:class=" :class="
createKnowledgeFormData.knowledgeType === knowledgeTypeItem.type createKnowledgeFormData.knowledgeType === knowledgeTypeItem.type
? 'border-theme-color text-theme-color bg-[#ecedfa]' ? 'border-theme-color text-theme-color'
: 'text-font-color bg-white' : '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" class="hover:border-theme-color hover:text-theme-color flex h-[66px] cursor-pointer flex-col items-center justify-center rounded-lg border"
@click="createKnowledgeFormData.knowledgeType = knowledgeTypeItem.type"
> >
<img :src="knowledgeTypeItem.icon" class="h-6 w-6" /> <img :src="knowledgeTypeItem.icon" class="h-6 w-6" />
<span class="mt-2 text-sm leading-[14px]">{{ t(knowledgeTypeItem.name) }}</span> <span class="mt-2 text-sm leading-[14px]">{{ knowledgeTypeItem.name }}</span>
</div> </div>
</div> </div>
</div> </div>
...@@ -169,22 +190,22 @@ function handleNext() { ...@@ -169,22 +190,22 @@ function handleNext() {
> >
<div class="grid w-full grid-cols-2 gap-2"> <div class="grid w-full grid-cols-2 gap-2">
<div <div
v-for="importKnowledgeFileItem in importKnowledgeFileList" v-for="importKnowledgeFileItem in currentKnowledgeTypeList"
:key="importKnowledgeFileItem.id" :key="importKnowledgeFileItem.id"
class="border-inactive-border-color flex h-[64px] flex-col items-start justify-center rounded-lg border bg-[#FAFAFA] px-[14px]" class="border-inactive-border-color flex h-[64px] flex-col items-start justify-center rounded-lg border bg-[#FAFAFA] px-[14px]"
> >
<div class="flex items-center"> <div class="flex items-center">
<component :is="importKnowledgeFileItem.icon()" /> <component :is="importKnowledgeFileItem.icon()" />
<span class="text-font-color ml-1 line-clamp-1">{{ t(importKnowledgeFileItem.title) }}</span> <span class="text-font-color ml-1 line-clamp-1">{{ importKnowledgeFileItem.title }}</span>
</div> </div>
<NPopover trigger="hover"> <NPopover trigger="hover">
<template #trigger> <template #trigger>
<span class="text-gray-font-color mt-1.5 line-clamp-1 text-xs"> <span class="text-gray-font-color mt-1.5 line-clamp-1 text-xs">
{{ t(importKnowledgeFileItem.desc) }} {{ importKnowledgeFileItem.desc }}
</span> </span>
</template> </template>
<span>{{ t(importKnowledgeFileItem.desc) }}</span> <span>{{ importKnowledgeFileItem.desc }}</span>
</NPopover> </NPopover>
</div> </div>
</div> </div>
......
...@@ -9,6 +9,7 @@ const { t } = useI18n() ...@@ -9,6 +9,7 @@ const { t } = useI18n()
export interface KnowledgeFormDataInterface { export interface KnowledgeFormDataInterface {
knowledgeName: string knowledgeName: string
desc: string desc: string
knowledgeType: 'Base' | 'QA'
} }
interface Props { interface Props {
...@@ -28,6 +29,7 @@ const emit = defineEmits<Emits>() ...@@ -28,6 +29,7 @@ const emit = defineEmits<Emits>()
const defaultKnowledgeFormData = { const defaultKnowledgeFormData = {
knowledgeName: '', knowledgeName: '',
desc: '', desc: '',
knowledgeType: 'Base' as const,
} }
const knowledgeFormRef = ref<FormInst | null>(null) const knowledgeFormRef = ref<FormInst | null>(null)
......
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref } from 'vue' import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { ScrollbarInst } from 'naive-ui' import { ScrollbarInst } from 'naive-ui'
import { Left, Search } from '@icon-park/vue-next' import { Left, Search } from '@icon-park/vue-next'
import { KnowledgeChunkItem, KnowledgeDocumentItem } from './types.d' import { KnowledgeChunkItem, KnowledgeDocumentItem } from '../../types'
import KnowledgeChuckItem from './components/knowledge-chuck-item.vue' import KnowledgeChuckItem from './components/knowledge-chuck-item.vue'
import { import {
fetchAddKnowledgeChunk, fetchAddKnowledgeChunk,
fetchDeleteKnowledgeChunk, fetchDeleteKnowledgeChunk,
fetchGetKnowledgeChunkList, fetchGetKnowledgeChunkList,
fetchGetKnowledgeDocumentListByKdIds,
fetchOpenKnowledgeChunk, fetchOpenKnowledgeChunk,
fetchUpdateKnowledgeChunk, fetchUpdateKnowledgeChunk,
} from '@/apis/knowledge' } from '@/apis/knowledge'
...@@ -19,16 +17,20 @@ import { usePagination } from '@/composables/usePagination.ts' ...@@ -19,16 +17,20 @@ import { usePagination } from '@/composables/usePagination.ts'
import EditKnowledgeChunkModal from './components/edit-knowledge-chunk-modal.vue' import EditKnowledgeChunkModal from './components/edit-knowledge-chunk-modal.vue'
import AddKnowledgeChunkModal from './components/add-knowledge-chunk-modal.vue' import AddKnowledgeChunkModal from './components/add-knowledge-chunk-modal.vue'
const { t } = useI18n() interface Props {
kdId: number
knowledgeDocument: Pick<KnowledgeDocumentItem, 'documentUrl' | 'documentName' | 'segmentationConfig'>
}
const router = useRouter() const props = defineProps<Props>()
const { paginationData } = usePagination() const emit = defineEmits<{
backList: []
}>()
const { t } = useI18n()
const currentKdId = ref(0) const { paginationData } = usePagination()
const currentKnowledgeDocumentName = ref('')
const currentKnowledgeDocumentUrl = ref('')
const currentKnowledgeSegmentationType = ref('')
const scrollBarRef = ref<ScrollbarInst | null>(null) const scrollBarRef = ref<ScrollbarInst | null>(null)
...@@ -63,38 +65,15 @@ const emptyKnowledgeChunkListText = computed(() => { ...@@ -63,38 +65,15 @@ const emptyKnowledgeChunkListText = computed(() => {
}) })
onMounted(async () => { onMounted(async () => {
if (!router.currentRoute.value.params.kdId) { props.kdId && (await handleGetKnowledgeChunkList())
window.$message.warning(t('personal_space_module.knowledge_module.not_find_knowledge_document_message'))
router.replace({ name: 'PersonalSpaceKnowledge' })
return
}
currentKdId.value = Number(router.currentRoute.value.params.kdId)
await handleGetKnowledgeDocumentDetail()
await handleGetKnowledgeChunkList()
}) })
function handleGetKnowledgeDocumentDetail() {
fetchGetKnowledgeDocumentListByKdIds<KnowledgeDocumentItem[]>([currentKdId.value])
.then((res) => {
if (res.code === 0) {
currentKnowledgeDocumentName.value = res.data[0].documentName
currentKnowledgeDocumentUrl.value = res.data[0].documentUrl
currentKnowledgeSegmentationType.value = res.data[0]?.segmentationConfig?.segmentationType
}
})
.catch(() => {
window.$message.warning(t('personal_space_module.knowledge_module.not_find_knowledge_document_message'))
router.replace({ name: 'PersonalSpaceKnowledge' })
})
}
async function handleGetKnowledgeChunkList() { async function handleGetKnowledgeChunkList() {
knowledgeChunkListLoading.value = true knowledgeChunkListLoading.value = true
const res = await fetchGetKnowledgeChunkList<{ totalChunk: number; chunkInfos: KnowledgeChunkItem[] }>( const res = await fetchGetKnowledgeChunkList<{ totalChunk: number; chunkInfos: KnowledgeChunkItem[] }>(
searchKnowledgeChunkValue.value, searchKnowledgeChunkValue.value,
currentKdId.value, props.kdId,
{ {
pagingInfo: paginationData, pagingInfo: paginationData,
}, },
...@@ -111,7 +90,7 @@ async function handleGetKnowledgeChunkList() { ...@@ -111,7 +90,7 @@ async function handleGetKnowledgeChunkList() {
} }
function handleBackKnowledgeDocumentList() { function handleBackKnowledgeDocumentList() {
router.replace({ name: 'KnowledgeDocument', params: { id: router.currentRoute.value.params.id } }) emit('backList')
} }
async function handleSearchKnowledgeChunkList() { async function handleSearchKnowledgeChunkList() {
...@@ -129,7 +108,7 @@ async function handleUpdateKnowledgeChunk(chunkRelationId: string, chunkContent: ...@@ -129,7 +108,7 @@ async function handleUpdateKnowledgeChunk(chunkRelationId: string, chunkContent:
updateKnowledgeChunkBtnLoading.value = true updateKnowledgeChunkBtnLoading.value = true
const res = await fetchUpdateKnowledgeChunk({ const res = await fetchUpdateKnowledgeChunk({
kdId: currentKdId.value, kdId: props.kdId,
chunkRelationId, chunkRelationId,
chunkContent, chunkContent,
}).finally(() => (updateKnowledgeChunkBtnLoading.value = false)) }).finally(() => (updateKnowledgeChunkBtnLoading.value = false))
...@@ -155,7 +134,7 @@ async function handleAddKnowledgeChunk(chunkContent: string, chunkSort: number) ...@@ -155,7 +134,7 @@ async function handleAddKnowledgeChunk(chunkContent: string, chunkSort: number)
addKnowledgeChunkBtnLoading.value = true addKnowledgeChunkBtnLoading.value = true
const res = await fetchAddKnowledgeChunk({ const res = await fetchAddKnowledgeChunk({
kdId: currentKdId.value, kdId: props.kdId,
chunkContent, chunkContent,
chunkSort, chunkSort,
}).finally(() => { }).finally(() => {
...@@ -174,7 +153,7 @@ async function handleDeleteKnowledgeChunk(chunkRelationId: string) { ...@@ -174,7 +153,7 @@ async function handleDeleteKnowledgeChunk(chunkRelationId: string) {
.ctWarning('', t('personal_space_module.knowledge_module.delete_knowledge_chunk_content_message')) .ctWarning('', t('personal_space_module.knowledge_module.delete_knowledge_chunk_content_message'))
.then(async () => { .then(async () => {
const res = await fetchDeleteKnowledgeChunk({ const res = await fetchDeleteKnowledgeChunk({
kdId: currentKdId.value, kdId: props.kdId,
chunkRelationId, chunkRelationId,
}) })
...@@ -191,7 +170,7 @@ async function handleUpdateOpenKnowledgeChunk(chunkItem: KnowledgeChunkItem) { ...@@ -191,7 +170,7 @@ async function handleUpdateOpenKnowledgeChunk(chunkItem: KnowledgeChunkItem) {
const res = await fetchOpenKnowledgeChunk({ const res = await fetchOpenKnowledgeChunk({
chunkRelationId, chunkRelationId,
isOpen: isOpen === 'Y' ? 'N' : 'Y', isOpen: isOpen === 'Y' ? 'N' : 'Y',
kdId: currentKdId.value, kdId: props.kdId,
}) })
if (res.code === 0) { if (res.code === 0) {
...@@ -214,31 +193,29 @@ async function handleGetKnowledgeChunkListUpdatePageSize(pageSize: number) { ...@@ -214,31 +193,29 @@ async function handleGetKnowledgeChunkListUpdatePageSize(pageSize: number) {
</script> </script>
<template> <template>
<div class="flex h-full flex-col overflow-hidden"> <div class="flex h-full w-full flex-col overflow-hidden">
<div class="my-6"> <div class="mb-4.5 gap-4.5 mt-6 flex items-center">
<div class="flex h-9 items-center"> <div class="flex h-9 items-center">
<Left theme="outline" size="20" fill="#333" class="cursor-pointer" @click="handleBackKnowledgeDocumentList" /> <Left theme="outline" size="20" fill="#333" class="cursor-pointer" @click="handleBackKnowledgeDocumentList" />
<img :src="documentIcon(currentKnowledgeDocumentUrl)" class="ml-[2px] h-9 w-9" /> <img :src="documentIcon(knowledgeDocument.documentUrl)" class="ml-[2px] h-9 w-9" />
<span class="text-font-color ml-[5px] line-clamp-1 select-none break-all text-lg"> <span class="text-font-color ml-[5px] line-clamp-1 select-none break-all text-lg">
{{ currentKnowledgeDocumentName }} {{ knowledgeDocument.documentName }}
</span> </span>
</div> </div>
<div class="ml-[66px] mt-1"> <ul class="flex flex-shrink-0 gap-2">
<ul class="flex gap-2"> <li class="border-inactive-border-color select-none rounded-full border px-4 py-1 leading-[18px]">
<li class="border-inactive-border-color select-none rounded-full border px-4 py-1 leading-[18px]"> {{ totalChunk }}
{{ totalChunk }} {{ t('personal_space_module.knowledge_module.segment') }}
{{ t('personal_space_module.knowledge_module.segment') }} </li>
</li> <li class="border-inactive-border-color select-none rounded-full border px-4 py-1 leading-[18px]">
<li class="border-inactive-border-color select-none rounded-full border px-4 py-1 leading-[18px]"> {{
{{ knowledgeDocument.segmentationConfig.segmentationType === 'DEFAULT'
currentKnowledgeSegmentationType === 'DEFAULT' ? t('personal_space_module.knowledge_module.auto_segment')
? t('personal_space_module.knowledge_module.auto_segment') : t('personal_space_module.knowledge_module.custom_segment')
: t('personal_space_module.knowledge_module.custom_segment') }}
}} </li>
</li> </ul>
</ul>
</div>
</div> </div>
<div class="mb-[18px] flex justify-end"> <div class="mb-[18px] flex justify-end">
......
...@@ -3,7 +3,7 @@ import { computed, ref, watch } from 'vue' ...@@ -3,7 +3,7 @@ import { computed, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { FormInst } from 'naive-ui' import { FormInst } from 'naive-ui'
import CustomModal from '@/components/custom-modal/custom-modal.vue' import CustomModal from '@/components/custom-modal/custom-modal.vue'
import { KnowledgeChunkItem } from '../knowledge-type' import { KnowledgeChunkItem } from '../../../types'
interface Props { interface Props {
isShowModal: boolean isShowModal: boolean
......
...@@ -3,7 +3,7 @@ import { computed, ref, watch } from 'vue' ...@@ -3,7 +3,7 @@ import { computed, ref, watch } from 'vue'
import { FormInst } from 'naive-ui' import { FormInst } from 'naive-ui'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import CustomModal from '@/components/custom-modal/custom-modal.vue' import CustomModal from '@/components/custom-modal/custom-modal.vue'
import { KnowledgeChunkItem } from '../knowledge-type' import { KnowledgeChunkItem } from '../../../types'
interface Props { interface Props {
isShowModal: boolean isShowModal: boolean
......
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { KnowledgeChunkItem } from '../types.d' import { KnowledgeChunkItem } from '../../../types.d'
interface Props { interface Props {
chunkItem: KnowledgeChunkItem chunkItem: KnowledgeChunkItem
...@@ -74,11 +74,11 @@ const isShowKnowledgeChunkAction = ref(false) ...@@ -74,11 +74,11 @@ const isShowKnowledgeChunkAction = ref(false)
<span> {{ t('common_module.data_table_module.delete') }}</span> <span> {{ t('common_module.data_table_module.delete') }}</span>
</n-popover> </n-popover>
<n-switch :value="chunkItem.isOpen === 'Y'" @update:value="emit('updateOpen', chunkItem)" /> <n-switch :value="chunkItem.isOpen === 'Y'" size="small" @update:value="emit('updateOpen', chunkItem)" />
</div> </div>
</div> </div>
<div class="text-font-color mt-[14px] whitespace-pre-wrap"> <div class="text-font-color mt-[14px] whitespace-pre-wrap break-all">
{{ chunkItem.chunkContent }} {{ chunkItem.chunkContent }}
</div> </div>
</div> </div>
......
import { NEllipsis, NPopover, NSwitch } from 'naive-ui'
import i18n from '@/locales'
import { QAKnowledgeChunkContentItem, QAKnowledgeChunkKeyItem } from '../types'
const t = i18n.global.t
export function createQAKnowledgeChunkColumn(
chunkColumnKey: QAKnowledgeChunkKeyItem[],
handleKnowledgeChunkTableAction: (
actionType: string,
chunkRelationId: string,
KnowledgeItem?: QAKnowledgeChunkContentItem,
) => void,
) {
return [
{
type: 'selection',
fixed: 'left',
className: 'qa-knowledge-chunk-selection',
},
{
title: () => <span class='font-family-medium'>{t('personal_space_module.knowledge_module.no')}</span>,
key: 'key',
minWidth: 92,
className: 'qa-knowledge-chunk-index',
render: (_: any, index: number) => {
return `${index + 1}`
},
},
...chunkColumnKey.map((item) => {
return {
title: () => {
return (
<div class='flex items-center gap-[10px]'>
<NEllipsis class='font-family-medium max-w-full' tooltip={{ width: 200 }}>
{item.structName}
</NEllipsis>
{item.isIndex === 'Y' && (
<span class='border-theme-color text-theme-color rounded-theme flex-shrink-0 border px-[11px] py-[2px] text-xs'>
{t('personal_space_module.knowledge_module.index')}
</span>
)}
</div>
)
},
key: item.structId,
align: 'left',
width: 246,
className: 'qa-knowledge-chunk-item',
render(row: QAKnowledgeChunkContentItem & { expanded: boolean }) {
return (
<div
class={[row.expanded ? '' : 'line-clamp-1', 'cursor-pointer']}
onClick={() => {
row.expanded = !row.expanded
}}
>
{row.chunkInfo.find((chunkInfo) => chunkInfo.structId === item.structId)?.content || '--'}
</div>
)
},
}
}),
{
title: '',
key: 'action',
align: '',
fixed: 'right',
width: 0,
render(row: QAKnowledgeChunkContentItem) {
return (
<div class='top-4.5 flex-center absolute right-[24px] z-10 gap-[10px]'>
<NPopover
v-slots={{
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'
onClick={() => {
handleKnowledgeChunkTableAction('edit', row.chunkRelationId, row)
}}
/>
),
}}
>
<span>{t('common_module.data_table_module.edit')}</span>
</NPopover>
<NPopover
v-slots={{
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'
onClick={() => {
handleKnowledgeChunkTableAction('addUp', row.chunkRelationId, row)
}}
/>
),
}}
>
<span>{t('personal_space_module.knowledge_module.add_chunk_up_message')}</span>
</NPopover>
<NPopover
v-slots={{
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'
onClick={() => {
handleKnowledgeChunkTableAction('addDown', row.chunkRelationId, row)
}}
/>
),
}}
>
<span>{t('personal_space_module.knowledge_module.add_chunk_down_message')}</span>
</NPopover>
<NPopover
v-slots={{
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'
onClick={() => {
handleKnowledgeChunkTableAction('delete', row.chunkRelationId)
}}
/>
),
}}
>
<span>{t('common_module.delete')}</span>
</NPopover>
<NSwitch
value={row.isOpen === 'Y'}
size='small'
onUpdateValue={() => handleKnowledgeChunkTableAction('updateOpen', row.chunkRelationId, row)}
/>
</div>
)
},
},
]
}
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { fetchGetKnowledgeDocumentListByKdIds } from '@/apis/knowledge'
import { KnowledgeDocumentItem } from '../types'
import BaseKnowledgeChunkDetail from './base-knowledge/base-knowledge-chunk-detail.vue'
import QAKnowledgeChunkDetail from './qa-knowledge/qa-knowledge-chunk-detail.vue'
const { t } = useI18n()
const router = useRouter()
const currentKdId = ref(0)
const currentKnowledgeDocument = ref<
Pick<KnowledgeDocumentItem, 'documentUrl' | 'documentName' | 'segmentationConfig'>
>({
documentName: '',
documentUrl: '',
segmentationConfig: {
segmentationType: 'DEFAULT',
chunkSize: 0,
scrapSize: 0,
repetitionRate: 0,
relationInfo: [],
regex: '',
punctuations: [],
},
})
const currentKnowledgeType = ref()
onMounted(async () => {
if (!router.currentRoute.value.params.kdId) {
window.$message.warning(t('personal_space_module.knowledge_module.not_find_knowledge_document_message'))
router.replace({ name: 'PersonalSpaceKnowledge' })
return
}
currentKdId.value = Number(router.currentRoute.value.params.kdId)
await handleGetKnowledgeDocumentDetail()
})
function handleGetKnowledgeDocumentDetail() {
fetchGetKnowledgeDocumentListByKdIds<KnowledgeDocumentItem[]>([currentKdId.value])
.then((res) => {
if (res.code === 0) {
currentKnowledgeDocument.value = res.data?.[0]
currentKnowledgeType.value = res.data?.[0]?.knowledgeType || 'Base'
}
})
.catch(() => {
window.$message.warning(t('personal_space_module.knowledge_module.not_find_knowledge_document_message'))
router.replace({ name: 'PersonalSpaceKnowledge' })
})
}
function handleBackKnowledgeDocumentList() {
router.replace({ name: 'KnowledgeDocument', params: { id: router.currentRoute.value.params.id } })
}
</script>
<template>
<div class="flex h-full flex-col overflow-hidden">
<div v-if="currentKnowledgeType === 'BASE'" class="flex h-full w-full">
<BaseKnowledgeChunkDetail
:kd-id="currentKdId"
:knowledge-document="currentKnowledgeDocument"
@back-list="handleBackKnowledgeDocumentList"
/>
</div>
<div v-if="currentKnowledgeType === 'QA'" class="flex h-full w-full">
<QAKnowledgeChunkDetail
:kd-id="currentKdId"
:knowledge-document="currentKnowledgeDocument"
@back-list="handleBackKnowledgeDocumentList"
/>
</div>
</div>
</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 { QAKnowledgeChunkKeyItem, QAKnowledgeChunkInfoItem } from '../../../types'
type QAKnowledgeChunkStructItem = QAKnowledgeChunkKeyItem & { content: string }
interface Props {
btnLoading: boolean
knowledgeChunkKeyList: QAKnowledgeChunkKeyItem[]
}
const emit = defineEmits<{
confirm: [chunkStructInfo: QAKnowledgeChunkInfoItem[]]
}>()
const props = defineProps<Props>()
const { t } = useI18n()
const isShow = defineModel<boolean>('isShow', { required: true })
const knowledgeChunkFormRef = ref<FormInst | null>(null)
const chunkStructList = ref<QAKnowledgeChunkStructItem[]>([])
const chunkStructRules = {
content: {
required: true,
trigger: 'blur',
message: t('personal_space_module.knowledge_module.please_enter_the_content'),
},
}
const dataTableScrollX = computed(() => {
return props.knowledgeChunkKeyList.length * 215
})
watch(
() => isShow.value,
(newVal) => {
if (newVal) {
chunkStructList.value = props.knowledgeChunkKeyList.map((chunkKeyItem) => {
return { ...chunkKeyItem, content: '' }
})
}
},
)
function handleAddKnowledgeChunk() {
knowledgeChunkFormRef.value?.validate((valid) => {
if (!valid) {
const newChunkStructInfo = chunkStructList.value.map((chunkItem) => {
const { structId, content } = chunkItem
return {
structId,
content,
}
})
emit('confirm', newChunkStructInfo)
}
})
}
</script>
<template>
<CustomModal
v-model:is-show="isShow"
:title="t('personal_space_module.knowledge_module.add_QA_data')"
:btn-loading="btnLoading"
:width="1000"
@confirm="handleAddKnowledgeChunk"
>
<template #content>
<n-scrollbar x-scrollable>
<n-form
ref="knowledgeChunkFormRef"
:inline="true"
:model="{ chunkStructList }"
:rules="chunkStructRules"
:style="{ width: dataTableScrollX + 'px' }"
class="rounded-theme"
>
<n-form-item
v-for="(chunkItem, index) in chunkStructList"
:key="chunkItem.structId"
class="qa-knowledge-chunk-key w-[215px]! box-border border-collapse overflow-hidden border border-r-0 border-[#e5e7eb] first:rounded-l-[10px] last:rounded-r-[10px] last:border-r"
:path="`chunkStructList[${index}].content`"
:show-require-mark="false"
feedback-class="px-3!"
:rule="chunkItem.isIndex === 'Y' ? chunkStructRules.content : []"
>
<template #label>
<div class="flex h-12 w-full flex-shrink-0 items-center border-b border-[#e5e7eb] bg-[#f7f7fa] px-3">
<span v-show="chunkItem.isIndex === 'Y'" class="mr-1 text-base text-[#d03050]">*</span>
<n-ellipsis :tooltip="{ width: '200px' }">
{{ chunkItem.structName }}
</n-ellipsis>
<span
v-show="chunkItem.isIndex === 'Y'"
class="border-theme-color text-theme-color rounded-theme ml-[10px] flex-shrink-0 border px-[11px] py-[2px] text-xs"
>
{{ t('personal_space_module.knowledge_module.index') }}
</span>
</div>
</template>
<div class="flex w-full flex-shrink-0 p-0">
<n-input
v-model:value="chunkItem.content"
type="textarea"
:placeholder="t('personal_space_module.knowledge_module.please_enter_the_content')"
class="chunk-content-input"
:autosize="{
minRows: 3,
maxRows: 3,
}"
/>
</div>
</n-form-item>
</n-form>
</n-scrollbar>
</template>
</CustomModal>
</template>
<style lang="scss" scoped>
:deep(.qa-knowledge-chunk-key.n-form-item) {
flex-shrink: 0;
width: 215px;
margin-right: 0 !important;
.n-form-item-label__text {
width: 100%;
}
.n-form-item-label {
padding: 0;
}
}
:deep(.chunk-content-input.n-input--textarea) {
--n-border: none !important;
--n-border-focus: none !important;
--n-border-hover: none !important;
--n-box-shadow-focus: none !important;
}
</style>
<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 { QAKnowledgeChunkKeyItem, QAKnowledgeChunkInfoItem, QAKnowledgeChunkContentItem } from '../../../types'
type QAKnowledgeChunkStructItem = QAKnowledgeChunkKeyItem & { content: string }
interface Props {
btnLoading: boolean
knowledgeChunkKeyList: QAKnowledgeChunkKeyItem[]
currentChunkItem: QAKnowledgeChunkContentItem
}
const emit = defineEmits<{
confirm: [chunkRelationId: string, chunkStructInfo: QAKnowledgeChunkInfoItem[]]
}>()
const props = defineProps<Props>()
const { t } = useI18n()
const isShow = defineModel<boolean>('isShow', { required: true })
const knowledgeChunkFormRef = ref<FormInst | null>(null)
const chunkStructList = ref<QAKnowledgeChunkStructItem[]>([])
const chunkStructRules = {
content: {
required: true,
trigger: 'blur',
message: t('personal_space_module.knowledge_module.please_enter_the_content'),
},
}
const dataTableScrollX = computed(() => {
return props.knowledgeChunkKeyList.length * 215
})
watch(
() => isShow.value,
(newVal) => {
if (newVal) {
const map = new Map(
props.currentChunkItem.chunkInfo.map((chunkInfoItem) => [chunkInfoItem.structId, chunkInfoItem]),
)
chunkStructList.value = props.knowledgeChunkKeyList.map((chunkKeyItem) => ({
...chunkKeyItem,
content: map.get(chunkKeyItem.structId)?.content || '',
}))
}
},
)
function handleAddKnowledgeChunk() {
knowledgeChunkFormRef.value?.validate((valid) => {
if (!valid) {
const newChunkStructInfo = chunkStructList.value.map((chunkItem) => {
const { structId, content } = chunkItem
return {
structId,
content,
}
})
emit('confirm', props.currentChunkItem.chunkRelationId, newChunkStructInfo)
}
})
}
</script>
<template>
<CustomModal
v-model:is-show="isShow"
:title="t('personal_space_module.knowledge_module.edit_QA_data')"
:btn-loading="btnLoading"
:width="1000"
@confirm="handleAddKnowledgeChunk"
>
<template #content>
<n-scrollbar x-scrollable>
<n-form
ref="knowledgeChunkFormRef"
:inline="true"
:model="{ chunkStructList }"
:rules="chunkStructRules"
:style="{ width: dataTableScrollX + 'px' }"
class="rounded-theme"
>
<n-form-item
v-for="(chunkItem, index) in chunkStructList"
:key="chunkItem.structId"
class="qa-knowledge-chunk-key w-[215px]! box-border border-collapse overflow-hidden border border-r-0 border-[#e5e7eb] first:rounded-l-[10px] last:rounded-r-[10px] last:border-r"
:path="`chunkStructList[${index}].content`"
:show-require-mark="false"
feedback-class="px-3!"
:rule="chunkItem.isIndex === 'Y' ? chunkStructRules.content : []"
>
<template #label>
<div class="flex h-12 w-full flex-shrink-0 items-center border-b border-[#e5e7eb] bg-[#f7f7fa] px-3">
<span v-show="chunkItem.isIndex === 'Y'" class="mr-1 text-base text-[#d03050]">*</span>
<n-ellipsis :tooltip="{ width: '200px' }">
{{ chunkItem.structName }}
</n-ellipsis>
<span
v-show="chunkItem.isIndex === 'Y'"
class="border-theme-color text-theme-color rounded-theme ml-[10px] flex-shrink-0 border px-[11px] py-[2px] text-xs"
>
{{ t('personal_space_module.knowledge_module.index') }}
</span>
</div>
</template>
<div class="flex w-full flex-shrink-0 p-0">
<n-input
v-model:value="chunkItem.content"
type="textarea"
:placeholder="t('personal_space_module.knowledge_module.please_enter_the_content')"
class="chunk-content-input"
:autosize="{
minRows: 3,
maxRows: 3,
}"
/>
</div>
</n-form-item>
</n-form>
</n-scrollbar>
</template>
</CustomModal>
</template>
<style lang="scss" scoped>
:deep(.qa-knowledge-chunk-key.n-form-item) {
flex-shrink: 0;
width: 215px;
margin-right: 0 !important;
.n-form-item-label__text {
width: 100%;
}
.n-form-item-label {
padding: 0;
}
}
:deep(.chunk-content-input.n-input--textarea) {
--n-border: none !important;
--n-border-focus: none !important;
--n-border-hover: none !important;
--n-box-shadow-focus: none !important;
}
</style>
<script setup lang="ts">
import { computed, h } from 'vue'
import { NSwitch } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import CustomModal from '@/components/custom-modal/custom-modal.vue'
import { QAKnowledgeChunkKeyItem } from '../../../types'
interface Props {
kdId: number
knowledgeChunkKeyList: QAKnowledgeChunkKeyItem[]
}
const props = defineProps<Props>()
const emit = defineEmits<{
updateKnowledgeChunkIndex: [chunkKeyItem: QAKnowledgeChunkKeyItem]
confirm: []
}>()
const { t } = useI18n()
const isShow = defineModel<boolean>('isShow', { required: true })
const isHasChunkIndex = computed(() => {
return props.knowledgeChunkKeyList.some((item) => item.isIndex === 'Y')
})
const chunkIndexColumns = createChunkIndexColumns()
function createChunkIndexColumns() {
return [
{
title: () => t('personal_space_module.knowledge_module.field_name'),
key: 'structName',
render: (row: QAKnowledgeChunkKeyItem) => {
return row.structName || '-'
},
},
{
title: () => t('personal_space_module.knowledge_module.setting_index'),
key: 'isIndex',
render: (row: QAKnowledgeChunkKeyItem) => {
return h(
NSwitch,
{ value: row.isIndex === 'Y', size: 'small', onUpdateValue: () => handleKnowledgeTableAction(row) },
{},
)
},
},
]
}
function handleKnowledgeTableAction(chunkKeyItem: QAKnowledgeChunkKeyItem) {
chunkKeyItem.isIndex = chunkKeyItem.isIndex === 'Y' ? 'N' : 'Y'
if (!isHasChunkIndex.value) {
chunkKeyItem.isIndex = chunkKeyItem.isIndex === 'Y' ? 'N' : 'Y'
window.$message.warning(t('personal_space_module.knowledge_module.select_at_least_one_field_as_the_index'))
return
}
emit('updateKnowledgeChunkIndex', chunkKeyItem)
}
</script>
<template>
<CustomModal
v-model:is-show="isShow"
:title="t('personal_space_module.knowledge_module.setting_index')"
:width="644"
@confirm="emit('confirm')"
>
<template #content>
<p class="text-gray-font-color mb-2.5">
{{ t('personal_space_module.knowledge_module.setting_index_desc') }}
</p>
<n-data-table :single-line="false" :columns="chunkIndexColumns" :data="knowledgeChunkKeyList" :max-height="300" />
</template>
</CustomModal>
</template>
...@@ -40,6 +40,7 @@ const updateKnowledgeInfoBtnLoading = ref(false) ...@@ -40,6 +40,7 @@ const updateKnowledgeInfoBtnLoading = ref(false)
const currentKnowledgeData = ref<KnowledgeFormDataInterface>({ const currentKnowledgeData = ref<KnowledgeFormDataInterface>({
knowledgeName: '', knowledgeName: '',
desc: '', desc: '',
knowledgeType: 'Base',
}) })
const isDisabledBatchDelBtn = computed(() => { const isDisabledBatchDelBtn = computed(() => {
...@@ -203,9 +204,11 @@ async function handleBatchDelDocument() { ...@@ -203,9 +204,11 @@ async function handleBatchDelDocument() {
} }
function handleToUploadDocument() { function handleToUploadDocument() {
const knowledgeType = currentKnowledgeData.value.knowledgeType || 'Base'
router.push({ router.push({
name: 'UploadKnowledge', name: 'UploadKnowledge',
params: { id: router.currentRoute.value.params.id, type: 'text-local-document' }, params: { id: router.currentRoute.value.params.id, type: `${knowledgeType}-local-document` },
}) })
} }
......
...@@ -11,6 +11,7 @@ export interface KnowledgeItem { ...@@ -11,6 +11,7 @@ export interface KnowledgeItem {
knowledgeName: string knowledgeName: string
desc: string desc: string
trainStatus: TrainStatus trainStatus: TrainStatus
knowledgeType: 'Base' | 'QA'
isOpen: 'Y' | 'N' isOpen: 'Y' | 'N'
createdTime: Date createdTime: Date
documentInfos: KnowledgeDocumentItem[] documentInfos: KnowledgeDocumentItem[]
...@@ -46,3 +47,22 @@ export interface KnowledgeChunkItem { ...@@ -46,3 +47,22 @@ export interface KnowledgeChunkItem {
chunkSort: number chunkSort: number
isOpen: 'Y' | 'N' isOpen: 'Y' | 'N'
} }
export interface QAKnowledgeChunkKeyItem {
structId: number
isIndex: 'Y' | 'N'
sort: number
structName: string
}
export interface QAKnowledgeChunkContentItem {
chunkRelationId: string
chunkSort: number
chunkInfo: QAKnowledgeChunkInfoItem[]
isOpen: 'Y' | 'N'
}
export interface QAKnowledgeChunkInfoItem {
structId: number
content: string
}
<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}.svg`
})
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 rounded-[10px] 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 { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import UploadFile from './upload-file.vue'
import DataSetting from './data-setting.vue'
import { fetchTrainKnowledge } from '@/apis/knowledge'
interface StepItem {
stepId: number
title: string
}
const { t } = useI18n()
const router = useRouter()
const stepList = ref<StepItem[]>([
{
stepId: 1,
title: t('common_module.upload'),
},
{
stepId: 2,
title: t('personal_space_module.knowledge_module.upload_document_module.data_process'),
},
])
const currentStepId = ref(1)
const currentKdIdList = ref<number[]>([])
const currentKnowledgeId = ref(0)
onMounted(() => {
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)
})
async function handleToDataProcess(kdId: number[]) {
currentKdIdList.value = kdId
const res = await fetchTrainKnowledge({
knowledgeInfoId: currentKnowledgeId.value,
kdIds: currentKdIdList.value,
segmentationConfig: {},
})
if (res.code === 0) {
currentStepId.value = 2
}
}
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>
<Check v-show="currentStepId > stepItem.stepId" theme="outline" size="24" fill="#fff" :stroke-width="3" />
</div>
<span
class="text-base"
:class="[currentStepId === stepItem.stepId ? 'text-theme-color' : 'text-gray-font-color']"
>
{{ 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="handleToDataProcess" />
</div>
<div v-if="currentStepId === 2">
<DataSetting :kd-ids="currentKdIdList" @confirm="handleToKnowledgeList" />
</div>
</NScrollbar>
</div>
</div>
</template>
...@@ -3,7 +3,8 @@ import { onMounted, ref } from 'vue' ...@@ -3,7 +3,8 @@ import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { Left } from '@icon-park/vue-next' import { Left } from '@icon-park/vue-next'
import UploadLocalDocument from './upload-local-document/index.vue' import BaseLocalDocument from './upload-local-document/index.vue'
import QALocalDocument from './qa-local-document/index.vue'
import { fetchGetKnowledgeDetail, fetchUpdateKnowledgeInfo } from '@/apis/knowledge' import { fetchGetKnowledgeDetail, fetchUpdateKnowledgeInfo } from '@/apis/knowledge'
import EditKnowledgeModal, { KnowledgeFormDataInterface } from '../components/edit-knowledge-modal.vue' import EditKnowledgeModal, { KnowledgeFormDataInterface } from '../components/edit-knowledge-modal.vue'
...@@ -19,6 +20,7 @@ const updateKnowledgeInfoBtnLoading = ref(false) ...@@ -19,6 +20,7 @@ const updateKnowledgeInfoBtnLoading = ref(false)
const currentKnowledgeData = ref<KnowledgeFormDataInterface>({ const currentKnowledgeData = ref<KnowledgeFormDataInterface>({
knowledgeName: '', knowledgeName: '',
desc: '', desc: '',
knowledgeType: 'Base',
}) })
onMounted(async () => { onMounted(async () => {
...@@ -81,7 +83,9 @@ async function handleUpdateKnowledgeInfo(knowledgeData: KnowledgeFormDataInterfa ...@@ -81,7 +83,9 @@ async function handleUpdateKnowledgeInfo(knowledgeData: KnowledgeFormDataInterfa
</div> </div>
</div> </div>
<UploadLocalDocument v-if="uploadKnowledgeType === 'text-local-document'" /> <BaseLocalDocument v-if="uploadKnowledgeType === 'Base-local-document'" />
<QALocalDocument v-if="uploadKnowledgeType === 'QA-local-document'" />
<EditKnowledgeModal <EditKnowledgeModal
v-model:is-show-modal="isShowEditKnowledgeModal" v-model:is-show-modal="isShowEditKnowledgeModal"
......
...@@ -39,7 +39,7 @@ const stepList = ref<StepItem[]>([ ...@@ -39,7 +39,7 @@ const stepList = ref<StepItem[]>([
onMounted(() => { onMounted(() => {
if (!router.currentRoute.value.params.id) { if (!router.currentRoute.value.params.id) {
window.$message.warning('知识库不存在') window.$message.warning(t('personal_space_module.knowledge_module.not_find_knowledge_message'))
router.replace({ name: 'PersonalSpaceKnowledge' }) router.replace({ name: 'PersonalSpaceKnowledge' })
return return
} }
......
...@@ -152,6 +152,7 @@ function handleLimitUpload(data: { file: UploadFileInfo; fileList: UploadFileInf ...@@ -152,6 +152,7 @@ function handleLimitUpload(data: { file: UploadFileInfo; fileList: UploadFileInf
async function handleUpload(file: any) { async function handleUpload(file: any) {
const formData = new FormData() const formData = new FormData()
formData.append('documentFiles', file.file.file) formData.append('documentFiles', file.file.file)
formData.append('knowledgeType', 'Base')
const fileData = { const fileData = {
id: file.file.id, id: file.file.id,
......
...@@ -71,6 +71,7 @@ async function handleCreateKnowledgeNextStep(createKnowledgeData: KnowledgeFormD ...@@ -71,6 +71,7 @@ async function handleCreateKnowledgeNextStep(createKnowledgeData: KnowledgeFormD
const res = await fetchCreateKnowledge<{ id: number }>({ const res = await fetchCreateKnowledge<{ id: number }>({
knowledgeName: createKnowledgeData.knowledgeName, knowledgeName: createKnowledgeData.knowledgeName,
desc: createKnowledgeData.knowledgeDesc, desc: createKnowledgeData.knowledgeDesc,
knowledgeType: createKnowledgeData.knowledgeType,
}).finally(() => { }).finally(() => {
createKnowledgeBtnLoading.value = false createKnowledgeBtnLoading.value = false
}) })
......
...@@ -28,7 +28,7 @@ export default { ...@@ -28,7 +28,7 @@ export default {
], ],
'font-family-name-quotes': null, 'font-family-name-quotes': null,
'font-family-no-missing-generic-family-keyword': null, 'font-family-no-missing-generic-family-keyword': null,
'scss/at-import-partial-extension': 'always', 'scss/load-partial-extension': 'always',
'alpha-value-notation': 'number', 'alpha-value-notation': 'number',
'selector-class-pattern': null, 'selector-class-pattern': null,
}, },
......
This diff is collapsed.
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