feat: support lark document

--story=1017908 --user=王孝刚 【知识库】 - X-Pack支持对接飞书文档 https://www.tapd.cn/57709429/s/1673069
This commit is contained in:
wxg0103 2025-03-20 10:05:32 +08:00
parent e995a663c8
commit ea0ab5e3d2
14 changed files with 565 additions and 138 deletions

View File

@ -4,18 +4,19 @@ import type { datasetData } from '@/api/type/dataset'
import type { pageRequest } from '@/api/type/common' import type { pageRequest } from '@/api/type/common'
import type { ApplicationFormType } from '@/api/type/application' import type { ApplicationFormType } from '@/api/type/application'
import { type Ref } from 'vue' import { type Ref } from 'vue'
const prefix = '/dataset' const prefix = '/dataset'
/** /**
* *
* @param * @param
* page { * page {
"current_page": "string", "current_page": "string",
"page_size": "string", "page_size": "string",
} }
* param { * param {
"name": "string", "name": "string",
} }
*/ */
const getDataset: ( const getDataset: (
page: pageRequest, page: pageRequest,
@ -46,28 +47,28 @@ const delDataset: (dataset_id: String, loading?: Ref<boolean>) => Promise<Result
/** /**
* *
* @param * @param
* { * {
"name": "string", "name": "string",
"desc": "string", "desc": "string",
"documents": [ "documents": [
{ {
"name": "string", "name": "string",
"paragraphs": [ "paragraphs": [
{ {
"content": "string", "content": "string",
"title": "string", "title": "string",
"problem_list": [ "problem_list": [
{ {
"id": "string", "id": "string",
"content": "string" "content": "string"
} }
] ]
} }
] ]
} }
] ]
} }
*/ */
const postDataset: (data: datasetData, loading?: Ref<boolean>) => Promise<Result<any>> = ( const postDataset: (data: datasetData, loading?: Ref<boolean>) => Promise<Result<any>> = (
data, data,
@ -78,13 +79,13 @@ const postDataset: (data: datasetData, loading?: Ref<boolean>) => Promise<Result
/** /**
* Web知识库 * Web知识库
* @param * @param
* { * {
"name": "string", "name": "string",
"desc": "string", "desc": "string",
"source_url": "string", "source_url": "string",
"selector": "string", "selector": "string",
} }
*/ */
const postWebDataset: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = ( const postWebDataset: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
data, data,
@ -92,15 +93,32 @@ const postWebDataset: (data: any, loading?: Ref<boolean>) => Promise<Result<any>
) => { ) => {
return post(`${prefix}/web`, data, undefined, loading) 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<boolean>) => Promise<Result<any>> = (
data,
loading
) => {
return post(`${prefix}/lark/save`, data, undefined, loading)
}
/** /**
* QA知识库 * QA知识库
* @param formData * @param formData
* { * {
"file": "file", "file": "file",
"name": "string", "name": "string",
"desc": "string", "desc": "string",
} }
*/ */
const postQADataset: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = ( const postQADataset: (data: any, loading?: Ref<boolean>) => Promise<Result<any>> = (
data, data,
@ -122,12 +140,12 @@ const getDatasetDetail: (dataset_id: string, loading?: Ref<boolean>) => Promise<
/** /**
* *
* @param * @param
* dataset_id * dataset_id
* { * {
"name": "string", "name": "string",
"desc": true "desc": true
} }
*/ */
const putDataset: ( const putDataset: (
dataset_id: string, dataset_id: string,
@ -136,6 +154,13 @@ const putDataset: (
) => Promise<Result<any>> = (dataset_id, data, loading) => { ) => Promise<Result<any>> = (dataset_id, data, loading) => {
return put(`${prefix}/${dataset_id}`, data, undefined, loading) return put(`${prefix}/${dataset_id}`, data, undefined, loading)
} }
const putLarkDataset: (
dataset_id: string,
data: any,
loading?: Ref<boolean>
) => Promise<Result<any>> = (dataset_id, data, loading) => {
return put(`${prefix}/lark/${dataset_id}`, data, undefined, loading)
}
/** /**
* *
* @param dataset_id * @param dataset_id
@ -229,6 +254,29 @@ const getDatasetModel: (
) => Promise<Result<Array<any>>> = (dataset_id, loading) => { ) => Promise<Result<Array<any>>> = (dataset_id, loading) => {
return get(`${prefix}/${dataset_id}/model`, 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<boolean>
) => Promise<Result<Array<any>>> = (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<boolean>
) => Promise<Result<Array<any>>> = (dataset_id, data, loading) => {
return post(`${prefix}/lark/${dataset_id}/import`, data, null, loading)
}
export default { export default {
getDataset, getDataset,
@ -245,5 +293,9 @@ export default {
postQADataset, postQADataset,
exportDataset, exportDataset,
getDatasetModel, getDatasetModel,
exportZipDataset exportZipDataset,
postLarkDataset,
getLarkDocumentList,
importLarkDocument,
putLarkDataset
} }

View File

@ -181,6 +181,18 @@ const putDocumentSync: (
) => Promise<Result<any>> = (dataset_id, document_id, loading) => { ) => Promise<Result<any>> = (dataset_id, document_id, loading) => {
return put(`${prefix}/${dataset_id}/document/${document_id}/sync`, undefined, undefined, loading) return put(`${prefix}/${dataset_id}/document/${document_id}/sync`, undefined, undefined, loading)
} }
const putLarkDocumentSync: (
dataset_id: string,
document_id: string,
loading?: Ref<boolean>
) => Promise<Result<any>> = (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<Result<boolean>> = (dataset_id, data, loading) => { ) => Promise<Result<boolean>> = (dataset_id, data, loading) => {
return put(`${prefix}/${dataset_id}/document/_bach`, { id_list: data }, undefined, loading) return put(`${prefix}/${dataset_id}/document/_bach`, { id_list: data }, undefined, loading)
} }
const delMulLarkSyncDocument: (
dataset_id: string,
data: any,
loading?: Ref<boolean>
) => Promise<Result<boolean>> = (dataset_id, data, loading) => {
return put(`${prefix}/lark/${dataset_id}/_batch`, { id_list: data }, undefined, loading)
}
/** /**
* Web站点文档 * Web站点文档
@ -394,5 +413,7 @@ export default {
batchGenerateRelated, batchGenerateRelated,
cancelTask, cancelTask,
exportDocumentZip, exportDocumentZip,
batchCancelTask batchCancelTask,
putLarkDocumentSync,
delMulLarkSyncDocument
} }

View File

@ -210,7 +210,8 @@ export default {
appSecretPlaceholder: 'Please enter APP secret', appSecretPlaceholder: 'Please enter APP secret',
verificationTokenPlaceholder: 'Please enter verification token', verificationTokenPlaceholder: 'Please enter verification token',
urlInfo: 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: { slackSetting: {
title: 'Slack Configuration', title: 'Slack Configuration',

View File

@ -49,13 +49,21 @@ export default {
datasetType: { datasetType: {
label: 'Type', label: 'Type',
generalInfo: 'Upload local documents', 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: { source_url: {
label: 'Web Root URL', label: 'Web Root URL',
placeholder: 'Please enter the web root URL', placeholder: 'Please enter the web root URL',
requiredMessage: '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: { selector: {
label: 'Selector', label: 'Selector',
placeholder: 'Default is body, can input .classname/#idname/tagname' placeholder: 'Default is body, can input .classname/#idname/tagname'
@ -79,8 +87,7 @@ export default {
replace: 'Replace Sync', replace: 'Replace Sync',
replaceText: 'Re-fetch Web site documents, replacing the documents in the local knowledge', replaceText: 'Re-fetch Web site documents, replacing the documents in the local knowledge',
complete: 'Full Sync', complete: 'Full Sync',
completeText: completeText: 'Delete all documents in the local knowledge and re-fetch web site documents',
'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.' tip: 'Note: All syncs will delete existing data and re-fetch new data. Please proceed with caution.'
} }
} }

View File

@ -197,7 +197,8 @@ export default {
appIdPlaceholder: '请输入App ID', appIdPlaceholder: '请输入App ID',
appSecretPlaceholder: '请输入App Secret', appSecretPlaceholder: '请输入App Secret',
verificationTokenPlaceholder: '请输入Verification Token', verificationTokenPlaceholder: '请输入Verification Token',
urlInfo: '-事件与回调-事件配置-配置订阅方式的 "请求地址" 中' urlInfo: '-事件与回调-事件配置-配置订阅方式的 "请求地址" 中',
folderTokenPlaceholder: '请输入Folder Token'
}, },
slackSetting: { slackSetting: {
title: 'Slack 应用配置', title: 'Slack 应用配置',

View File

@ -3,6 +3,8 @@ export default {
createDataset: '创建知识库', createDataset: '创建知识库',
general: '通用型', general: '通用型',
web: 'web 站点', web: 'web 站点',
lark: '飞书',
yuque: '语雀',
relatedApplications: '关联应用', relatedApplications: '关联应用',
document_count: '文档数', document_count: '文档数',
relatedApp_count: '关联应用', relatedApp_count: '关联应用',
@ -47,14 +49,22 @@ export default {
}, },
datasetType: { datasetType: {
label: '知识库类型', label: '知识库类型',
generalInfo: '上传本地文档', generalInfo: '通过上传文件或手动录入构建知识库',
webInfo: '同步Web网站文本数据' webInfo: '通过网站链接构建知识库',
larkInfo: '通过飞书文档构建知识库',
yuqueInfo: '通过语雀文档构建知识库'
}, },
source_url: { source_url: {
label: 'Web 根地址', label: 'Web 根地址',
placeholder: '请输入 Web 根地址', placeholder: '请输入 Web 根地址',
requiredMessage: ' 请输入 Web 根地址' requiredMessage: ' 请输入 Web 根地址'
}, },
user_id: {
requiredMessage: '请输入User ID'
},
token: {
requiredMessage: '请输入Token'
},
selector: { selector: {
label: '选择器', label: '选择器',
placeholder: '默认为 body可输入 .classname/#idname/tagname' placeholder: '默认为 body可输入 .classname/#idname/tagname'

View File

@ -55,7 +55,8 @@ export default {
tip1: '1、点击下载对应模版并完善信息', tip1: '1、点击下载对应模版并完善信息',
tip2: '2、上传的表格文件中每个 sheet 会作为一个文档sheet名称为文档名称', tip2: '2、上传的表格文件中每个 sheet 会作为一个文档sheet名称为文档名称',
tip3: '3、每次最多上传 50 个文件,每个文件不超过 100MB' tip3: '3、每次最多上传 50 个文件,每个文件不超过 100MB'
} },
lark: {}
}, },
setRules: { setRules: {
title: { title: {

View File

@ -59,8 +59,8 @@ export default {
references: ' (引用知識庫)', references: ' (引用知識庫)',
placeholder: '請輸入提示詞', placeholder: '請輸入提示詞',
requiredMessage: '請輸入提示詞', requiredMessage: '請輸入提示詞',
tooltip:'透過調整提示詞內容,可以引導大模型對話方向,該提示詞會被固定在上下文的開頭。', tooltip: '透過調整提示詞內容,可以引導大模型對話方向,該提示詞會被固定在上下文的開頭。',
noReferencesTooltip: noReferencesTooltip:
'透過調整提示詞內容,可以引導大模型對話方向,該提示詞會被固定在上下文的開頭。可以使用變數:{question} 是用戶提出問題的佔位符。', '透過調整提示詞內容,可以引導大模型對話方向,該提示詞會被固定在上下文的開頭。可以使用變數:{question} 是用戶提出問題的佔位符。',
referencesTooltip: referencesTooltip:
@ -104,9 +104,9 @@ export default {
}, },
reasoningContent: { reasoningContent: {
label: '輸出思考', label: '輸出思考',
tooltip:'請根據模型返回的思考標簽設置,標簽中間的內容將會認定爲思考過程', tooltip: '請根據模型返回的思考標簽設置,標簽中間的內容將會認定爲思考過程',
start: '開始', start: '開始',
end: '結束', end: '結束'
} }
}, },
buttons: { buttons: {
@ -196,12 +196,13 @@ export default {
appIdPlaceholder: '請輸入App ID', appIdPlaceholder: '請輸入App ID',
appSecretPlaceholder: '請輸入App Secret', appSecretPlaceholder: '請輸入App Secret',
verificationTokenPlaceholder: '請輸入Verification Token', verificationTokenPlaceholder: '請輸入Verification Token',
urlInfo: '-事件與回呼-事件配置-配置訂閱方式的 "請求位址" 中' urlInfo: '-事件與回呼-事件配置-配置訂閱方式的 "請求位址" 中',
folderTokenPlaceholder: '請輸入Folder Token'
}, },
slackSetting: { slackSetting: {
title: 'Slack 應用配置', title: 'Slack 應用配置',
signingSecretPlaceholder: '請輸入 Signing Secret', signingSecretPlaceholder: '請輸入 Signing Secret',
botUserTokenPlaceholder: '請輸入 Bot User Token', botUserTokenPlaceholder: '請輸入 Bot User Token'
}, },
copyUrl: '複製連結填入到' copyUrl: '複製連結填入到'
}, },

View File

@ -3,6 +3,8 @@ export default {
createDataset: '建立知識庫', createDataset: '建立知識庫',
general: '通用型', general: '通用型',
web: 'Web 站點', web: 'Web 站點',
lark: '飛書',
yuque: '語雀',
relatedApplications: '關聯應用', relatedApplications: '關聯應用',
document_count: '文檔數', document_count: '文檔數',
relatedApp_count: '關聯應用', relatedApp_count: '關聯應用',
@ -46,14 +48,22 @@ export default {
}, },
datasetType: { datasetType: {
label: '知識庫類型', label: '知識庫類型',
generalInfo: '上傳本地檔案', generalInfo: '透過上傳檔案或手動錄入建置知識庫',
webInfo: '同步Web網站文字資料' webInfo: '透過網站連結建立知識庫',
larkInfo: '透過飛書文檔建構知識庫',
yuqueInfo: '透過語雀文件建構知識庫'
}, },
source_url: { source_url: {
label: 'Web 根位址', label: 'Web 根位址',
placeholder: '請輸入 Web 根位址', placeholder: '請輸入 Web 根位址',
requiredMessage: '請輸入 Web 根位址' requiredMessage: '請輸入 Web 根位址'
}, },
user_id: {
requiredMessage: '請輸入 User ID'
},
token: {
requiredMessage: '請輸入 Token'
},
selector: { selector: {
label: '選擇器', label: '選擇器',
placeholder: '預設為 body可輸入 .classname/#idname/tagname' placeholder: '預設為 body可輸入 .classname/#idname/tagname'

View File

@ -23,9 +23,9 @@
</AppAvatar> </AppAvatar>
<div> <div>
<div>{{ $t('views.dataset.general') }}</div> <div>{{ $t('views.dataset.general') }}</div>
<el-text type="info">{{ <el-text type="info"
$t('views.dataset.datasetForm.form.datasetType.generalInfo') >{{ $t('views.dataset.datasetForm.form.datasetType.generalInfo') }}
}}</el-text> </el-text>
</div> </div>
</div> </div>
</el-card> </el-card>
@ -42,6 +42,21 @@
</div> </div>
</div> </div>
</el-card> </el-card>
<el-card shadow="never" class="mb-8" style="width: 50%" v-if="detail?.type === '2'">
<div class="flex align-center">
<AppAvatar shape="square" :size="32" style="background: none">
<img src="@/assets/logo_lark.svg" style="width: 100%" alt="" />
</AppAvatar>
<div>
<p>
<el-text>{{ $t('views.dataset.lark') }}</el-text>
</p>
<el-text type="info"
>{{ $t('views.dataset.datasetForm.form.datasetType.larkInfo') }}
</el-text>
</div>
</div>
</el-card>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
:label="$t('views.dataset.datasetForm.form.source_url.label')" :label="$t('views.dataset.datasetForm.form.source_url.label')"
@ -64,6 +79,30 @@
@blur="form.selector = form.selector.trim()" @blur="form.selector = form.selector.trim()"
/> />
</el-form-item> </el-form-item>
<el-form-item label="App ID" prop="app_id" v-if="detail.type === '2'">
<el-input
v-model="form.app_id"
:placeholder="
$t('views.application.applicationAccess.larkSetting.appIdPlaceholder')
"
/>
</el-form-item>
<el-form-item label="App Secret" prop="app_id" v-if="detail.type === '2'">
<el-input
v-model="form.app_secret"
:placeholder="
$t('views.application.applicationAccess.larkSetting.appSecretPlaceholder')
"
/>
</el-form-item>
<el-form-item label="Folder Token" prop="folder_token" v-if="detail.type === '2'">
<el-input
v-model="form.folder_token"
:placeholder="
$t('views.application.applicationAccess.larkSetting.folderTokenPlaceholder')
"
/>
</el-form-item>
</el-form> </el-form>
<div v-if="application_id_list.length > 0"> <div v-if="application_id_list.length > 0">
<h4 class="title-decoration-1 mb-16">{{ $t('views.dataset.relatedApplications') }}</h4> <h4 class="title-decoration-1 mb-16">{{ $t('views.dataset.relatedApplications') }}</h4>
@ -105,7 +144,7 @@
</div> </div>
<div class="text-right"> <div class="text-right">
<el-button @click="submit" type="primary"> {{ $t('common.save') }} </el-button> <el-button @click="submit" type="primary"> {{ $t('common.save') }}</el-button>
</div> </div>
</div> </div>
</el-scrollbar> </el-scrollbar>
@ -122,6 +161,7 @@ import { MsgSuccess, MsgConfirm } from '@/utils/message'
import { isAppIcon } from '@/utils/application' import { isAppIcon } from '@/utils/application'
import useStore from '@/stores' import useStore from '@/stores'
import { t } from '@/locales' import { t } from '@/locales'
const route = useRoute() const route = useRoute()
const { const {
params: { id } params: { id }
@ -138,7 +178,10 @@ const cloneModelId = ref('')
const form = ref<any>({ const form = ref<any>({
source_url: '', source_url: '',
selector: '' selector: '',
app_id: '',
app_secret: '',
folder_token: ''
}) })
const rules = reactive({ const rules = reactive({
@ -148,6 +191,27 @@ const rules = reactive({
message: t('views.dataset.datasetForm.form.source_url.requiredMessage'), message: t('views.dataset.datasetForm.form.source_url.requiredMessage'),
trigger: 'blur' 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) => { await webFormRef.value.validate((valid: any) => {
if (valid) { if (valid) {
const obj = const obj =
detail.value.type === '1' detail.value.type === '1' || detail.value.type === '2'
? { ? {
application_id_list: application_id_list.value, application_id_list: application_id_list.value,
meta: form.value, meta: form.value,
@ -172,17 +236,33 @@ async function submit() {
confirmButtonText: t('views.dataset.setting.vectorization') confirmButtonText: t('views.dataset.setting.vectorization')
}) })
.then(() => { .then(() => {
datasetApi.putDataset(id, obj, loading).then((res) => { if (detail.value.type === '2') {
datasetApi.putReEmbeddingDataset(id).then(() => { datasetApi.putLarkDataset(id, obj, loading).then((res) => {
MsgSuccess(t('common.saveSuccess')) 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(() => {}) .catch(() => {})
} else { } else {
datasetApi.putDataset(id, obj, loading).then((res) => { if (detail.value.type === '2') {
MsgSuccess(t('common.saveSuccess')) 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) => { dataset.asyncGetDatasetDetail(id, loading).then((res: any) => {
detail.value = res.data detail.value = res.data
cloneModelId.value = res.data?.embedding_mode_id 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 form.value = res.data.meta
} }
application_id_list.value = res.data?.application_id_list application_id_list.value = res.data?.application_id_list

View File

@ -19,9 +19,9 @@
> >
<div class="mt-16 mb-16"> <div class="mt-16 mb-16">
<el-radio-group v-model="form.fileType" class="app-radio-button-group"> <el-radio-group v-model="form.fileType" class="app-radio-button-group">
<el-radio-button value="txt">{{ <el-radio-button value="txt"
$t('views.document.fileType.txt.label') >{{ $t('views.document.fileType.txt.label') }}
}}</el-radio-button> </el-radio-button>
</el-radio-group> </el-radio-group>
</div> </div>
<div class="update-info flex p-8-12 border-r-4 mb-16"> <div class="update-info flex p-8-12 border-r-4 mb-16">
@ -42,7 +42,14 @@
/> />
</div> </div>
<el-tree :props="props" :load="loadNode" lazy show-checkbox /> <el-tree
:props="props"
:load="loadNode"
lazy
show-checkbox
node-key="token"
ref="treeRef"
/>
</el-form> </el-form>
</div> </div>
</el-scrollbar> </el-scrollbar>
@ -65,20 +72,28 @@ import documentApi from '@/api/document'
import { MsgConfirm, MsgSuccess } from '@/utils/message' import { MsgConfirm, MsgSuccess } from '@/utils/message'
import { t } from '@/locales' import { t } from '@/locales'
import type Node from 'element-plus/es/components/tree/src/model/node' 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 router = useRouter()
const route = useRoute() const route = useRoute()
const { const {
query: { id } // iddatasetIDid query: { id, folder_token } // iddatasetIDid folder_tokentoken
} = route } = route
const datasetId = id as string
const folderToken = folder_token as string
const loading = ref(false) const loading = ref(false)
const disabled = ref(false) const disabled = ref(false)
const allCheck = ref(false) const allCheck = ref(false)
const treeRef = ref<any>(null)
interface Tree {
name: string
leaf?: boolean
type: string
token: string
is_exist: boolean
}
const form = ref({ const form = ref({
fileType: 'txt', fileType: 'txt',
fileList: [] as any fileList: [] as any
@ -93,33 +108,47 @@ const rules = reactive({
const props = { const props = {
label: 'name', label: 'name',
children: 'zones', children: 'zones',
isLeaf: 'leaf' isLeaf: (data: any) => data.type !== 'folder',
disabled: (data: any) => data.is_exist
} }
const loadNode = (node: Node, resolve: (data: Tree[]) => void) => { const loadNode = (node: Node, resolve: (nodeData: Tree[]) => void) => {
if (node.level === 0) { console.log(node)
return resolve([{ name: 'region' }]) const token = node.level === 0 ? folderToken : node.data.token // 使 folder_token使 node.data.token
} dataset
if (node.level > 1) return resolve([]) .getLarkDocumentList(datasetId, token, {}, loading)
.then((res) => {
setTimeout(() => { const data: any = res.data
const data: Tree[] = [ resolve(data.files as Tree[])
{ })
name: 'leaf', .catch((err) => {
leaf: true console.error('Failed to load tree nodes:', err)
}, })
{
name: 'zone'
}
]
resolve(data)
}, 500)
} }
function submit() { function submit() {
loading.value = true 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() { function back() {
router.go(-1) router.go(-1)
} }
@ -131,6 +160,7 @@ function back() {
margin: 0 auto; margin: 0 auto;
overflow: hidden; overflow: hidden;
} }
&__footer { &__footer {
padding: 16px 24px; padding: 16px 24px;
position: fixed; position: fixed;
@ -140,6 +170,7 @@ function back() {
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
} }
.upload-document { .upload-document {
width: 70%; width: 70%;
margin: 0 auto; margin: 0 auto;

View File

@ -70,6 +70,58 @@
</el-card> </el-card>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="20" v-hasPermission="new ComplexPermission([], ['x-pack'], 'OR')">
<el-col :span="12">
<el-card
shadow="never"
class="mb-16"
:class="datasetForm.type === '2' ? 'active' : ''"
@click="datasetForm.type = '2'"
>
<div class="flex-between">
<div class="flex align-center">
<AppAvatar shape="square" :size="32" style="background: none">
<img src="@/assets/logo_lark.svg" style="width: 100%" alt="" />
</AppAvatar>
<div>
<p>
<el-text>{{ $t('views.dataset.lark') }}</el-text>
</p>
<el-text type="info">{{
$t('views.dataset.datasetForm.form.datasetType.larkInfo')
}}</el-text>
</div>
</div>
<el-radio value="2" size="large" style="width: 16px"></el-radio>
</div>
</el-card>
</el-col>
<el-col :span="12">
<!-- <el-card-->
<!-- shadow="never"-->
<!-- class="mb-16"-->
<!-- :class="datasetForm.type === '3' ? 'active' : ''"-->
<!-- @click="datasetForm.type = '3'"-->
<!-- >-->
<!-- <div class="flex-between">-->
<!-- <div class="flex align-center">-->
<!-- <AppAvatar class="mr-8" :size="32">-->
<!-- <img src="@/assets/icon_web.svg" style="width: 100%" alt="" />-->
<!-- </AppAvatar>-->
<!-- <div>-->
<!-- <p>-->
<!-- <el-text>{{ $t('views.dataset.yuque') }}</el-text>-->
<!-- </p>-->
<!-- <el-text type="info">{{-->
<!-- $t('views.dataset.datasetForm.form.datasetType.yuqueInfo')-->
<!-- }}</el-text>-->
<!-- </div>-->
<!-- </div>-->
<!-- <el-radio value="3" size="large" style="width: 16px"></el-radio>-->
<!-- </div>-->
<!-- </el-card>-->
</el-col>
</el-row>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
@ -93,6 +145,42 @@
@blur="datasetForm.selector = datasetForm.selector.trim()" @blur="datasetForm.selector = datasetForm.selector.trim()"
/> />
</el-form-item> </el-form-item>
<el-form-item label="App ID" prop="app_id" v-if="datasetForm.type === '2'">
<el-input
v-model="datasetForm.app_id"
:placeholder="$t('views.application.applicationAccess.larkSetting.appIdPlaceholder')"
/>
</el-form-item>
<el-form-item label="App Secret" prop="app_id" v-if="datasetForm.type === '2'">
<el-input
v-model="datasetForm.app_secret"
:placeholder="$t('views.application.applicationAccess.larkSetting.appSecretPlaceholder')"
/>
</el-form-item>
<el-form-item label="Folder Token" prop="folder_token" v-if="datasetForm.type === '2'">
<el-input
v-model="datasetForm.folder_token"
:placeholder="
$t('views.application.applicationAccess.larkSetting.folderTokenPlaceholder')
"
/>
</el-form-item>
<el-form-item label="User ID" prop="user_id" v-if="datasetForm.type === '3'">
<el-input
v-model="datasetForm.user_id"
:placeholder="
$t('views.application.applicationAccess.larkSetting.folderTokenPlaceholder')
"
/>
</el-form-item>
<el-form-item label="Token" prop="token" v-if="datasetForm.type === '3'">
<el-input
v-model="datasetForm.token"
:placeholder="
$t('views.application.applicationAccess.larkSetting.folderTokenPlaceholder')
"
/>
</el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
@ -113,6 +201,7 @@ import BaseForm from './BaseForm.vue'
import datasetApi from '@/api/dataset' import datasetApi from '@/api/dataset'
import { MsgSuccess, MsgAlert } from '@/utils/message' import { MsgSuccess, MsgAlert } from '@/utils/message'
import { t } from '@/locales' import { t } from '@/locales'
import { ComplexPermission } from '@/utils/permission/type'
const emit = defineEmits(['refresh']) const emit = defineEmits(['refresh'])
const router = useRouter() const router = useRouter()
@ -125,7 +214,10 @@ const dialogVisible = ref<boolean>(false)
const datasetForm = ref<any>({ const datasetForm = ref<any>({
type: '0', type: '0',
source_url: '', source_url: '',
selector: '' selector: '',
app_id: '',
app_secret: '',
folder_token: ''
}) })
const rules = reactive({ const rules = reactive({
@ -135,6 +227,41 @@ const rules = reactive({
message: t('views.dataset.datasetForm.form.source_url.requiredMessage'), message: t('views.dataset.datasetForm.form.source_url.requiredMessage'),
trigger: 'blur' trigger: 'blur'
} }
],
app_id: [
{
required: true,
message: t('views.application.applicationAccess.larkSetting.appIdPlaceholder'),
trigger: 'blur'
}
],
app_secret: [
{
required: true,
message: t('views.application.applicationAccess.larkSetting.appSecretPlaceholder'),
trigger: 'blur'
}
],
folder_token: [
{
required: true,
message: t('views.application.applicationAccess.larkSetting.folderTokenPlaceholder'),
trigger: 'blur'
}
],
user_id: [
{
required: true,
message: t('views.dataset.datasetForm.form.user_id.requiredMessage'),
trigger: 'blur'
}
],
token: [
{
required: true,
message: t('views.dataset.datasetForm.form.token.requiredMessage'),
trigger: 'blur'
}
] ]
}) })
@ -167,13 +294,20 @@ const submitHandle = async () => {
router.push({ path: `/dataset/${res.data.id}/document` }) router.push({ path: `/dataset/${res.data.id}/document` })
emit('refresh') emit('refresh')
}) })
} else { } else if (datasetForm.value.type === '1') {
const obj = { ...BaseFormRef.value.form, ...datasetForm.value } const obj = { ...BaseFormRef.value.form, ...datasetForm.value }
datasetApi.postWebDataset(obj, loading).then((res) => { datasetApi.postWebDataset(obj, loading).then((res) => {
MsgSuccess(t('common.createSuccess')) MsgSuccess(t('common.createSuccess'))
router.push({ path: `/dataset/${res.data.id}/document` }) router.push({ path: `/dataset/${res.data.id}/document` })
emit('refresh') emit('refresh')
}) })
} else if (datasetForm.value.type === '2') {
const obj = { ...BaseFormRef.value.form, ...datasetForm.value }
datasetApi.postLarkDataset(obj, loading).then((res) => {
MsgSuccess(t('common.createSuccess'))
router.push({ path: `/dataset/${res.data.id}/document` })
emit('refresh')
})
} }
} else { } else {
return false return false

View File

@ -79,6 +79,20 @@
style="height: 22px" style="height: 22px"
>{{ $t('views.dataset.web') }}</el-tag >{{ $t('views.dataset.web') }}</el-tag
> >
<el-tag
class="purple-tag"
v-else-if="item.type === '2'"
type="warning"
style="height: 22px"
>{{ $t('views.dataset.lark') }}</el-tag
>
<el-tag
class="purple-tag"
v-else-if="item.type === '3'"
type="warning"
style="height: 22px"
>{{ $t('views.dataset.yuque') }}</el-tag
>
</div> </div>
<template #footer> <template #footer>

View File

@ -8,17 +8,34 @@
v-if="datasetDetail.type === '0'" v-if="datasetDetail.type === '0'"
type="primary" type="primary"
@click="router.push({ path: '/dataset/upload', query: { id: id } })" @click="router.push({ path: '/dataset/upload', query: { id: id } })"
>{{ $t('views.document.uploadDocument') }}</el-button >{{ $t('views.document.uploadDocument') }}
> </el-button>
<el-button v-if="datasetDetail.type === '1'" type="primary" @click="importDoc">{{ <el-button v-if="datasetDetail.type === '1'" type="primary" @click="importDoc"
$t('views.document.importDocument') >{{ $t('views.document.importDocument') }}
}}</el-button> </el-button>
<el-button <el-button
@click="syncMulDocument" @click="syncMulDocument"
:disabled="multipleSelection.length === 0" :disabled="multipleSelection.length === 0"
v-if="datasetDetail.type === '1'" v-if="datasetDetail.type === '1'"
>{{ $t('views.document.syncDocument') }}</el-button >{{ $t('views.document.syncDocument') }}
> </el-button>
<el-button
v-if="datasetDetail.type === '2'"
type="primary"
@click="
router.push({
path: '/dataset/import',
query: { id: id, folder_token: datasetDetail.meta.folder_token }
})
"
>{{ $t('views.document.importDocument') }}
</el-button>
<el-button
@click="syncLarkMulDocument"
:disabled="multipleSelection.length === 0"
v-if="datasetDetail.type === '2'"
>{{ $t('views.document.syncDocument') }}
</el-button>
<el-button @click="openDatasetDialog()" :disabled="multipleSelection.length === 0"> <el-button @click="openDatasetDialog()" :disabled="multipleSelection.length === 0">
{{ $t('views.document.setting.migration') }} {{ $t('views.document.setting.migration') }}
</el-button> </el-button>
@ -101,7 +118,9 @@
link link
:type="filterMethod['status'] ? 'primary' : ''" :type="filterMethod['status'] ? 'primary' : ''"
> >
<el-icon><Filter /></el-icon> <el-icon>
<Filter />
</el-icon>
</el-button> </el-button>
<template #dropdown> <template #dropdown>
<el-dropdown-menu style="width: 100px"> <el-dropdown-menu style="width: 100px">
@ -109,20 +128,20 @@
:class="filterMethod['status'] ? '' : 'is-active'" :class="filterMethod['status'] ? '' : 'is-active'"
:command="beforeCommand('status', '')" :command="beforeCommand('status', '')"
class="justify-center" class="justify-center"
>{{ $t('views.document.table.all') }}</el-dropdown-item >{{ $t('views.document.table.all') }}
> </el-dropdown-item>
<el-dropdown-item <el-dropdown-item
:class="filterMethod['status'] === State.SUCCESS ? 'is-active' : ''" :class="filterMethod['status'] === State.SUCCESS ? 'is-active' : ''"
class="justify-center" class="justify-center"
:command="beforeCommand('status', State.SUCCESS)" :command="beforeCommand('status', State.SUCCESS)"
>{{ $t('views.document.fileStatus.SUCCESS') }}</el-dropdown-item >{{ $t('views.document.fileStatus.SUCCESS') }}
> </el-dropdown-item>
<el-dropdown-item <el-dropdown-item
:class="filterMethod['status'] === State.FAILURE ? 'is-active' : ''" :class="filterMethod['status'] === State.FAILURE ? 'is-active' : ''"
class="justify-center" class="justify-center"
:command="beforeCommand('status', State.FAILURE)" :command="beforeCommand('status', State.FAILURE)"
>{{ $t('views.document.fileStatus.FAILURE') }}</el-dropdown-item >{{ $t('views.document.fileStatus.FAILURE') }}
> </el-dropdown-item>
<el-dropdown-item <el-dropdown-item
:class=" :class="
filterMethod['status'] === State.STARTED && filterMethod['status'] === State.STARTED &&
@ -132,14 +151,14 @@
" "
class="justify-center" class="justify-center"
:command="beforeCommand('status', State.STARTED, TaskType.EMBEDDING)" :command="beforeCommand('status', State.STARTED, TaskType.EMBEDDING)"
>{{ $t('views.document.fileStatus.EMBEDDING') }}</el-dropdown-item >{{ $t('views.document.fileStatus.EMBEDDING') }}
> </el-dropdown-item>
<el-dropdown-item <el-dropdown-item
:class="filterMethod['status'] === State.PENDING ? 'is-active' : ''" :class="filterMethod['status'] === State.PENDING ? 'is-active' : ''"
class="justify-center" class="justify-center"
:command="beforeCommand('status', State.PENDING)" :command="beforeCommand('status', State.PENDING)"
>{{ $t('views.document.fileStatus.PENDING') }}</el-dropdown-item >{{ $t('views.document.fileStatus.PENDING') }}
> </el-dropdown-item>
<el-dropdown-item <el-dropdown-item
:class=" :class="
filterMethod['status'] === State.STARTED && filterMethod['status'] === State.STARTED &&
@ -149,8 +168,8 @@
" "
class="justify-center" class="justify-center"
:command="beforeCommand('status', State.STARTED, TaskType.GENERATE_PROBLEM)" :command="beforeCommand('status', State.STARTED, TaskType.GENERATE_PROBLEM)"
>{{ $t('views.document.fileStatus.GENERATE') }}</el-dropdown-item >{{ $t('views.document.fileStatus.GENERATE') }}
> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
@ -170,7 +189,9 @@
link link
:type="filterMethod['is_active'] ? 'primary' : ''" :type="filterMethod['is_active'] ? 'primary' : ''"
> >
<el-icon><Filter /></el-icon> <el-icon>
<Filter />
</el-icon>
</el-button> </el-button>
<template #dropdown> <template #dropdown>
<el-dropdown-menu style="width: 100px"> <el-dropdown-menu style="width: 100px">
@ -178,20 +199,20 @@
:class="filterMethod['is_active'] === '' ? 'is-active' : ''" :class="filterMethod['is_active'] === '' ? 'is-active' : ''"
:command="beforeCommand('is_active', '')" :command="beforeCommand('is_active', '')"
class="justify-center" class="justify-center"
>{{ $t('views.document.table.all') }}</el-dropdown-item >{{ $t('views.document.table.all') }}
> </el-dropdown-item>
<el-dropdown-item <el-dropdown-item
:class="filterMethod['is_active'] === true ? 'is-active' : ''" :class="filterMethod['is_active'] === true ? 'is-active' : ''"
class="justify-center" class="justify-center"
:command="beforeCommand('is_active', true)" :command="beforeCommand('is_active', true)"
>{{ $t('views.document.enableStatus.enable') }}</el-dropdown-item >{{ $t('views.document.enableStatus.enable') }}
> </el-dropdown-item>
<el-dropdown-item <el-dropdown-item
:class="filterMethod['is_active'] === false ? 'is-active' : ''" :class="filterMethod['is_active'] === false ? 'is-active' : ''"
class="justify-center" class="justify-center"
:command="beforeCommand('is_active', false)" :command="beforeCommand('is_active', false)"
>{{ $t('views.document.enableStatus.close') }}</el-dropdown-item >{{ $t('views.document.enableStatus.close') }}
> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
@ -218,7 +239,9 @@
link link
:type="filterMethod['hit_handling_method'] ? 'primary' : ''" :type="filterMethod['hit_handling_method'] ? 'primary' : ''"
> >
<el-icon><Filter /></el-icon> <el-icon>
<Filter />
</el-icon>
</el-button> </el-button>
<template #dropdown> <template #dropdown>
<el-dropdown-menu style="width: 150px"> <el-dropdown-menu style="width: 150px">
@ -226,15 +249,15 @@
:class="filterMethod['hit_handling_method'] ? '' : 'is-active'" :class="filterMethod['hit_handling_method'] ? '' : 'is-active'"
:command="beforeCommand('hit_handling_method', '')" :command="beforeCommand('hit_handling_method', '')"
class="justify-center" class="justify-center"
>{{ $t('views.document.table.all') }}</el-dropdown-item >{{ $t('views.document.table.all') }}
> </el-dropdown-item>
<template v-for="(value, key) of hitHandlingMethod" :key="key"> <template v-for="(value, key) of hitHandlingMethod" :key="key">
<el-dropdown-item <el-dropdown-item
:class="filterMethod['hit_handling_method'] === key ? 'is-active' : ''" :class="filterMethod['hit_handling_method'] === key ? 'is-active' : ''"
class="justify-center" class="justify-center"
:command="beforeCommand('hit_handling_method', key)" :command="beforeCommand('hit_handling_method', key)"
>{{ $t(value) }}</el-dropdown-item >{{ $t(value) }}
> </el-dropdown-item>
</template> </template>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
@ -342,7 +365,7 @@
</el-dropdown> </el-dropdown>
</span> </span>
</div> </div>
<div v-if="datasetDetail.type === '1'"> <div v-if="datasetDetail.type === '1' || datasetDetail.type === '2'">
<span class="mr-4"> <span class="mr-4">
<el-tooltip <el-tooltip
effect="dark" effect="dark"
@ -478,6 +501,7 @@ import GenerateRelatedDialog from '@/components/generate-related-dialog/index.vu
import EmbeddingContentDialog from '@/views/document/component/EmbeddingContentDialog.vue' import EmbeddingContentDialog from '@/views/document/component/EmbeddingContentDialog.vue'
import { TaskType, State } from '@/utils/status' import { TaskType, State } from '@/utils/status'
import { t } from '@/locales' import { t } from '@/locales'
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const { const {
@ -596,6 +620,7 @@ function beforeCommand(attr: string, val: any, task_type?: number) {
task_type task_type
} }
} }
const cancelTask = (row: any, task_type: number) => { const cancelTask = (row: any, task_type: number) => {
documentApi.cancelTask(row.dataset_id, row.id, { type: task_type }).then(() => { documentApi.cancelTask(row.dataset_id, row.id, { type: task_type }).then(() => {
MsgSuccess(t('views.document.tip.sendMessage')) MsgSuccess(t('views.document.tip.sendMessage'))
@ -606,6 +631,7 @@ function importDoc() {
title.value = t('views.document.importDocument') title.value = t('views.document.importDocument')
ImportDocumentDialogRef.value.open() ImportDocumentDialogRef.value.open()
} }
function settingDoc(row: any) { function settingDoc(row: any) {
title.value = t('common.setting') title.value = t('common.setting')
ImportDocumentDialogRef.value.open(row) ImportDocumentDialogRef.value.open(row)
@ -640,6 +666,28 @@ const closeInterval = () => {
} }
function syncDocument(row: any) { function syncDocument(row: any) {
console.log('row', row)
if (row.type === '1') {
syncWebDocument(row)
} else {
syncLarkDocument(row)
}
}
function syncLarkDocument(row: any) {
MsgConfirm(t('views.document.sync.confirmTitle'), t('views.document.sync.confirmMessage1'), {
confirmButtonText: t('views.document.sync.label'),
confirmButtonClass: 'danger'
})
.then(() => {
documentApi.putLarkDocumentSync(id, row.id).then(() => {
getList()
})
})
.catch(() => {})
}
function syncWebDocument(row: any) {
if (row.meta?.source_url) { if (row.meta?.source_url) {
MsgConfirm(t('views.document.sync.confirmTitle'), t('views.document.sync.confirmMessage1'), { MsgConfirm(t('views.document.sync.confirmTitle'), t('views.document.sync.confirmMessage1'), {
confirmButtonText: t('views.document.sync.label'), confirmButtonText: t('views.document.sync.label'),
@ -708,6 +756,19 @@ function syncMulDocument() {
}) })
} }
function syncLarkMulDocument() {
const arr: string[] = []
multipleSelection.value.map((v) => {
if (v) {
arr.push(v.id)
}
})
documentApi.delMulLarkSyncDocument(id, arr, loading).then(() => {
MsgSuccess(t('views.document.sync.successMessage'))
getList()
})
}
function deleteMulDocument() { function deleteMulDocument() {
MsgConfirm( MsgConfirm(
`${t('views.document.delete.confirmTitle1')} ${multipleSelection.value.length} ${t('views.document.delete.confirmTitle2')}`, `${t('views.document.delete.confirmTitle1')} ${multipleSelection.value.length} ${t('views.document.delete.confirmTitle2')}`,
@ -801,6 +862,7 @@ function editName(val: string, id: string) {
function cellMouseEnter(row: any) { function cellMouseEnter(row: any) {
currentMouseId.value = row.id currentMouseId.value = row.id
} }
function cellMouseLeave() { function cellMouseLeave() {
currentMouseId.value = null currentMouseId.value = null
} }
@ -846,6 +908,7 @@ function refresh() {
} }
const GenerateRelatedDialogRef = ref() const GenerateRelatedDialogRef = ref()
function openGenerateDialog(row?: any) { function openGenerateDialog(row?: any) {
const arr: string[] = [] const arr: string[] = []
if (row) { if (row) {
@ -883,6 +946,7 @@ onBeforeUnmount(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.document-main { .document-main {
box-sizing: border-box; box-sizing: border-box;
.mul-operation { .mul-operation {
position: fixed; position: fixed;
margin-left: var(--sidebar-width); margin-left: var(--sidebar-width);