diff --git a/ui/src/api/dataset.ts b/ui/src/api/dataset.ts index d2599041f..4223cb1e6 100644 --- a/ui/src/api/dataset.ts +++ b/ui/src/api/dataset.ts @@ -4,18 +4,19 @@ import type { datasetData } from '@/api/type/dataset' import type { pageRequest } from '@/api/type/common' import type { ApplicationFormType } from '@/api/type/application' import { type Ref } from 'vue' + const prefix = '/dataset' /** * 获取分页知识库 - * @param 参数 + * @param 参数 * page { - "current_page": "string", - "page_size": "string", - } + "current_page": "string", + "page_size": "string", + } * param { - "name": "string", - } + "name": "string", + } */ const getDataset: ( page: pageRequest, @@ -46,28 +47,28 @@ const delDataset: (dataset_id: String, loading?: Ref) => Promise) => Promise> = ( data, @@ -78,13 +79,13 @@ const postDataset: (data: datasetData, loading?: Ref) => Promise) => Promise> = ( data, @@ -92,15 +93,32 @@ const postWebDataset: (data: any, loading?: Ref) => Promise ) => { return post(`${prefix}/web`, data, undefined, loading) } +/** + * 创建Lark知识库 + * @param 参数 + * { + "name": "string", + "desc": "string", + "app_id": "string", + "app_secret": "string", + "folder_token": "string", + } + */ +const postLarkDataset: (data: any, loading?: Ref) => Promise> = ( + data, + loading +) => { + return post(`${prefix}/lark/save`, data, undefined, loading) +} /** * 创建QA知识库 - * @param 参数 formData + * @param 参数 formData * { - "file": "file", - "name": "string", - "desc": "string", - } + "file": "file", + "name": "string", + "desc": "string", + } */ const postQADataset: (data: any, loading?: Ref) => Promise> = ( data, @@ -122,12 +140,12 @@ const getDatasetDetail: (dataset_id: string, loading?: Ref) => Promise< /** * 修改知识库信息 - * @param 参数 + * @param 参数 * dataset_id * { - "name": "string", - "desc": true - } + "name": "string", + "desc": true + } */ const putDataset: ( dataset_id: string, @@ -136,6 +154,13 @@ const putDataset: ( ) => Promise> = (dataset_id, data, loading) => { return put(`${prefix}/${dataset_id}`, data, undefined, loading) } +const putLarkDataset: ( + dataset_id: string, + data: any, + loading?: Ref +) => Promise> = (dataset_id, data, loading) => { + return put(`${prefix}/lark/${dataset_id}`, data, undefined, loading) +} /** * 获取知识库 可关联的应用列表 * @param dataset_id @@ -229,6 +254,29 @@ const getDatasetModel: ( ) => Promise>> = (dataset_id, loading) => { return get(`${prefix}/${dataset_id}/model`, loading) } +/** + * 获取飞书文档列表 + * @param dataset_id + * @param folder_token + * @param loading + * @returns + */ +const getLarkDocumentList: ( + dataset_id: string, + folder_token: string, + data: any, + loading?: Ref +) => Promise>> = (dataset_id, folder_token, data, loading) => { + return post(`${prefix}/lark/${dataset_id}/${folder_token}/doc_list`, data, null, loading) +} + +const importLarkDocument: ( + dataset_id: string, + data: any, + loading?: Ref +) => Promise>> = (dataset_id, data, loading) => { + return post(`${prefix}/lark/${dataset_id}/import`, data, null, loading) +} export default { getDataset, @@ -245,5 +293,9 @@ export default { postQADataset, exportDataset, getDatasetModel, - exportZipDataset + exportZipDataset, + postLarkDataset, + getLarkDocumentList, + importLarkDocument, + putLarkDataset } diff --git a/ui/src/api/document.ts b/ui/src/api/document.ts index 307eb3488..ad4792af7 100644 --- a/ui/src/api/document.ts +++ b/ui/src/api/document.ts @@ -181,6 +181,18 @@ const putDocumentSync: ( ) => Promise> = (dataset_id, document_id, loading) => { return put(`${prefix}/${dataset_id}/document/${document_id}/sync`, undefined, undefined, loading) } +const putLarkDocumentSync: ( + dataset_id: string, + document_id: string, + loading?: Ref +) => Promise> = (dataset_id, document_id, loading) => { + return put( + `${prefix}/lark/${dataset_id}/document/${document_id}/sync`, + undefined, + undefined, + loading + ) +} /** * 批量同步文档 @@ -193,6 +205,13 @@ const delMulSyncDocument: ( ) => Promise> = (dataset_id, data, loading) => { return put(`${prefix}/${dataset_id}/document/_bach`, { id_list: data }, undefined, loading) } +const delMulLarkSyncDocument: ( + dataset_id: string, + data: any, + loading?: Ref +) => Promise> = (dataset_id, data, loading) => { + return put(`${prefix}/lark/${dataset_id}/_batch`, { id_list: data }, undefined, loading) +} /** * 创建Web站点文档 @@ -394,5 +413,7 @@ export default { batchGenerateRelated, cancelTask, exportDocumentZip, - batchCancelTask + batchCancelTask, + putLarkDocumentSync, + delMulLarkSyncDocument } diff --git a/ui/src/locales/lang/en-US/views/application.ts b/ui/src/locales/lang/en-US/views/application.ts index ebc4ef7cd..4ae10790f 100644 --- a/ui/src/locales/lang/en-US/views/application.ts +++ b/ui/src/locales/lang/en-US/views/application.ts @@ -210,7 +210,8 @@ export default { appSecretPlaceholder: 'Please enter APP secret', verificationTokenPlaceholder: 'Please enter verification token', urlInfo: - '-Events and callbacks - event configuration - configure the "request address" of the subscription method' + '-Events and callbacks - event configuration - configure the "request address" of the subscription method', + folderTokenPlaceholder: 'Please enter folder token' }, slackSetting: { title: 'Slack Configuration', diff --git a/ui/src/locales/lang/en-US/views/dataset.ts b/ui/src/locales/lang/en-US/views/dataset.ts index 7764c7c54..45cecf6cc 100644 --- a/ui/src/locales/lang/en-US/views/dataset.ts +++ b/ui/src/locales/lang/en-US/views/dataset.ts @@ -49,13 +49,21 @@ export default { datasetType: { label: 'Type', generalInfo: 'Upload local documents', - webInfo: 'Sync text data from a web site' + webInfo: 'Sync text data from a web site', + larkInfo: 'Sync documents from Feishu', + yuqueInfo: 'Sync documents from Yuque' }, source_url: { label: 'Web Root URL', placeholder: 'Please enter the web root URL', requiredMessage: 'Please enter the web root URL' }, + user_id: { + requiredMessage: 'Please enter User ID' + }, + token: { + requiredMessage: 'Please enter Token' + }, selector: { label: 'Selector', placeholder: 'Default is body, can input .classname/#idname/tagname' @@ -79,8 +87,7 @@ export default { replace: 'Replace Sync', replaceText: 'Re-fetch Web site documents, replacing the documents in the local knowledge', complete: 'Full Sync', - completeText: - 'Delete all documents in the local knowledge and re-fetch web site documents', + completeText: 'Delete all documents in the local knowledge and re-fetch web site documents', tip: 'Note: All syncs will delete existing data and re-fetch new data. Please proceed with caution.' } } diff --git a/ui/src/locales/lang/zh-CN/views/application.ts b/ui/src/locales/lang/zh-CN/views/application.ts index b818827e3..dc9b16216 100644 --- a/ui/src/locales/lang/zh-CN/views/application.ts +++ b/ui/src/locales/lang/zh-CN/views/application.ts @@ -197,7 +197,8 @@ export default { appIdPlaceholder: '请输入App ID', appSecretPlaceholder: '请输入App Secret', verificationTokenPlaceholder: '请输入Verification Token', - urlInfo: '-事件与回调-事件配置-配置订阅方式的 "请求地址" 中' + urlInfo: '-事件与回调-事件配置-配置订阅方式的 "请求地址" 中', + folderTokenPlaceholder: '请输入Folder Token' }, slackSetting: { title: 'Slack 应用配置', diff --git a/ui/src/locales/lang/zh-CN/views/dataset.ts b/ui/src/locales/lang/zh-CN/views/dataset.ts index 5ca010525..e68b55fbe 100644 --- a/ui/src/locales/lang/zh-CN/views/dataset.ts +++ b/ui/src/locales/lang/zh-CN/views/dataset.ts @@ -3,6 +3,8 @@ export default { createDataset: '创建知识库', general: '通用型', web: 'web 站点', + lark: '飞书', + yuque: '语雀', relatedApplications: '关联应用', document_count: '文档数', relatedApp_count: '关联应用', @@ -47,14 +49,22 @@ export default { }, datasetType: { label: '知识库类型', - generalInfo: '上传本地文档', - webInfo: '同步Web网站文本数据' + generalInfo: '通过上传文件或手动录入构建知识库', + webInfo: '通过网站链接构建知识库', + larkInfo: '通过飞书文档构建知识库', + yuqueInfo: '通过语雀文档构建知识库' }, source_url: { label: 'Web 根地址', placeholder: '请输入 Web 根地址', requiredMessage: ' 请输入 Web 根地址' }, + user_id: { + requiredMessage: '请输入User ID' + }, + token: { + requiredMessage: '请输入Token' + }, selector: { label: '选择器', placeholder: '默认为 body,可输入 .classname/#idname/tagname' diff --git a/ui/src/locales/lang/zh-CN/views/document.ts b/ui/src/locales/lang/zh-CN/views/document.ts index 154ca5745..8a6602323 100644 --- a/ui/src/locales/lang/zh-CN/views/document.ts +++ b/ui/src/locales/lang/zh-CN/views/document.ts @@ -55,7 +55,8 @@ export default { tip1: '1、点击下载对应模版并完善信息', tip2: '2、上传的表格文件中每个 sheet 会作为一个文档,sheet名称为文档名称', tip3: '3、每次最多上传 50 个文件,每个文件不超过 100MB' - } + }, + lark: {} }, setRules: { title: { diff --git a/ui/src/locales/lang/zh-Hant/views/application.ts b/ui/src/locales/lang/zh-Hant/views/application.ts index 192a6958f..3b6f1756e 100644 --- a/ui/src/locales/lang/zh-Hant/views/application.ts +++ b/ui/src/locales/lang/zh-Hant/views/application.ts @@ -59,8 +59,8 @@ export default { references: ' (引用知識庫)', placeholder: '請輸入提示詞', requiredMessage: '請輸入提示詞', - tooltip:'透過調整提示詞內容,可以引導大模型對話方向,該提示詞會被固定在上下文的開頭。', - + tooltip: '透過調整提示詞內容,可以引導大模型對話方向,該提示詞會被固定在上下文的開頭。', + noReferencesTooltip: '透過調整提示詞內容,可以引導大模型對話方向,該提示詞會被固定在上下文的開頭。可以使用變數:{question} 是用戶提出問題的佔位符。', referencesTooltip: @@ -104,9 +104,9 @@ export default { }, reasoningContent: { label: '輸出思考', - tooltip:'請根據模型返回的思考標簽設置,標簽中間的內容將會認定爲思考過程', + tooltip: '請根據模型返回的思考標簽設置,標簽中間的內容將會認定爲思考過程', start: '開始', - end: '結束', + end: '結束' } }, buttons: { @@ -196,12 +196,13 @@ export default { appIdPlaceholder: '請輸入App ID', appSecretPlaceholder: '請輸入App Secret', verificationTokenPlaceholder: '請輸入Verification Token', - urlInfo: '-事件與回呼-事件配置-配置訂閱方式的 "請求位址" 中' + urlInfo: '-事件與回呼-事件配置-配置訂閱方式的 "請求位址" 中', + folderTokenPlaceholder: '請輸入Folder Token' }, slackSetting: { title: 'Slack 應用配置', signingSecretPlaceholder: '請輸入 Signing Secret', - botUserTokenPlaceholder: '請輸入 Bot User Token', + botUserTokenPlaceholder: '請輸入 Bot User Token' }, copyUrl: '複製連結填入到' }, diff --git a/ui/src/locales/lang/zh-Hant/views/dataset.ts b/ui/src/locales/lang/zh-Hant/views/dataset.ts index 0eace754b..e6e63eb79 100644 --- a/ui/src/locales/lang/zh-Hant/views/dataset.ts +++ b/ui/src/locales/lang/zh-Hant/views/dataset.ts @@ -3,6 +3,8 @@ export default { createDataset: '建立知識庫', general: '通用型', web: 'Web 站點', + lark: '飛書', + yuque: '語雀', relatedApplications: '關聯應用', document_count: '文檔數', relatedApp_count: '關聯應用', @@ -46,14 +48,22 @@ export default { }, datasetType: { label: '知識庫類型', - generalInfo: '上傳本地檔案', - webInfo: '同步Web網站文字資料' + generalInfo: '透過上傳檔案或手動錄入建置知識庫', + webInfo: '透過網站連結建立知識庫', + larkInfo: '透過飛書文檔建構知識庫', + yuqueInfo: '透過語雀文件建構知識庫' }, source_url: { label: 'Web 根位址', placeholder: '請輸入 Web 根位址', requiredMessage: '請輸入 Web 根位址' }, + user_id: { + requiredMessage: '請輸入 User ID' + }, + token: { + requiredMessage: '請輸入 Token' + }, selector: { label: '選擇器', placeholder: '預設為 body,可輸入 .classname/#idname/tagname' diff --git a/ui/src/views/dataset/DatasetSetting.vue b/ui/src/views/dataset/DatasetSetting.vue index 6b2d90d9f..4978490e0 100644 --- a/ui/src/views/dataset/DatasetSetting.vue +++ b/ui/src/views/dataset/DatasetSetting.vue @@ -23,9 +23,9 @@
{{ $t('views.dataset.general') }}
- {{ - $t('views.dataset.datasetForm.form.datasetType.generalInfo') - }} + {{ $t('views.dataset.datasetForm.form.datasetType.generalInfo') }} +
@@ -42,6 +42,21 @@ + +
+ + + +
+

+ {{ $t('views.dataset.lark') }} +

+ {{ $t('views.dataset.datasetForm.form.datasetType.larkInfo') }} + +
+
+
+ + + + + + + + +

{{ $t('views.dataset.relatedApplications') }}

@@ -105,7 +144,7 @@
- {{ $t('common.save') }} + {{ $t('common.save') }}
@@ -122,6 +161,7 @@ import { MsgSuccess, MsgConfirm } from '@/utils/message' import { isAppIcon } from '@/utils/application' import useStore from '@/stores' import { t } from '@/locales' + const route = useRoute() const { params: { id } @@ -138,7 +178,10 @@ const cloneModelId = ref('') const form = ref({ source_url: '', - selector: '' + selector: '', + app_id: '', + app_secret: '', + folder_token: '' }) const rules = reactive({ @@ -148,6 +191,27 @@ const rules = reactive({ message: t('views.dataset.datasetForm.form.source_url.requiredMessage'), trigger: 'blur' } + ], + app_id: [ + { + required: true, + message: t('views.application.applicationAccess.larkSetting.appIdPlaceholder'), + trigger: 'blur' + } + ], + app_secret: [ + { + required: true, + message: t('views.application.applicationAccess.larkSetting.appSecretPlaceholder'), + trigger: 'blur' + } + ], + folder_token: [ + { + required: true, + message: t('views.application.applicationAccess.larkSetting.folderTokenPlaceholder'), + trigger: 'blur' + } ] }) @@ -156,7 +220,7 @@ async function submit() { await webFormRef.value.validate((valid: any) => { if (valid) { const obj = - detail.value.type === '1' + detail.value.type === '1' || detail.value.type === '2' ? { application_id_list: application_id_list.value, meta: form.value, @@ -172,17 +236,33 @@ async function submit() { confirmButtonText: t('views.dataset.setting.vectorization') }) .then(() => { - datasetApi.putDataset(id, obj, loading).then((res) => { - datasetApi.putReEmbeddingDataset(id).then(() => { - MsgSuccess(t('common.saveSuccess')) + if (detail.value.type === '2') { + datasetApi.putLarkDataset(id, obj, loading).then((res) => { + datasetApi.putReEmbeddingDataset(id).then(() => { + MsgSuccess(t('common.saveSuccess')) + }) }) - }) + } else { + datasetApi.putDataset(id, obj, loading).then((res) => { + datasetApi.putReEmbeddingDataset(id).then(() => { + MsgSuccess(t('common.saveSuccess')) + }) + }) + } }) .catch(() => {}) } else { - datasetApi.putDataset(id, obj, loading).then((res) => { - MsgSuccess(t('common.saveSuccess')) - }) + if (detail.value.type === '2') { + datasetApi.putLarkDataset(id, obj, loading).then((res) => { + datasetApi.putReEmbeddingDataset(id).then(() => { + MsgSuccess(t('common.saveSuccess')) + }) + }) + } else { + datasetApi.putDataset(id, obj, loading).then((res) => { + MsgSuccess(t('common.saveSuccess')) + }) + } } } }) @@ -193,7 +273,7 @@ function getDetail() { dataset.asyncGetDatasetDetail(id, loading).then((res: any) => { detail.value = res.data cloneModelId.value = res.data?.embedding_mode_id - if (detail.value.type === '1') { + if (detail.value.type === '1' || detail.value.type === '2') { form.value = res.data.meta } application_id_list.value = res.data?.application_id_list diff --git a/ui/src/views/dataset/ImportDocumentDataset.vue b/ui/src/views/dataset/ImportDocumentDataset.vue index 3bdcc9e63..fe8f2b33f 100644 --- a/ui/src/views/dataset/ImportDocumentDataset.vue +++ b/ui/src/views/dataset/ImportDocumentDataset.vue @@ -19,9 +19,9 @@ >
- {{ - $t('views.document.fileType.txt.label') - }} + {{ $t('views.document.fileType.txt.label') }} +
@@ -42,7 +42,14 @@ />
- + @@ -65,20 +72,28 @@ import documentApi from '@/api/document' import { MsgConfirm, MsgSuccess } from '@/utils/message' import { t } from '@/locales' import type Node from 'element-plus/es/components/tree/src/model/node' +import dataset from '@/api/dataset' -interface Tree { - name: string - leaf?: boolean -} const router = useRouter() const route = useRoute() const { - query: { id } // id为datasetID,有id的是上传文档 + query: { id, folder_token } // id为datasetID,有id的是上传文档 folder_token为飞书文件夹token } = route +const datasetId = id as string +const folderToken = folder_token as string const loading = ref(false) const disabled = ref(false) const allCheck = ref(false) +const treeRef = ref(null) +interface Tree { + name: string + leaf?: boolean + type: string + token: string + is_exist: boolean +} + const form = ref({ fileType: 'txt', fileList: [] as any @@ -93,33 +108,47 @@ const rules = reactive({ const props = { label: 'name', children: 'zones', - isLeaf: 'leaf' + isLeaf: (data: any) => data.type !== 'folder', + disabled: (data: any) => data.is_exist } -const loadNode = (node: Node, resolve: (data: Tree[]) => void) => { - if (node.level === 0) { - return resolve([{ name: 'region' }]) - } - if (node.level > 1) return resolve([]) - - setTimeout(() => { - const data: Tree[] = [ - { - name: 'leaf', - leaf: true - }, - { - name: 'zone' - } - ] - - resolve(data) - }, 500) +const loadNode = (node: Node, resolve: (nodeData: Tree[]) => void) => { + console.log(node) + const token = node.level === 0 ? folderToken : node.data.token // 根节点使用 folder_token,其他节点使用 node.data.token + dataset + .getLarkDocumentList(datasetId, token, {}, loading) + .then((res) => { + const data: any = res.data + resolve(data.files as Tree[]) + }) + .catch((err) => { + console.error('Failed to load tree nodes:', err) + }) } function submit() { loading.value = true + // 选中的节点的token + const checkedNodes = treeRef.value?.getCheckedNodes() || [] + const newList = checkedNodes.map((node: any) => { + return { + name: node.name, + token: node.token, + type: node.type + } + }) + dataset + .importLarkDocument(datasetId, newList, loading) + .then((res) => { + MsgSuccess(t('views.document.tip.importMessage')) + router.go(-1) + }) + .catch((err) => { + console.error('Failed to load tree nodes:', err) + }) + loading.value = false } + function back() { router.go(-1) } @@ -131,6 +160,7 @@ function back() { margin: 0 auto; overflow: hidden; } + &__footer { padding: 16px 24px; position: fixed; @@ -140,6 +170,7 @@ function back() { width: 100%; box-sizing: border-box; } + .upload-document { width: 70%; margin: 0 auto; diff --git a/ui/src/views/dataset/component/CreateDatasetDialog.vue b/ui/src/views/dataset/component/CreateDatasetDialog.vue index bf8c8cd55..586b15bc0 100644 --- a/ui/src/views/dataset/component/CreateDatasetDialog.vue +++ b/ui/src/views/dataset/component/CreateDatasetDialog.vue @@ -70,6 +70,58 @@ + + + +
+
+ + + +
+

+ {{ $t('views.dataset.lark') }} +

+ {{ + $t('views.dataset.datasetForm.form.datasetType.larkInfo') + }} +
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +