Commit ea8abf47 by YeYuheng Committed by GitHub

dingding (#6856)

parent 34842fc1
{
"recommendations": [
"alexcvzz.vscode-sqlite"
]
}
\ No newline at end of file
---
title: DingTalk Knowledge Base
description: How to connect DingTalk Knowledge Base to FastGPT
---
FastGPT supports connecting DingTalk Knowledge Base through a DingTalk internal enterprise app. When creating the dataset, enter `App Key`, `App Secret`, and `User ID`. After creation, open the dataset detail page, click `Add file`, and select the DingTalk workspace, online documents, or folders to import.
Only DingTalk online document text is supported. Binary files such as PDF, Word, Excel, and PPT are not supported.
## 1. Create a DingTalk app
![Create a DingTalk app](../../../../public/imgs/image-dd3.png)
Open the [DingTalk developer app page](https://open-dev.dingtalk.com/fe/app?hash=%23%2Fcorp%2Fapp#/corp/app), then select an internal enterprise app under the target organization.
If you do not have an app yet, create an internal enterprise app from `Application Development`.
## 2. Get the FastGPT fields
![Get App Key and App Secret](../../../../public/imgs/image-dd4.png)
| FastGPT field | Where to get it in DingTalk |
| --- | --- |
| `App Key` | Open `Credentials and Basic Information` in the app detail page, then copy `Client ID (formerly AppKey and SuiteKey)`. |
| `App Secret` | Copy `Client Secret (formerly AppSecret and SuiteSecret)` from the same page. |
| `User ID` | Ask the organization contact administrator to open DingTalk admin. Path: [oa.dingtalk.com](https://oa.dingtalk.com/) -> `Contacts` -> `Member Management` -> select the operator member -> copy the member `User ID` from the detail page. |
Notes:
- `App Secret` is sensitive. Do not share it publicly.
- `User ID` is not a phone number, display name, or `unionId`.
- If the member detail page does not show `User ID`, ask the contact administrator to export the member list from `Contacts`; the exported sheet usually contains member `User ID`.
- We recommend using a dedicated DingTalk member as the FastGPT sync account and granting it read-only access to the target workspace.
- Workspaces that this member cannot access will not appear in FastGPT.
## 3. Enable DingTalk app permissions
![Enable DingTalk app permissions](../../../../public/imgs/image-dd5.png)
Open `Permissions` in the DingTalk app detail page, then search for and enable:
| Permission | Purpose |
| --- | --- |
| `qyapi_get_member` | Get the operator ID from `User ID`. |
| `Wiki.Workspace.Read` | List DingTalk workspaces accessible to the operator. |
| `Wiki.Node.Read` | List folders and documents under a workspace. |
| `Storage.File.Read` | Read DingTalk online document content. |
Save and publish the app configuration after enabling permissions. If an error contains `requiredScopes`, enable the permissions listed there.
## 4. Create a DingTalk dataset in FastGPT
1. Open the FastGPT dataset list and click `New`.
2. Select `DingTalk Knowledge Base` under external document sources.
3. Enter:
- `App Key`
- `App Secret`
- `User ID`
4. Confirm creation.
You do not need to select a DingTalk workspace or root directory during creation.
## 5. Add files and sync
After creation:
1. Open the dataset detail page.
2. Click `Add file`.
3. Select the target DingTalk workspace.
4. Select online documents or folders to import.
5. Confirm the import.
When a folder is selected, FastGPT recursively imports supported online documents under that folder.
When DingTalk document content changes, click `Sync` from the imported file menu. FastGPT will read the latest content and update indexes.
---
title: 钉钉知识库
description: FastGPT 钉钉知识库功能介绍和使用方式
---
| | |
| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| ![alt text](../../../../public/imgs/image-dd1.png) | ![alt text](../../../../public/imgs/image-dd2.png) |
FastGPT 支持通过钉钉企业内部应用接入钉钉知识库。创建时只需要填写 `App Key`、`App Secret`、`User ID`,创建完成后进入知识库详情页点击`添加文件`,再选择要导入的钉钉知识库、在线文档或文件夹。
当前仅支持钉钉在线文档文本,不支持 PDF、Word、Excel、PPT 等二进制文件。
## 1. 创建钉钉应用
![创建钉钉应用](../../../../public/imgs/image-dd3.png)
打开 [钉钉开发者后台应用详情](https://open-dev.dingtalk.com/fe/app?hash=%23%2Fcorp%2Fapp#/corp/app),选择目标企业下的企业内部应用。
如果还没有应用,先进入`应用开发`创建一个企业内部应用。
## 2. 获取 FastGPT 要填写的参数
![获取 App Key 和 App Secret](../../../../public/imgs/image-dd4.png)
| FastGPT 字段 | 钉钉里去哪里拿 |
| --- | --- |
| `App Key` | 应用详情页左侧进入`凭证与基础信息`,复制`Client ID(原 AppKey 和 SuiteKey)`。 |
| `App Secret` | 同一页面复制`Client Secret(原 AppSecret 和 SuiteSecret)`。 |
| `User ID` | 由企业通讯录管理员进入钉钉管理后台查看。路径:[oa.dingtalk.com](https://oa.dingtalk.com/) -> `通讯录` -> `成员管理` -> 找到作为操作人的成员 -> 点击成员详情,复制该成员的 `User ID`。 |
注意:
- `App Secret` 是密钥,不要公开发送。
- `User ID` 不是手机号、姓名,也不是 `unionId`。
- 如果成员详情页没有展示 `User ID`,让通讯录管理员在`通讯录`里导出成员列表,导出的表格中通常包含成员 `User ID`。
- 建议使用一个专门的钉钉成员作为 FastGPT 同步账号,并给它目标知识库的只读权限。
- 该成员没有权限访问的钉钉知识库,不会出现在 FastGPT 的添加文件列表里。
## 3. 配置钉钉应用权限
![配置钉钉应用权限](../../../../public/imgs/image-dd5.png)
在钉钉应用详情页左侧进入`权限管理`,搜索并开通以下权限:
| 权限标识 | 用途 |
| --- | --- |
| `qyapi_get_member` | 通过 `User ID` 获取接口需要的操作人 ID。 |
| `Wiki.Workspace.Read` | 获取当前操作人可访问的钉钉知识库列表。 |
| `Wiki.Node.Read` | 获取知识库下的文件夹和文档列表。 |
| `Storage.File.Read` | 读取钉钉在线文档正文。 |
权限配置完成后,保存并发布应用配置。若接口报错中出现 `requiredScopes`,按提示补开对应权限。
## 4. 在 FastGPT 中创建钉钉知识库
1. 进入 FastGPT 知识库列表,点击`新建`。
2. 选择`第三方知识库`下的`钉钉知识库`。
3. 填写:
- `App Key`
- `App Secret`
- `User ID`
4. 点击确认创建。
## 5. 添加文件和同步
创建完成后:
1. 进入该知识库详情页。
2. 右上角点击`添加文件`。
3. 选择目标钉钉知识库。
4. 选择要导入的在线文档或文件夹。
5. 确认导入。
选择文件夹时,FastGPT 会递归导入该文件夹下支持的在线文档。
钉钉文档内容更新后,可在已导入文件的更多菜单中点击`同步`,FastGPT 会重新读取最新正文并更新索引。
......@@ -8,6 +8,7 @@
"api_dataset",
"lark_dataset",
"yuque_dataset",
"dingtalk_dataset",
"websync",
"third_dataset",
"template"
......
......@@ -8,6 +8,7 @@
"api_dataset",
"lark_dataset",
"yuque_dataset",
"dingtalk_dataset",
"websync",
"third_dataset",
"template"
......
......@@ -8,6 +8,7 @@ description: 'FastGPT V4.15.0 更新说明'
1. 新增循环节点,弃用旧的批量执行。
2. 全局变量输入框支持输入 object 类型数据。
3. 工具调用模式下,如果开启了虚拟机功能,用户对话框上传的文件会直接注入到虚拟机中。
4. 第三方知识库接入钉钉知识库。
## ⚙️ 优化
......
......@@ -50,6 +50,7 @@ description: FastGPT Toc
- [/en/introduction/guide/knowledge_base/api_dataset](/en/introduction/guide/knowledge_base/api_dataset)
- [/en/introduction/guide/knowledge_base/collection_tags](/en/introduction/guide/knowledge_base/collection_tags)
- [/en/introduction/guide/knowledge_base/dataset_engine](/en/introduction/guide/knowledge_base/dataset_engine)
- [/en/introduction/guide/knowledge_base/dingtalk_dataset](/en/introduction/guide/knowledge_base/dingtalk_dataset)
- [/en/introduction/guide/knowledge_base/lark_dataset](/en/introduction/guide/knowledge_base/lark_dataset)
- [/en/introduction/guide/knowledge_base/rag](/en/introduction/guide/knowledge_base/rag)
- [/en/introduction/guide/knowledge_base/template](/en/introduction/guide/knowledge_base/template)
......
......@@ -50,6 +50,7 @@ description: FastGPT 文档目录
- [/introduction/guide/knowledge_base/api_dataset](/introduction/guide/knowledge_base/api_dataset)
- [/introduction/guide/knowledge_base/collection_tags](/introduction/guide/knowledge_base/collection_tags)
- [/introduction/guide/knowledge_base/dataset_engine](/introduction/guide/knowledge_base/dataset_engine)
- [/introduction/guide/knowledge_base/dingtalk_dataset](/introduction/guide/knowledge_base/dingtalk_dataset)
- [/introduction/guide/knowledge_base/lark_dataset](/introduction/guide/knowledge_base/lark_dataset)
- [/introduction/guide/knowledge_base/rag](/introduction/guide/knowledge_base/rag)
- [/introduction/guide/knowledge_base/template](/introduction/guide/knowledge_base/template)
......
......@@ -252,7 +252,7 @@
"content/self-host/upgrading/4-14/41481.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/4-14/4149.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/4-14/4149.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/4-15/4150.mdx": "2026-04-28T22:44:51+08:00",
"content/self-host/upgrading/4-15/4150.mdx": "2026-04-29T14:53:45+08:00",
"content/self-host/upgrading/outdated/40.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/40.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/41.en.mdx": "2026-04-26T21:08:47+08:00",
......
......@@ -67,6 +67,7 @@ export type FastGPTFeConfigsType = {
show_dataset_feishu?: boolean;
show_dataset_yuque?: boolean;
show_dataset_dingtalk?: boolean;
show_publish_feishu?: boolean;
show_publish_dingtalk?: boolean;
show_publish_wecom?: boolean;
......
......@@ -40,12 +40,26 @@ export const YuqueServerSchema = z
.meta({ description: '语雀服务器配置' });
export type YuqueServerType = z.infer<typeof YuqueServerSchema>;
export type YuqueServer = YuqueServerType;
export const DingtalkServerSchema = z
.object({
appKey: z.string(),
appSecret: z.string().optional(),
userId: z.string(),
operatorId: z.string().optional(),
workspaceId: z.string().optional(),
rootNodeId: z.string().optional(),
workspaceName: z.string().optional()
})
.meta({ description: '钉钉知识库配置' });
export type DingtalkServerType = z.infer<typeof DingtalkServerSchema>;
export type DingtalkServer = DingtalkServerType;
export const ApiDatasetServerSchema = z
.object({
apiServer: APIFileServerSchema.optional(),
feishuServer: FeishuServerSchema.optional(),
yuqueServer: YuqueServerSchema.optional()
yuqueServer: YuqueServerSchema.optional(),
dingtalkServer: DingtalkServerSchema.optional()
})
.meta({ description: '第三方知识库配置' });
export type ApiDatasetServerType = z.infer<typeof ApiDatasetServerSchema>;
......
......@@ -3,7 +3,7 @@ import type { ApiDatasetServerType } from './type';
export const filterApiDatasetServerPublicData = (apiDatasetServer?: ApiDatasetServerType) => {
if (!apiDatasetServer) return undefined;
const { apiServer, yuqueServer, feishuServer } = apiDatasetServer;
const { apiServer, yuqueServer, feishuServer, dingtalkServer } = apiDatasetServer;
return {
apiServer: apiServer
......@@ -26,6 +26,17 @@ export const filterApiDatasetServerPublicData = (apiDatasetServer?: ApiDatasetSe
appSecret: '',
folderToken: feishuServer.folderToken
}
: undefined,
dingtalkServer: dingtalkServer
? {
appKey: dingtalkServer.appKey,
appSecret: '',
userId: dingtalkServer.userId,
operatorId: dingtalkServer.operatorId,
workspaceId: dingtalkServer.workspaceId,
rootNodeId: dingtalkServer.rootNodeId,
workspaceName: dingtalkServer.workspaceName
}
: undefined
};
};
......@@ -9,7 +9,8 @@ export enum DatasetTypeEnum {
apiDataset = 'apiDataset',
feishu = 'feishu',
yuque = 'yuque'
yuque = 'yuque',
dingtalk = 'dingtalk'
}
// @ts-ignore
......@@ -43,6 +44,13 @@ export const ApiDatasetTypeMap: Record<
label: i18nT('dataset:yuque_dataset'),
collectionLabel: i18nT('common:File'),
courseUrl: '/introduction/guide/knowledge_base/yuque_dataset/'
},
[DatasetTypeEnum.dingtalk]: {
icon: 'core/dataset/dingtalkDatasetOutline',
avatar: 'core/dataset/dingtalkDatasetColor',
label: i18nT('dataset:dingtalk_dataset'),
collectionLabel: i18nT('common:File'),
courseUrl: '/introduction/guide/knowledge_base/dingtalk_dataset/'
}
};
export const DatasetTypeMap: Record<
......
......@@ -14,6 +14,7 @@ import {
import {
ApiDatasetServerSchema,
APIFileServerSchema,
DingtalkServerSchema,
FeishuServerSchema,
YuqueServerSchema
} from './apiDataset/type';
......@@ -107,6 +108,10 @@ export const DatasetSchema = z
yuqueServer: YuqueServerSchema.optional().meta({
description: '语雀服务器配置',
deprecated: true
}),
dingtalkServer: DingtalkServerSchema.optional().meta({
description: '钉钉知识库配置',
deprecated: true
})
})
.meta({ description: '知识库' });
......
......@@ -51,7 +51,7 @@ export const CreateDatasetBodySchema = z.object({
description: '视觉语言模型名称'
}),
apiDatasetServer: ApiDatasetServerSchema.optional().meta({
description: '第三方知识库服务器配置(API/飞书/语雀)'
description: '第三方知识库服务器配置(API/飞书/语雀/钉钉)'
})
});
......@@ -266,7 +266,7 @@ export const UpdateDatasetBodySchema = z.object({
description: '外部读取 URL'
}),
apiDatasetServer: ApiDatasetServerSchema.optional().meta({
description: '第三方知识库服务器配置(API/飞书/语雀)'
description: '第三方知识库服务器配置(API/飞书/语雀/钉钉)'
}),
autoSync: z.boolean().optional().meta({
description: '是否自动同步'
......
......@@ -19,7 +19,7 @@ export const GetApiDatasetCatalogBodySchema = z.object({
description: '父级节点 ID,不传或 null 表示根目录'
}),
apiDatasetServer: ApiDatasetServerSchema.optional().meta({
description: '第三方知识库服务器配置(API/飞书/语雀)'
description: '第三方知识库服务器配置(API/飞书/语雀/钉钉)'
})
});
export type GetApiDatasetCatalogBody = z.infer<typeof GetApiDatasetCatalogBodySchema>;
......
......@@ -17,7 +17,8 @@ describe('filterApiDatasetServerPublicData', () => {
expect(result).toEqual({
apiServer: undefined,
yuqueServer: undefined,
feishuServer: undefined
feishuServer: undefined,
dingtalkServer: undefined
});
});
......@@ -39,7 +40,8 @@ describe('filterApiDatasetServerPublicData', () => {
basePath: '/v1'
},
yuqueServer: undefined,
feishuServer: undefined
feishuServer: undefined,
dingtalkServer: undefined
});
});
......@@ -61,7 +63,8 @@ describe('filterApiDatasetServerPublicData', () => {
token: '',
basePath: '/docs'
},
feishuServer: undefined
feishuServer: undefined,
dingtalkServer: undefined
});
});
......@@ -83,7 +86,8 @@ describe('filterApiDatasetServerPublicData', () => {
appId: 'app-123',
appSecret: '',
folderToken: 'folder-token-456'
}
},
dingtalkServer: undefined
});
});
......@@ -123,7 +127,8 @@ describe('filterApiDatasetServerPublicData', () => {
appId: 'feishu-app',
appSecret: '',
folderToken: 'feishu-folder'
}
},
dingtalkServer: undefined
});
});
......@@ -161,7 +166,8 @@ describe('filterApiDatasetServerPublicData', () => {
appId: 'app-id',
appSecret: '',
folderToken: 'folder-token'
}
},
dingtalkServer: undefined
});
});
......@@ -192,6 +198,37 @@ describe('filterApiDatasetServerPublicData', () => {
appId: 'app',
appSecret: '',
folderToken: 'folder'
},
dingtalkServer: undefined
});
});
it('should filter dingtalkServer and clear appSecret', () => {
const input: ApiDatasetServerType = {
dingtalkServer: {
appKey: 'ding-app',
appSecret: 'ding-secret',
userId: 'user-id',
operatorId: 'operator-id',
workspaceId: 'workspace-id',
rootNodeId: 'root-node-id',
workspaceName: 'Workspace'
}
};
const result = filterApiDatasetServerPublicData(input);
expect(result).toEqual({
apiServer: undefined,
yuqueServer: undefined,
feishuServer: undefined,
dingtalkServer: {
appKey: 'ding-app',
appSecret: '',
userId: 'user-id',
operatorId: 'operator-id',
workspaceId: 'workspace-id',
rootNodeId: 'root-node-id',
workspaceName: 'Workspace'
}
});
});
......
import { useApiDatasetRequest } from './custom/api';
import { useYuqueDatasetRequest } from './yuqueDataset/api';
import { useFeishuDatasetRequest } from './feishuDataset/api';
import { useDingtalkDatasetRequest } from './dingtalkDataset/api';
import type { ApiDatasetServerType } from '@fastgpt/global/core/dataset/apiDataset/type';
export const getApiDatasetRequest = async (apiDatasetServer?: ApiDatasetServerType) => {
const { apiServer, yuqueServer, feishuServer } = apiDatasetServer || {};
const { apiServer, yuqueServer, feishuServer, dingtalkServer } = apiDatasetServer || {};
if (apiServer) {
return useApiDatasetRequest({ apiServer });
......@@ -15,5 +16,8 @@ export const getApiDatasetRequest = async (apiDatasetServer?: ApiDatasetServerTy
if (feishuServer) {
return useFeishuDatasetRequest({ feishuServer });
}
if (dingtalkServer) {
return useDingtalkDatasetRequest({ dingtalkServer });
}
return Promise.reject('Can not find api dataset server');
};
......@@ -43,6 +43,7 @@ export function getI18nDatasetType(type: DatasetTypeEnum | string): string {
if (type === DatasetTypeEnum.apiDataset) return i18nT('account_team:dataset.api_file');
if (type === DatasetTypeEnum.feishu) return i18nT('account_team:dataset.feishu_dataset');
if (type === DatasetTypeEnum.yuque) return i18nT('account_team:dataset.yuque_dataset');
if (type === DatasetTypeEnum.dingtalk) return i18nT('account_team:dataset.dingtalk_dataset');
return i18nT('common:UnKnow');
}
......
......@@ -218,6 +218,10 @@ export const iconPaths = {
import('./icons/core/dataset/externalDatasetColor.svg'),
'core/dataset/externalDatasetOutline': () =>
import('./icons/core/dataset/externalDatasetOutline.svg'),
'core/dataset/dingtalkDatasetColor': () =>
import('./icons/core/dataset/dingtalkDatasetColor.svg'),
'core/dataset/dingtalkDatasetOutline': () =>
import('./icons/core/dataset/dingtalkDatasetOutline.svg'),
'core/dataset/feishuDatasetColor': () => import('./icons/core/dataset/feishuDatasetColor.svg'),
'core/dataset/feishuDatasetOutline': () =>
import('./icons/core/dataset/feishuDatasetOutline.svg'),
......
<svg t="1777364982098" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6500" width="200" height="200"><path d="M512.003 79C272.855 79 79 272.855 79 512.003 79 751.145 272.855 945 512.003 945 751.145 945 945 751.145 945 512.003 945 272.855 751.145 79 512.003 79z m200.075 375.014c-0.867 3.764-3.117 9.347-6.234 16.012h0.087l-0.347 0.648c-18.183 38.86-65.631 115.108-65.631 115.108l-0.215-0.52-13.856 24.147h66.8L565.063 779l29.002-115.368h-52.598l18.27-76.29c-14.76 3.55-32.253 8.436-52.945 15.1 0 0-27.967 16.36-80.607-31.5 0 0-35.501-31.29-14.891-39.078 8.744-3.33 42.466-7.573 69.004-11.122 35.93-4.845 57.965-7.441 57.965-7.441s-110.607 1.643-136.841-2.468c-26.237-4.11-59.525-47.905-66.626-86.377 0 0-10.953-21.117 23.595-11.122 34.547 10 177.535 38.95 177.535 38.95s-185.933-56.992-198.36-70.929c-12.381-13.846-36.406-75.902-33.289-113.981 0 0 1.343-9.521 11.127-6.926 0 0 137.49 62.75 231.475 97.152 94.028 34.403 175.76 51.885 165.2 96.414z" fill="#3AA2EB" p-id="6501"></path></svg>
<svg t="1777364982098" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6500" width="200" height="200"><path d="M512.003 79C272.855 79 79 272.855 79 512.003 79 751.145 272.855 945 512.003 945 751.145 945 945 751.145 945 512.003 945 272.855 751.145 79 512.003 79z m200.075 375.014c-0.867 3.764-3.117 9.347-6.234 16.012h0.087l-0.347 0.648c-18.183 38.86-65.631 115.108-65.631 115.108l-0.215-0.52-13.856 24.147h66.8L565.063 779l29.002-115.368h-52.598l18.27-76.29c-14.76 3.55-32.253 8.436-52.945 15.1 0 0-27.967 16.36-80.607-31.5 0 0-35.501-31.29-14.891-39.078 8.744-3.33 42.466-7.573 69.004-11.122 35.93-4.845 57.965-7.441 57.965-7.441s-110.607 1.643-136.841-2.468c-26.237-4.11-59.525-47.905-66.626-86.377 0 0-10.953-21.117 23.595-11.122 34.547 10 177.535 38.95 177.535 38.95s-185.933-56.992-198.36-70.929c-12.381-13.846-36.406-75.902-33.289-113.981 0 0 1.343-9.521 11.127-6.926 0 0 137.49 62.75 231.475 97.152 94.028 34.403 175.76 51.885 165.2 96.414z" fill="#3AA2EB" p-id="6501"></path></svg>
......@@ -61,6 +61,7 @@
"dataset.common_dataset": "Dataset",
"dataset.external_file": "External File",
"dataset.feishu_dataset": "Feishu Cloud Documentation",
"dataset.dingtalk_dataset": "DingTalk Knowledge Base",
"dataset.folder_dataset": "Folder",
"dataset.website_dataset": "Website Sync",
"dataset.yuque_dataset": "Yuque Knowledge Base",
......
......@@ -72,13 +72,16 @@
"external_file": "External file library",
"external_file_dataset_desc": "You can use external file library to build a knowledge library through the API",
"external_id": "File Reading ID",
"external_other_dataset_desc": "Customize API, Feishu, Yuque and other external documents as knowledge bases",
"external_other_dataset_desc": "Customize API, Feishu, Yuque, DingTalk and other external documents as knowledge bases",
"external_read_url": "External Preview URL",
"external_read_url_tip": "Configure the reading URL of your file library for user authentication. Use the {{fileId}} variable to refer to the external file ID.",
"external_url": "File Access URL",
"feishu_dataset": "Feishu Dataset",
"feishu_dataset_config": "Feishu Dataset Config",
"feishu_dataset_desc": "Can build a dataset using Feishu documents by configuring permissions, without secondary storage",
"dingtalk_dataset": "DingTalk Knowledge Base",
"dingtalk_dataset_config": "Configure DingTalk Knowledge Base",
"dingtalk_dataset_desc": "Build knowledge base using DingTalk online documents by configuring permissions, without secondary storage",
"file_list": "File list",
"file_model_function_tip": "Used for QA generation, auto-indexing, and other AI-powered data processing.",
"filename": "Filename",
......
......@@ -61,6 +61,7 @@
"dataset.common_dataset": "知识库",
"dataset.external_file": "外部文件",
"dataset.feishu_dataset": "飞书云文档",
"dataset.dingtalk_dataset": "钉钉知识库",
"dataset.folder_dataset": "文件夹",
"dataset.website_dataset": "网站同步",
"dataset.yuque_dataset": "语雀知识库",
......
......@@ -72,13 +72,16 @@
"external_file": "外部文件库",
"external_file_dataset_desc": "可以通过 API,使用外部文件库构建知识库",
"external_id": "文件阅读 ID",
"external_other_dataset_desc": "自定义API、飞书、语雀等外部文档作为知识库",
"external_other_dataset_desc": "自定义API、飞书、语雀、钉钉等外部文档作为知识库",
"external_read_url": "外部预览地址",
"external_read_url_tip": "可以配置你文件库的阅读地址。便于对用户进行阅读鉴权操作。目前可以使用 {{fileId}} 变量来指代外部文件 ID。",
"external_url": "文件访问 URL",
"feishu_dataset": "飞书知识库",
"feishu_dataset_config": "配置飞书知识库",
"feishu_dataset_desc": "可通过配置飞书文档权限,使用飞书文档构建知识库,文档不会进行二次存储",
"dingtalk_dataset": "钉钉知识库",
"dingtalk_dataset_config": "配置钉钉知识库",
"dingtalk_dataset_desc": "可通过配置钉钉知识库权限,使用钉钉在线文档构建知识库,文档不会进行二次存储",
"file_list": "文件列表",
"file_model_function_tip": "用于增强索引和 QA 生成",
"filename": "文件名",
......
......@@ -60,6 +60,7 @@
"dataset.common_dataset": "知識庫",
"dataset.external_file": "外部文件",
"dataset.feishu_dataset": "飛書云文檔",
"dataset.dingtalk_dataset": "釘釘知識庫",
"dataset.folder_dataset": "資料夾",
"dataset.website_dataset": "網站同步",
"dataset.yuque_dataset": "語雀知識庫",
......
......@@ -72,13 +72,16 @@
"external_file": "外部檔案庫",
"external_file_dataset_desc": "可以通過 API,使用外部文件庫構建知識庫",
"external_id": "檔案讀取識別碼",
"external_other_dataset_desc": "自定義API、飛書、語雀等外部文檔作為知識庫",
"external_other_dataset_desc": "自定義API、飛書、語雀、釘釘等外部文檔作為知識庫",
"external_read_url": "外部預覽網址",
"external_read_url_tip": "可以設定您檔案庫的讀取網址,方便對使用者進行讀取權限驗證。目前可使用 {{fileId}} 變數來代表外部檔案識別碼。",
"external_url": "檔案存取網址",
"feishu_dataset": "飛書知識庫",
"feishu_dataset_config": "設定飛書知識庫",
"feishu_dataset_desc": "可透過設定飛書文件權限,使用飛書文件建構知識庫,文件不會進行二次儲存",
"dingtalk_dataset": "釘釘知識庫",
"dingtalk_dataset_config": "設定釘釘知識庫",
"dingtalk_dataset_desc": "可透過設定釘釘知識庫權限,使用釘釘線上文件建構知識庫,文件不會進行二次儲存",
"file_list": "文件列表",
"file_model_function_tip": "用於增強索引和問答生成",
"filename": "檔案名稱",
......
Subproject commit 1a56dfc0d3c2fc1dc3e4a2f23ad1847926c804a8
Subproject commit 23b39240c48e8b75132258e8e0549f91fc871020
......@@ -267,6 +267,43 @@ const ApiDatasetForm = ({
{renderDirectoryModal()}
</>
)}
{type === DatasetTypeEnum.dingtalk && (
<>
<Flex mt={6} alignItems={'center'}>
<FormLabel flex={['', '0 0 110px']} fontSize={'sm'} required>
App Key
</FormLabel>
<Input
bg={'myWhite.600'}
placeholder={'App Key'}
maxLength={200}
{...register('apiDatasetServer.dingtalkServer.appKey', { required: true })}
/>
</Flex>
<Flex mt={6} alignItems={'center'}>
<FormLabel flex={['', '0 0 110px']} fontSize={'sm'} required>
App Secret
</FormLabel>
<Input
bg={'myWhite.600'}
placeholder={'App Secret'}
maxLength={200}
{...register('apiDatasetServer.dingtalkServer.appSecret', { required: true })}
/>
</Flex>
<Flex mt={6} alignItems={'center'}>
<FormLabel flex={['', '0 0 110px']} fontSize={'sm'} required>
User ID
</FormLabel>
<Input
bg={'myWhite.600'}
placeholder={'User ID'}
maxLength={200}
{...register('apiDatasetServer.dingtalkServer.userId', { required: true })}
/>
</Flex>
</>
)}
</>
);
};
......
......@@ -69,12 +69,7 @@ const CustomAPIFileInput = () => {
});
},
{
refreshDeps: [
datasetDetail._id,
datasetDetail.apiDatasetServer?.apiServer,
parent,
searchKey
],
refreshDeps: [datasetDetail._id, datasetDetail.apiDatasetServer, parent, searchKey],
throttleWait: 500,
manual: false
}
......
......@@ -374,6 +374,37 @@ const Info = ({ datasetId }: { datasetId: string }) => {
</Box>
</>
)}
{datasetDetail.type === DatasetTypeEnum.dingtalk && (
<>
<Box w={'100%'} alignItems={'center'} pt={4}>
<Flex justifyContent={'space-between'} mb={1}>
<FormLabel fontSize={'mini'} fontWeight={'500'}>
{t('dataset:dingtalk_dataset_config')}
</FormLabel>
<MyIcon
name={'edit'}
w={'14px'}
_hover={{ color: 'primary.600' }}
cursor={'pointer'}
onClick={() =>
setEditedAPIDataset({
id: datasetDetail._id,
apiDatasetServer: datasetDetail.apiDatasetServer
})
}
/>
</Flex>
<Box fontSize={'mini'}>
{datasetDetail.apiDatasetServer?.dingtalkServer?.workspaceName ||
datasetDetail.apiDatasetServer?.dingtalkServer?.workspaceId}
</Box>
<Box fontSize={'mini'} color={'myGray.500'} mt={1}>
{datasetDetail.apiDatasetServer?.dingtalkServer?.userId}
</Box>
</Box>
</>
)}
</Box>
{datasetDetail.permission.hasManagePer && (
......
......@@ -27,7 +27,8 @@ export type CreateDatasetType =
| DatasetTypeEnum.apiDataset
| DatasetTypeEnum.websiteDataset
| DatasetTypeEnum.feishu
| DatasetTypeEnum.yuque;
| DatasetTypeEnum.yuque
| DatasetTypeEnum.dingtalk;
const CreateModal = ({
onClose,
......
......@@ -75,7 +75,8 @@ export const createApiDatasetCollection = async ({
const startId =
dataset.apiDatasetServer?.apiServer?.basePath ||
dataset.apiDatasetServer?.yuqueServer?.basePath ||
dataset.apiDatasetServer?.feishuServer?.folderToken;
dataset.apiDatasetServer?.feishuServer?.folderToken ||
dataset.apiDatasetServer?.dingtalkServer?.rootNodeId;
// check if the directory is selected
const isDirectorySelected = apiFiles.length === 1 && apiFiles[0].id === RootCollectionId;
......
......@@ -196,6 +196,16 @@ const Dataset = () => {
onClick: () => onSelectDatasetType(DatasetTypeEnum.yuque)
}
]
: []),
...(feConfigs?.show_dataset_dingtalk !== false
? [
{
icon: 'core/dataset/dingtalkDatasetColor',
label: t('dataset:dingtalk_dataset'),
description: t('dataset:dingtalk_dataset_desc'),
onClick: () => onSelectDatasetType(DatasetTypeEnum.dingtalk)
}
]
: [])
]
}
......
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { RootCollectionId } from '@fastgpt/global/core/dataset/collection/constants';
import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants';
const { mockListFiles, mockCreateCollectionAndInsertData, mockCreateOneCollection } = vi.hoisted(
() => ({
mockListFiles: vi.fn(),
mockCreateCollectionAndInsertData: vi.fn(),
mockCreateOneCollection: vi.fn()
})
);
vi.mock('@/service/middleware/entry', () => ({
NextAPI: (handler: any) => handler
}));
vi.mock('@fastgpt/service/support/permission/dataset/auth', () => ({
authDataset: vi.fn()
}));
vi.mock('@fastgpt/service/support/permission/teamLimit', () => ({
checkDatasetIndexLimit: vi.fn()
}));
vi.mock('@fastgpt/service/core/dataset/collection/schema', () => ({
MongoDatasetCollection: {
find: vi.fn(() => ({
lean: vi.fn().mockResolvedValue([])
}))
}
}));
vi.mock('@fastgpt/service/core/dataset/apiDataset', () => ({
getApiDatasetRequest: vi.fn(async () => ({
listFiles: mockListFiles
}))
}));
vi.mock('@fastgpt/service/common/mongo/sessionRun', () => ({
mongoSessionRun: vi.fn((fn: any) => fn('session'))
}));
vi.mock('@fastgpt/service/core/dataset/collection/controller', () => ({
createCollectionAndInsertData: mockCreateCollectionAndInsertData,
createOneCollection: mockCreateOneCollection
}));
import { createApiDatasetCollection } from '@/pages/api/core/dataset/collection/create/apiCollectionV2';
import { getApiDatasetRequest } from '@fastgpt/service/core/dataset/apiDataset';
describe('createApiDatasetCollection', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should use dingtalk rootNodeId when importing root folder recursively', async () => {
mockListFiles.mockResolvedValueOnce([
{
id: 'doc-1',
rawId: 'doc-1',
parentId: 'dingtalk-root',
name: 'Doc 1',
type: 'file',
hasChild: false,
updateTime: new Date(),
createTime: new Date()
}
]);
const dataset = {
_id: 'dataset-id',
teamId: 'team-id',
type: DatasetTypeEnum.dingtalk,
apiDatasetServer: {
dingtalkServer: {
appKey: 'ding-app',
userId: 'user-id',
rootNodeId: 'dingtalk-root'
}
},
permission: {}
} as any;
await createApiDatasetCollection({
datasetId: 'dataset-id',
apiFiles: [
{
id: RootCollectionId,
rawId: RootCollectionId,
parentId: '',
name: 'ROOT_FOLDER',
type: 'folder',
hasChild: true,
updateTime: new Date(),
createTime: new Date()
}
],
customPdfParse: false,
trainingType: 'chunk',
teamId: 'team-id',
tmbId: 'tmb-id',
dataset
} as any);
expect(getApiDatasetRequest).toHaveBeenCalledWith(dataset.apiDatasetServer);
expect(mockListFiles).toHaveBeenCalledWith({
parentId: 'dingtalk-root'
});
expect(mockCreateCollectionAndInsertData).toHaveBeenCalledWith(
expect.objectContaining({
dataset,
createCollectionParams: expect.objectContaining({
apiFileId: 'doc-1',
type: 'apiFile'
}),
session: 'session'
})
);
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment