feat: paragraph

This commit is contained in:
wangdan-fit2cloud 2025-06-19 20:32:02 +08:00
parent ba078a9aab
commit 72d9833038
12 changed files with 213 additions and 156 deletions

View File

@ -14,13 +14,29 @@ Object.defineProperty(prefix, 'value', {
}) })
/** /**
* *
* @param knowledge_id, * @param knowledge_id,
* param { * param {
" name": "string", " name": "string",
} }
*/ */
const getDocumentList: (knowledge_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
knowledge_id,
loading,
) => {
return get(`${prefix.value}/${knowledge_id}/document`, undefined, loading)
}
/**
*
* @param knowledge_id,
* param {
"name": "string",
folder_id: "string",
}
*/
const getDocumentPage: ( const getDocumentPage: (
knowledge_id: string, knowledge_id: string,
page: pageRequest, page: pageRequest,
@ -549,15 +565,9 @@ const importLarkDocument: (
return post(`${prefix.value}/lark/${knowledge_id}/import`, data, null, loading) return post(`${prefix.value}/lark/${knowledge_id}/import`, data, null, loading)
} }
// todo
const getAllDocument: (knowledge_id: string, loading?: Ref<boolean>) => Promise<Result<any>> = (
knowledge_id,
loading,
) => {
return get(`${prefix.value}/${knowledge_id}/document`, undefined, loading)
}
export default { export default {
getDocumentList,
getDocumentPage, getDocumentPage,
getDocumentDetail, getDocumentDetail,
putDocument, putDocument,

View File

@ -239,6 +239,11 @@ const putBatchGenerateRelated: (
/** /**
* *
* @param knowledge_id,target_knowledge_id, * @param knowledge_id,target_knowledge_id,
* {
"id_list": [
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
]
}
*/ */
const putMigrateMulParagraph: ( const putMigrateMulParagraph: (
knowledge_id: string, knowledge_id: string,

View File

@ -5,6 +5,7 @@
width="650" width="650"
:close-on-click-modal="false" :close-on-click-modal="false"
:close-on-press-escape="false" :close-on-press-escape="false"
@click.stop
> >
<div class="content-height"> <div class="content-height">
<el-form <el-form

View File

@ -4,15 +4,16 @@ export default {
syncDocument: 'Sync Document', syncDocument: 'Sync Document',
selected: 'Selected', selected: 'Selected',
items: 'Items', items: 'Items',
migrateDocument: 'Migrate to',
searchBar: { searchBar: {
placeholder: 'Search by document name' placeholder: 'Search by document name',
}, },
setting: { setting: {
migration: 'Move', migration: 'Move',
cancelGenerateQuestion: 'Cancel Generating Questions', cancelGenerateQuestion: 'Cancel Generating Questions',
cancelVectorization: 'Cancel Vectorization', cancelVectorization: 'Cancel Vectorization',
cancelGenerate: 'Cancel Generation', cancelGenerate: 'Cancel Generation',
export: 'Export to' export: 'Export to',
}, },
tip: { tip: {
saveMessage: 'Current changes have not been saved. Confirm exit?', saveMessage: 'Current changes have not been saved. Confirm exit?',
@ -21,7 +22,7 @@ export default {
vectorizationSuccess: 'Successful', vectorizationSuccess: 'Successful',
nameMessage: 'Document name cannot be empty!', nameMessage: 'Document name cannot be empty!',
importMessage: 'Successful', importMessage: 'Successful',
migrationSuccess: 'Successful' migrationSuccess: 'Successful',
}, },
upload: { upload: {
selectFile: 'Select File', selectFile: 'Select File',
@ -34,71 +35,71 @@ export default {
errorMessage3: 'File cannot be empty', errorMessage3: 'File cannot be empty',
errorMessage4: 'Up to 50 files can be uploaded at once', errorMessage4: 'Up to 50 files can be uploaded at once',
template: 'Template', template: 'Template',
download: 'Download' download: 'Download',
}, },
fileType: { fileType: {
txt: { txt: {
label: 'Text File', label: 'Text File',
tip1: '1. It is recommended to standardize the segment markers in the file before uploading.', tip1: '1. It is recommended to standardize the segment markers in the file before uploading.',
tip2: '2. Up to 50 files can be uploaded at once, with each file not exceeding 100MB.' tip2: '2. Up to 50 files can be uploaded at once, with each file not exceeding 100MB.',
}, },
table: { table: {
label: 'Table', label: 'Table',
tip1: '1. Click to download the corresponding template and complete the information:', tip1: '1. Click to download the corresponding template and complete the information:',
tip2: '2. The first row must be column headers, and the column headers must be meaningful terms. Each record in the table will be treated as a segment.', tip2: '2. The first row must be column headers, and the column headers must be meaningful terms. Each record in the table will be treated as a segment.',
tip3: '3. Each sheet in the uploaded spreadsheet file will be treated as a document, with the sheet name as the document name.', tip3: '3. Each sheet in the uploaded spreadsheet file will be treated as a document, with the sheet name as the document name.',
tip4: '4. Up to 50 files can be uploaded at once, with each file not exceeding 100MB.' tip4: '4. Up to 50 files can be uploaded at once, with each file not exceeding 100MB.',
}, },
QA: { QA: {
label: 'QA Pairs', label: 'QA Pairs',
tip1: '1. Click to download the corresponding template and complete the information:', tip1: '1. Click to download the corresponding template and complete the information:',
tip2: '2. Each sheet in the uploaded spreadsheet file will be treated as a document, with the sheet name as the document name.', tip2: '2. Each sheet in the uploaded spreadsheet file will be treated as a document, with the sheet name as the document name.',
tip3: '3. Up to 50 files can be uploaded at once, with each file not exceeding 100MB.' tip3: '3. Up to 50 files can be uploaded at once, with each file not exceeding 100MB.',
} },
}, },
setRules: { setRules: {
title: { title: {
setting: 'Set Segment Rules', setting: 'Set Segment Rules',
preview: 'Preview' preview: 'Preview',
}, },
intelligent: { intelligent: {
label: 'Automatic Segmentation (Recommended)', label: 'Automatic Segmentation (Recommended)',
text: 'If you are unsure how to set segmentation rules, it is recommended to use automatic segmentation.' text: 'If you are unsure how to set segmentation rules, it is recommended to use automatic segmentation.',
}, },
advanced: { advanced: {
label: 'Advanced Segmentation', label: 'Advanced Segmentation',
text: 'Users can customize segmentation delimiters, segment length, and cleaning rules based on document standards.' text: 'Users can customize segmentation delimiters, segment length, and cleaning rules based on document standards.',
}, },
patterns: { patterns: {
label: 'Segment Delimiters', label: 'Segment Delimiters',
tooltip: tooltip:
'Recursively split according to the selected symbols in order. If the split result exceeds the segment length, it will be truncated to the segment length.', 'Recursively split according to the selected symbols in order. If the split result exceeds the segment length, it will be truncated to the segment length.',
placeholder: 'Please select' placeholder: 'Please select',
}, },
limit: { limit: {
label: 'Segment Length' label: 'Segment Length',
}, },
with_filter: { with_filter: {
label: 'Auto Clean', label: 'Auto Clean',
text: 'Remove duplicate extra symbols, spaces, blank lines, and tab words.' text: 'Remove duplicate extra symbols, spaces, blank lines, and tab words.',
}, },
checkedConnect: { checkedConnect: {
label: 'Add "Related Questions" section for question-based QA pairs during import.' label: 'Add "Related Questions" section for question-based QA pairs during import.',
} },
}, },
buttons: { buttons: {
prev: 'Previous', prev: 'Previous',
next: 'Next', next: 'Next',
import: 'Start Import', import: 'Start Import',
preview: 'Apply' preview: 'Apply',
}, },
table: { table: {
name: 'Document Name', name: 'Document Name',
char_length: 'Character', char_length: 'Character',
paragraph: 'Segment', paragraph: 'Segment',
all: 'All', all: 'All',
updateTime: 'Update Time' updateTime: 'Update Time',
}, },
fileStatus: { fileStatus: {
label: 'File Status', label: 'File Status',
@ -109,12 +110,12 @@ export default {
GENERATE: 'Generating', GENERATE: 'Generating',
SYNC: 'Syncing', SYNC: 'Syncing',
REVOKE: 'Cancelling', REVOKE: 'Cancelling',
finish: 'Finish' finish: 'Finish',
}, },
enableStatus: { enableStatus: {
label: 'Status', label: 'Status',
enable: 'Enabled', enable: 'Enabled',
close: 'Disabled' close: 'Disabled',
}, },
sync: { sync: {
label: 'Sync', label: 'Sync',
@ -122,7 +123,7 @@ export default {
confirmMessage1: confirmMessage1:
'Syncing will delete existing data and retrieve new data. Please proceed with caution.', 'Syncing will delete existing data and retrieve new data. Please proceed with caution.',
confirmMessage2: 'Cannot sync, please set the document URL first.', confirmMessage2: 'Cannot sync, please set the document URL first.',
successMessage: 'Successful' successMessage: 'Successful',
}, },
delete: { delete: {
confirmTitle1: 'Confirm batch deletion of', confirmTitle1: 'Confirm batch deletion of',
@ -132,31 +133,31 @@ export default {
successMessage: 'Successful', successMessage: 'Successful',
confirmTitle3: 'Confirm deleting document:', confirmTitle3: 'Confirm deleting document:',
confirmMessage1: 'Under this document', confirmMessage1: 'Under this document',
confirmMessage2: 'All segments will be deleted, please operate with caution. ' confirmMessage2: 'All segments will be deleted, please operate with caution. ',
}, },
form: { form: {
source_url: { source_url: {
label: 'Document URL', label: 'Document URL',
placeholder: 'Enter document URL, one per line. Incorrect URL will cause import failure.', placeholder: 'Enter document URL, one per line. Incorrect URL will cause import failure.',
requiredMessage: 'Please enter a document URL' requiredMessage: 'Please enter a document URL',
}, },
selector: { selector: {
label: 'Selector', label: 'Selector',
placeholder: 'Default is body, you can input .classname/#idname/tagname' placeholder: 'Default is body, you can input .classname/#idname/tagname',
}, },
hit_handling_method: { hit_handling_method: {
label: 'Retrieve-Respond', label: 'Retrieve-Respond',
tooltip: 'When user asks a question, handle matched segments according to the set method.' tooltip: 'When user asks a question, handle matched segments according to the set method.',
}, },
similarity: { similarity: {
label: 'Similarity Higher Than', label: 'Similarity Higher Than',
placeholder: 'Directly return segment content', placeholder: 'Directly return segment content',
requiredMessage: 'Please enter similarity value' requiredMessage: 'Please enter similarity value',
} },
}, },
hitHandlingMethod: { hitHandlingMethod: {
optimization: 'Model optimization', optimization: 'Model optimization',
directly_return: 'Respond directly' directly_return: 'Respond directly',
}, },
generateQuestion: { generateQuestion: {
title: 'Generate Questions', title: 'Generate Questions',
@ -167,12 +168,12 @@ export default {
tip4: 'The generation effect depends on the selected model and prompt. Users can adjust to achieve the best effect.', tip4: 'The generation effect depends on the selected model and prompt. Users can adjust to achieve the best effect.',
prompt1: prompt1:
'Content: {data}\n \n Please summarize the above and generate 5 questions based on the summary. \nAnswer requirements: \n - Please output only questions; \n - Please place each question in', 'Content: {data}\n \n Please summarize the above and generate 5 questions based on the summary. \nAnswer requirements: \n - Please output only questions; \n - Please place each question in',
prompt2: 'tag.' prompt2: 'tag.',
}, },
feishu: { feishu: {
selectDocument: 'Select Document', selectDocument: 'Select Document',
tip1: 'Only documents and tables are supported. Documents will be segmented based on titles, and tables will be converted to Markdown format before segmentation.', tip1: 'Only documents and tables are supported. Documents will be segmented based on titles, and tables will be converted to Markdown format before segmentation.',
tip2: 'The system does not store the original document. Before importing the document, it is recommended to standardize the document segmentation markers.', tip2: 'The system does not store the original document. Before importing the document, it is recommended to standardize the document segmentation markers.',
allCheck: 'Select All' allCheck: 'Select All',
} },
} }

View File

@ -4,6 +4,7 @@ export default {
syncDocument: '同步文档', syncDocument: '同步文档',
selected: '已选', selected: '已选',
items: '项', items: '项',
migrateDocument:'文档迁移到',
searchBar: { searchBar: {
placeholder: '按 文档名称 搜索' placeholder: '按 文档名称 搜索'
}, },

View File

@ -4,8 +4,9 @@ export default {
syncDocument: '同步文檔', syncDocument: '同步文檔',
selected: '已選', selected: '已選',
items: '項', items: '項',
migrateDocument: '文檔遷移到',
searchBar: { searchBar: {
placeholder: '按 文檔名稱 搜索' placeholder: '按 文檔名稱 搜索',
}, },
setting: { setting: {
migration: '遷移', migration: '遷移',
@ -21,7 +22,7 @@ export default {
vectorizationSuccess: '批量向量化成功', vectorizationSuccess: '批量向量化成功',
nameMessage: '文件名稱不能为空!', nameMessage: '文件名稱不能为空!',
importMessage: '導入成功', importMessage: '導入成功',
migrationSuccess: '遷移成功' migrationSuccess: '遷移成功',
}, },
upload: { upload: {
selectFile: '選擇文件', selectFile: '選擇文件',
@ -34,70 +35,70 @@ export default {
errorMessage3: '文件不能为空', errorMessage3: '文件不能为空',
errorMessage4: '每次最多上傳50個文件', errorMessage4: '每次最多上傳50個文件',
template: '模板', template: '模板',
download: '下載' download: '下載',
}, },
fileType: { fileType: {
txt: { txt: {
label: '文本文件', label: '文本文件',
tip1: '1、文件上傳前建議規範文件的分段標識', tip1: '1、文件上傳前建議規範文件的分段標識',
tip2: '2、每次最多上傳 50 個文件,每個文件不超过 100MB' tip2: '2、每次最多上傳 50 個文件,每個文件不超过 100MB',
}, },
table: { table: {
label: '表格', label: '表格',
tip1: '1、點擊下載對應模板並完善信息', tip1: '1、點擊下載對應模板並完善信息',
tip2: '2、第一行必須是列標題且列標題必須是有意義的術語表中每條記錄將作為一個分段', tip2: '2、第一行必須是列標題且列標題必須是有意義的術語表中每條記錄將作為一個分段',
tip3: '3、上傳的表格文件中每個 sheet 會作為一個文檔sheet 名稱為文檔名稱', tip3: '3、上傳的表格文件中每個 sheet 會作為一個文檔sheet 名稱為文檔名稱',
tip4: '4、每次最多上傳 50 個文件,每個文件不超过 100MB' tip4: '4、每次最多上傳 50 個文件,每個文件不超过 100MB',
}, },
QA: { QA: {
label: 'QA 問答對', label: 'QA 問答對',
tip1: '1、點擊下載對應模板並完善信息', tip1: '1、點擊下載對應模板並完善信息',
tip2: '2、上傳的表格文件中每個 sheet 會作為一個文檔sheet 名稱為文檔名稱', tip2: '2、上傳的表格文件中每個 sheet 會作為一個文檔sheet 名稱為文檔名稱',
tip3: '3、每次最多上傳 50 個文件,每個文件不超过 100MB' tip3: '3、每次最多上傳 50 個文件,每個文件不超过 100MB',
} },
}, },
setRules: { setRules: {
title: { title: {
setting: '設置分段規則', setting: '設置分段規則',
preview: '分段預覽' preview: '分段預覽',
}, },
intelligent: { intelligent: {
label: '智能分段(推薦)', label: '智能分段(推薦)',
text: '不了解如何設置分段規則推薦使用智能分段' text: '不了解如何設置分段規則推薦使用智能分段',
}, },
advanced: { advanced: {
label: '高級分段', label: '高級分段',
text: '用戶可根據文檔規範自行設置分段標識符、分段長度以及清洗規則' text: '用戶可根據文檔規範自行設置分段標識符、分段長度以及清洗規則',
}, },
patterns: { patterns: {
label: '分段標識', label: '分段標識',
tooltip: '按照所選符號先後順序做遞歸分割,分割結果超出分段長度將截取至分段長度。', tooltip: '按照所選符號先後順序做遞歸分割,分割結果超出分段長度將截取至分段長度。',
placeholder: '請選擇' placeholder: '請選擇',
}, },
limit: { limit: {
label: '分段長度' label: '分段長度',
}, },
with_filter: { with_filter: {
label: '自動清洗', label: '自動清洗',
text: '去掉重複多餘符號空格、空行、制表符' text: '去掉重複多餘符號空格、空行、制表符',
}, },
checkedConnect: { checkedConnect: {
label: '導入時添加分段標題為關聯問題(適用於標題為問題的問答對)' label: '導入時添加分段標題為關聯問題(適用於標題為問題的問答對)',
} },
}, },
buttons: { buttons: {
prev: '上一步', prev: '上一步',
next: '下一步', next: '下一步',
import: '開始導入', import: '開始導入',
preview: '生成預覽' preview: '生成預覽',
}, },
table: { table: {
name: '文件名稱', name: '文件名稱',
char_length: '字符數', char_length: '字符數',
paragraph: '分段', paragraph: '分段',
all: '全部', all: '全部',
updateTime: '更新時間' updateTime: '更新時間',
}, },
fileStatus: { fileStatus: {
label: '文件狀態', label: '文件狀態',
@ -108,19 +109,19 @@ export default {
GENERATE: '生成中', GENERATE: '生成中',
SYNC: '同步中', SYNC: '同步中',
REVOKE: '取消中', REVOKE: '取消中',
finish: '完圓' finish: '完圓',
}, },
enableStatus: { enableStatus: {
label: '啟用狀態', label: '啟用狀態',
enable: '開啟', enable: '開啟',
close: '關閉' close: '關閉',
}, },
sync: { sync: {
label: '同步', label: '同步',
confirmTitle: '確認同步文檔?', confirmTitle: '確認同步文檔?',
confirmMessage1: '同步將刪除已有數據重新獲取新數據,請謹慎操作。', confirmMessage1: '同步將刪除已有數據重新獲取新數據,請謹慎操作。',
confirmMessage2: '無法同步,請先去設置文檔 URL地址', confirmMessage2: '無法同步,請先去設置文檔 URL地址',
successMessage: '同步文檔成功' successMessage: '同步文檔成功',
}, },
delete: { delete: {
confirmTitle1: '是否批量刪除', confirmTitle1: '是否批量刪除',
@ -129,31 +130,31 @@ export default {
successMessage: '批量刪除成功', successMessage: '批量刪除成功',
confirmTitle3: '是否刪除文檔:', confirmTitle3: '是否刪除文檔:',
confirmMessage1: '此文檔下的', confirmMessage1: '此文檔下的',
confirmMessage2: '個分段都會被刪除,請謹慎操作。' confirmMessage2: '個分段都會被刪除,請謹慎操作。',
}, },
form: { form: {
source_url: { source_url: {
label: '文檔地址', label: '文檔地址',
placeholder: '請輸入文檔地址,一行一個,地址不正確文檔會導入失敗。', placeholder: '請輸入文檔地址,一行一個,地址不正確文檔會導入失敗。',
requiredMessage: '請輸入文檔地址' requiredMessage: '請輸入文檔地址',
}, },
selector: { selector: {
label: '選擇器', label: '選擇器',
placeholder: '默認為 body可輸入 .classname/#idname/tagname' placeholder: '默認為 body可輸入 .classname/#idname/tagname',
}, },
hit_handling_method: { hit_handling_method: {
label: '命中處理方式', label: '命中處理方式',
tooltip: '用戶提問時,命中文檔下的分段時按照設置的方式進行處理。' tooltip: '用戶提問時,命中文檔下的分段時按照設置的方式進行處理。',
}, },
similarity: { similarity: {
label: '相似度高于', label: '相似度高于',
placeholder: '直接返回分段内容', placeholder: '直接返回分段内容',
requiredMessage: '请输入相似度' requiredMessage: '请输入相似度',
}, },
}, },
hitHandlingMethod: { hitHandlingMethod: {
optimization: '模型優化', optimization: '模型優化',
directly_return: '直接回答' directly_return: '直接回答',
}, },
generateQuestion: { generateQuestion: {
title: '生成問題', title: '生成問題',
@ -169,6 +170,6 @@ export default {
selectDocument: '選擇文檔', selectDocument: '選擇文檔',
tip1: '僅支持文檔和表格類型文檔會根據標題分段表格會轉為Markdown格式後再分段。', tip1: '僅支持文檔和表格類型文檔會根據標題分段表格會轉為Markdown格式後再分段。',
tip2: '系統不存儲原始文檔,導入文檔前,建議規範文檔的分段標識。', tip2: '系統不存儲原始文檔,導入文檔前,建議規範文檔的分段標識。',
allCheck: '全選' allCheck: '全選',
} },
} }

View File

@ -5,10 +5,10 @@ import { type Ref } from 'vue'
const useDocumentStore = defineStore('document', { const useDocumentStore = defineStore('document', {
state: () => ({}), state: () => ({}),
actions: { actions: {
async asyncGetAllDocument(id: string, loading?: Ref<boolean>) { async asyncGetKnowledgeDocument(id: string, loading?: Ref<boolean>) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
documentApi documentApi
.getAllDocument(id, loading) .getDocumentList(id, loading)
.then((res) => { .then((res) => {
resolve(res) resolve(res)
}) })

View File

@ -1,29 +1,22 @@
<template> <template>
<el-dialog <el-dialog
:title="$t('views.chatLog.selectKnowledge')" :title="`${$t('views.document.migrateDocument')}`"
v-model="dialogVisible" v-model="dialogVisible"
width="600" width="600"
class="select-knowledge-dialog" class="select-knowledge-dialog"
:close-on-click-modal="false" :close-on-click-modal="false"
:close-on-press-escape="false" :close-on-press-escape="false"
> >
<template #header="{ titleId, titleClass }"> <el-form ref="FormRef" :model="form" label-position="top" require-asterisk-position="right">
<h4 :id="titleId" :class="titleClass">{{ '文档迁移到' }}</h4>
</template>
<el-form
ref="FormRef"
:model="form"
label-position="top"
require-asterisk-position="right"
>
<el-form-item :label="$t('views.chatLog.selectKnowledge')" required> <el-form-item :label="$t('views.chatLog.selectKnowledge')" required>
<el-tree-select <el-tree-select
v-model="form.selectKnowledge" v-model="form.selectKnowledge"
:props="defaultProps" :props="defaultProps"
node-key="id" node-key="id"
:default-expanded-keys="['default']"
lazy lazy
:load="loadTree" :load="loadTree"
:placeholder="$t('views.chatLog.selectKnowledgePlaceholder')"
:loading="loading"
> >
<template #default="{ data }"> <template #default="{ data }">
<div class="flex align-center"> <div class="flex align-center">
@ -81,6 +74,9 @@ const loading = ref<boolean>(false)
const dialogVisible = ref<boolean>(false) const dialogVisible = ref<boolean>(false)
const knowledgeList = ref<any>([]) const knowledgeList = ref<any>([])
const documentList = ref<any>([]) const documentList = ref<any>([])
const form = ref<any>({
selectKnowledge: '',
})
const defaultProps = { const defaultProps = {
children: 'children', children: 'children',
@ -91,9 +87,14 @@ const defaultProps = {
}, },
} }
const form = ref<any>({ const loadTree = (node: any, resolve: any) => {
selectKnowledge: '', console.log(node)
}) if (node.isLeaf) return resolve([])
const folder_id = node.level === 0 ? '' : node.data.id
knowledge.asyncGetFolderKnowledge(folder_id, loading).then((res: any) => {
resolve(res.data)
})
}
watch(dialogVisible, (bool) => { watch(dialogVisible, (bool) => {
if (!bool) { if (!bool) {
@ -108,14 +109,6 @@ const open = (list: any) => {
dialogVisible.value = true dialogVisible.value = true
} }
const loadTree = (node: any, resolve: any) => {
console.log(node)
if (node.isLeaf) return resolve([])
const folder_id = node.level === 0 ? '' : node.data.id
knowledge.asyncGetFolderKnowledge(folder_id, loading).then((res: any) => {
resolve(res.data)
})
}
const submitHandle = () => { const submitHandle = () => {
documentApi documentApi
.putMigrateMulDocument(id, form.value.selectKnowledge, documentList.value, loading) .putMigrateMulDocument(id, form.value.selectKnowledge, documentList.value, loading)

View File

@ -7,7 +7,7 @@
@click.stop="editParagraph(data)" @click.stop="editParagraph(data)"
> >
<h2 class="mb-16">{{ data.title || '-' }}</h2> <h2 class="mb-16">{{ data.title || '-' }}</h2>
<div v-show="show" class="mk-sticky"> <div v-show="show" class="mk-sticky" v-if="!disabled">
<el-card <el-card
class="paragraph-box-operation mt-8 mr-8" class="paragraph-box-operation mt-8 mr-8"
shadow="always" shadow="always"
@ -79,7 +79,6 @@ import GenerateRelatedDialog from '@/components/generate-related-dialog/index.vu
import ParagraphDialog from '@/views/paragraph/component/ParagraphDialog.vue' import ParagraphDialog from '@/views/paragraph/component/ParagraphDialog.vue'
import SelectDocumentDialog from '@/views/paragraph/component/SelectDocumentDialog.vue' import SelectDocumentDialog from '@/views/paragraph/component/SelectDocumentDialog.vue'
import { MsgSuccess, MsgConfirm } from '@/utils/message' import { MsgSuccess, MsgConfirm } from '@/utils/message'
import { elPaginationKey } from 'element-plus'
const { paragraph } = useStore() const { paragraph } = useStore()
@ -89,9 +88,10 @@ const {
} = route as any } = route as any
const props = defineProps<{ const props = defineProps<{
data: any data: any
disabled?: boolean
}>() }>()
const emit = defineEmits(['changeState', 'deleteParagraph']) const emit = defineEmits(['changeState', 'deleteParagraph', 'refresh', 'refreshMigrateParagraph'])
const loading = ref(false) const loading = ref(false)
const changeStateloading = ref(false) const changeStateloading = ref(false)
const show = ref(false) const show = ref(false)
@ -127,13 +127,6 @@ function openGenerateDialog(row: any) {
GenerateRelatedDialogRef.value.open([], 'paragraph', row.id) GenerateRelatedDialogRef.value.open([], 'paragraph', row.id)
} }
} }
function openSelectDocumentDialog(row?: any) {
// if (row) {
// multipleSelection.value = [row.id]
// }
// SelectDocumentDialogRef.value.open(multipleSelection.value)
}
function deleteParagraph(row: any) { function deleteParagraph(row: any) {
MsgConfirm( MsgConfirm(
`${t('views.paragraph.delete.confirmTitle')} ${row.title || '-'} ?`, `${t('views.paragraph.delete.confirmTitle')} ${row.title || '-'} ?`,
@ -151,17 +144,28 @@ function deleteParagraph(row: any) {
}) })
.catch(() => {}) .catch(() => {})
} }
const SelectDocumentDialogRef = ref()
const ParagraphDialogRef = ref() const ParagraphDialogRef = ref()
const title = ref('') const title = ref('')
function editParagraph(row: any) { function editParagraph(row: any) {
if (!props.disabled) {
title.value = t('views.paragraph.paragraphDetail') title.value = t('views.paragraph.paragraphDetail')
ParagraphDialogRef.value.open(row) ParagraphDialogRef.value.open(row)
}
} }
function refresh() {} const SelectDocumentDialogRef = ref()
function openSelectDocumentDialog(row?: any) {
SelectDocumentDialogRef.value.open([row.id])
}
function refreshMigrateParagraph() {} function refresh(data?: any) {
emit('refresh', data)
}
function refreshMigrateParagraph() {
emit('refreshMigrateParagraph', props.data)
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.paragraph-box { .paragraph-box {

View File

@ -12,7 +12,7 @@
<el-col :span="18"> <el-col :span="18">
<el-scrollbar height="500" wrap-class="paragraph-scrollbar"> <el-scrollbar height="500" wrap-class="paragraph-scrollbar">
<div class="p-24" style="padding-bottom: 8px"> <div class="p-24" style="padding-bottom: 8px">
<div style="position: absolute; right: 20px; top: 20px; "> <div style="position: absolute; right: 20px; top: 20px">
<el-button text @click="isEdit = true" v-if="problemId && !isEdit"> <el-button text @click="isEdit = true" v-if="problemId && !isEdit">
<el-icon><EditPen /></el-icon> <el-icon><EditPen /></el-icon>
</el-button> </el-button>
@ -22,9 +22,9 @@
</div> </div>
</el-scrollbar> </el-scrollbar>
<div class="text-right p-24 pt-0" v-if="problemId && isEdit"> <div class="text-right p-24 pt-0" v-if="problemId && isEdit">
<el-button @click.prevent="cancelEdit"> {{$t('common.cancel')}} </el-button> <el-button @click.prevent="cancelEdit"> {{ $t('common.cancel') }} </el-button>
<el-button type="primary" :disabled="loading" @click="handleDebounceClick"> <el-button type="primary" :disabled="loading" @click="handleDebounceClick">
{{$t('common.save')}} {{ $t('common.save') }}
</el-button> </el-button>
</div> </div>
</el-col> </el-col>
@ -40,9 +40,9 @@
</el-row> </el-row>
<template #footer v-if="!problemId"> <template #footer v-if="!problemId">
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click.prevent="dialogVisible = false"> {{$t('common.cancel')}} </el-button> <el-button @click.prevent="dialogVisible = false"> {{ $t('common.cancel') }} </el-button>
<el-button :disabled="loading" type="primary" @click="handleDebounceClick"> <el-button :disabled="loading" type="primary" @click="handleDebounceClick">
{{$t('common.submit')}} {{ $t('common.submit') }}
</el-button> </el-button>
</span> </span>
</template> </template>
@ -58,14 +58,14 @@ import paragraphApi from '@/api/knowledge/paragraph'
import useStore from '@/stores' import useStore from '@/stores'
const props = defineProps({ const props = defineProps({
title: String title: String,
}) })
const { paragraph } = useStore() const { paragraph } = useStore()
const route = useRoute() const route = useRoute()
const { const {
params: { id, documentId } params: { id, documentId },
} = route as any } = route as any
const emit = defineEmits(['refresh']) const emit = defineEmits(['refresh'])
@ -122,7 +122,7 @@ const submitHandle = async () => {
documentId || document_id.value, documentId || document_id.value,
problemId.value, problemId.value,
paragraphFormRef.value?.form, paragraphFormRef.value?.form,
loading loading,
) )
.then((res: any) => { .then((res: any) => {
isEdit.value = false isEdit.value = false
@ -133,7 +133,7 @@ const submitHandle = async () => {
ProblemRef.value.problemList.length > 0 ProblemRef.value.problemList.length > 0
? { ? {
problem_list: ProblemRef.value.problemList, problem_list: ProblemRef.value.problemList,
...paragraphFormRef.value?.form ...paragraphFormRef.value?.form,
} }
: paragraphFormRef.value?.form : paragraphFormRef.value?.form
paragraphApi.postParagraph(id, documentId, obj, loading).then((res) => { paragraphApi.postParagraph(id, documentId, obj, loading).then((res) => {

View File

@ -5,6 +5,7 @@
width="500" width="500"
:close-on-click-modal="false" :close-on-click-modal="false"
:close-on-press-escape="false" :close-on-press-escape="false"
@click.stop
> >
<el-form <el-form
ref="formRef" ref="formRef"
@ -14,22 +15,37 @@
:rules="rules" :rules="rules"
@submit.prevent @submit.prevent
> >
<el-form-item :label="$t('views.chatLog.selectKnowledge')" prop="dataset_id"> <el-form-item :label="$t('views.chatLog.selectKnowledge')" prop="knowledge_id">
<el-select <el-tree-select
v-model="form.dataset_id" v-model="form.knowledge_id"
filterable :props="defaultProps"
node-key="id"
lazy
:load="loadTree"
:placeholder="$t('views.chatLog.selectKnowledgePlaceholder')" :placeholder="$t('views.chatLog.selectKnowledgePlaceholder')"
@change="changeKnowledge"
:loading="optionLoading" :loading="optionLoading"
@change="changeDataset"
> >
<el-option v-for="item in datasetList" :key="item.id" :label="item.name" :value="item.id"> <template #default="{ data }">
<span class="flex align-center"> <div class="flex align-center">
<KnowledgeIcon v-if="!item.dataset_id" :type="item.type" /> <KnowledgeIcon
class="mr-12"
:size="20"
v-if="data.resource_type !== 'folder'"
:type="data.type"
/>
<el-avatar v-else class="mr-12" shape="square" :size="20" style="background: none">
<img
src="@/assets/knowledge/icon_file-folder_colorful.svg"
style="width: 100%"
alt=""
/>
</el-avatar>
{{ item.name }} {{ data.name }}
</span> </div>
</el-option> </template>
</el-select> </el-tree-select>
</el-form-item> </el-form-item>
<el-form-item :label="$t('views.chatLog.saveToDocument')" prop="document_id"> <el-form-item :label="$t('views.chatLog.saveToDocument')" prop="document_id">
<el-select <el-select
@ -70,7 +86,7 @@ const { knowledge, document } = useStore()
const route = useRoute() const route = useRoute()
const { const {
params: { id, documentId }, params: { id, documentId }, // idknowledgeID
} = route as any } = route as any
const emit = defineEmits(['refresh']) const emit = defineEmits(['refresh'])
@ -80,18 +96,19 @@ const dialogVisible = ref<boolean>(false)
const loading = ref(false) const loading = ref(false)
const form = ref<any>({ const form = ref<any>({
dataset_id: '', knowledge_id: '',
document_id: '', document_id: '',
}) })
const rules = reactive<FormRules>({ const rules = reactive<FormRules>({
dataset_id: [ knowledge_id: [
{ required: true, message: t('views.chatLog.selectKnowledgePlaceholder'), trigger: 'change' }, { required: true, message: t('views.chatLog.selectKnowledgePlaceholder'), trigger: 'change' },
], ],
document_id: [{ required: true, message: t('views.chatLog.documentPlaceholder'), trigger: 'change' }], document_id: [
{ required: true, message: t('views.chatLog.documentPlaceholder'), trigger: 'change' },
],
}) })
const datasetList = ref<any[]>([])
const documentList = ref<any[]>([]) const documentList = ref<any[]>([])
const optionLoading = ref(false) const optionLoading = ref(false)
const paragraphList = ref<string[]>([]) const paragraphList = ref<string[]>([])
@ -99,36 +116,46 @@ const paragraphList = ref<string[]>([])
watch(dialogVisible, (bool) => { watch(dialogVisible, (bool) => {
if (!bool) { if (!bool) {
form.value = { form.value = {
dataset_id: '', knowledge_id: '',
document_id: '', document_id: '',
} }
datasetList.value = []
documentList.value = [] documentList.value = []
paragraphList.value = [] paragraphList.value = []
formRef.value?.clearValidate() formRef.value?.clearValidate()
} }
}) })
function changeDataset(id: string) { const defaultProps = {
children: 'children',
label: 'name',
isLeaf: (data: any) => data.resource_type && data.resource_type !== 'folder',
disabled: (data: any, node: any) => {
return data.id === id
},
}
const loadTree = (node: any, resolve: any) => {
console.log(node)
if (node.isLeaf) return resolve([])
const folder_id = node.level === 0 ? '' : node.data.id
knowledge.asyncGetFolderKnowledge(folder_id, optionLoading).then((res: any) => {
resolve(res.data)
})
}
function changeKnowledge(id: string) {
form.value.document_id = '' form.value.document_id = ''
getDocument(id) getDocument(id)
} }
function getDocument(id: string) { function getDocument(id: string) {
document.asyncGetAllDocument(id, loading).then((res: any) => { document.asyncGetKnowledgeDocument(id, optionLoading).then((res: any) => {
documentList.value = res.data?.filter((v: any) => v.id !== documentId) documentList.value = res.data?.filter((v: any) => v.id !== documentId)
}) })
} }
function getDataset() {
knowledge.asyncGetFolderKnowledge(loading).then((res: any) => {
datasetList.value = res.data
})
}
const open = (list: any) => { const open = (list: any) => {
paragraphList.value = list paragraphList.value = list
getDataset()
formRef.value?.clearValidate() formRef.value?.clearValidate()
dialogVisible.value = true dialogVisible.value = true
} }
@ -136,13 +163,16 @@ const submitForm = async (formEl: FormInstance | undefined) => {
if (!formEl) return if (!formEl) return
await formEl.validate((valid, fields) => { await formEl.validate((valid, fields) => {
if (valid) { if (valid) {
const obj = {
id_list: paragraphList.value,
}
paragraphApi paragraphApi
.putMigrateMulParagraph( .putMigrateMulParagraph(
id, id,
documentId, documentId,
form.value.dataset_id, form.value.knowledge_id,
form.value.document_id, form.value.document_id,
paragraphList.value, obj,
loading, loading,
) )
.then(() => { .then(() => {

View File

@ -83,21 +83,27 @@
ghostClass="ghost" ghostClass="ghost"
> >
<template v-for="(item, index) in paragraphDetail" :key="item.id"> <template v-for="(item, index) in paragraphDetail" :key="item.id">
<div :id="`m${item.id}`" style="display: flex; margin-bottom: 16px"> <div :id="`m${item.id}`" class="flex mb-16">
<!-- 批量操作 --> <!-- 批量操作 -->
<div class="paragraph-card flex" v-if="isBatch === true"> <div class="paragraph-card flex w-full" v-if="isBatch === true">
<el-checkbox :value="item.id" /> <el-checkbox :value="item.id" />
<ParagraphCard :data="item" class="mb-8 w-full" /> <ParagraphCard
:data="item"
class="mb-8 w-full"
@refresh="refresh"
@refreshMigrateParagraph="refreshMigrateParagraph"
:disabled="true"
/>
</div> </div>
<!-- 非批量操作 --> <!-- 非批量操作 -->
<div class="handle paragraph-card flex" :id="item.id" v-else> <div class="handle paragraph-card flex w-full" :id="item.id" v-else>
<img <img
src="@/assets/sort.svg" src="@/assets/sort.svg"
alt="" alt=""
height="15" height="15"
class="handle-img mr-8 mt-24 cursor" class="handle-img mr-8 mt-24 cursor"
/> />
</div>
<ParagraphCard <ParagraphCard
:data="item" :data="item"
class="mb-8 w-full" class="mb-8 w-full"
@ -105,6 +111,7 @@
@deleteParagraph="deleteParagraph" @deleteParagraph="deleteParagraph"
/> />
</div> </div>
</div>
</template> </template>
</VueDraggable> </VueDraggable>
</InfiniteScroll> </InfiniteScroll>
@ -151,6 +158,7 @@ import { VueDraggable } from 'vue-draggable-plus'
import { MsgSuccess, MsgConfirm } from '@/utils/message' import { MsgSuccess, MsgConfirm } from '@/utils/message'
import useStore from '@/stores' import useStore from '@/stores'
import { t } from '@/locales' import { t } from '@/locales'
import disable$ from 'dingtalk-jsapi/api/ui/pullToRefresh/disable'
const { paragraph } = useStore() const { paragraph } = useStore()
const route = useRoute() const route = useRoute()
const { const {
@ -192,7 +200,10 @@ function changeState(id: string) {
paragraphDetail.value[index].is_active = !paragraphDetail.value[index].is_active paragraphDetail.value[index].is_active = !paragraphDetail.value[index].is_active
} }
function refreshMigrateParagraph() { function refreshMigrateParagraph(data: any) {
if (data) {
multipleSelection.value = data
}
paragraphDetail.value = paragraphDetail.value.filter( paragraphDetail.value = paragraphDetail.value.filter(
(v) => !multipleSelection.value.includes(v.id), (v) => !multipleSelection.value.includes(v.id),
) )