diff --git a/docSite/assets/imgs/dingtalk-bot-1.png b/docSite/assets/imgs/dingtalk-bot-1.png new file mode 100644 index 000000000000..9f80e313422b Binary files /dev/null and b/docSite/assets/imgs/dingtalk-bot-1.png differ diff --git a/docSite/assets/imgs/dingtalk-bot-2.png b/docSite/assets/imgs/dingtalk-bot-2.png new file mode 100644 index 000000000000..ac0bfeeaeb95 Binary files /dev/null and b/docSite/assets/imgs/dingtalk-bot-2.png differ diff --git a/docSite/assets/imgs/dingtalk-bot-3.png b/docSite/assets/imgs/dingtalk-bot-3.png new file mode 100644 index 000000000000..ef23bf80638a Binary files /dev/null and b/docSite/assets/imgs/dingtalk-bot-3.png differ diff --git a/docSite/assets/imgs/dingtalk-bot-4.png b/docSite/assets/imgs/dingtalk-bot-4.png new file mode 100644 index 000000000000..efcd9f55c07d Binary files /dev/null and b/docSite/assets/imgs/dingtalk-bot-4.png differ diff --git a/docSite/assets/imgs/dingtalk-bot-5.png b/docSite/assets/imgs/dingtalk-bot-5.png new file mode 100644 index 000000000000..34336324c587 Binary files /dev/null and b/docSite/assets/imgs/dingtalk-bot-5.png differ diff --git a/docSite/assets/imgs/dingtalk-bot-6.png b/docSite/assets/imgs/dingtalk-bot-6.png new file mode 100644 index 000000000000..66454a3a9882 Binary files /dev/null and b/docSite/assets/imgs/dingtalk-bot-6.png differ diff --git a/docSite/assets/imgs/dingtalk-bot-7.png b/docSite/assets/imgs/dingtalk-bot-7.png new file mode 100644 index 000000000000..0ad3388432a8 Binary files /dev/null and b/docSite/assets/imgs/dingtalk-bot-7.png differ diff --git a/docSite/assets/imgs/dingtalk-bot-8.png b/docSite/assets/imgs/dingtalk-bot-8.png new file mode 100644 index 000000000000..4ff31a0f2a87 Binary files /dev/null and b/docSite/assets/imgs/dingtalk-bot-8.png differ diff --git a/docSite/assets/imgs/image-25.png b/docSite/assets/imgs/image-25.png new file mode 100644 index 000000000000..751eda1188fd Binary files /dev/null and b/docSite/assets/imgs/image-25.png differ diff --git a/docSite/assets/imgs/image-26.png b/docSite/assets/imgs/image-26.png new file mode 100644 index 000000000000..9be8d6c5899d Binary files /dev/null and b/docSite/assets/imgs/image-26.png differ diff --git a/docSite/assets/imgs/image-27.png b/docSite/assets/imgs/image-27.png new file mode 100644 index 000000000000..23b554871f8f Binary files /dev/null and b/docSite/assets/imgs/image-27.png differ diff --git a/docSite/assets/imgs/image-28.png b/docSite/assets/imgs/image-28.png new file mode 100644 index 000000000000..5ed434726069 Binary files /dev/null and b/docSite/assets/imgs/image-28.png differ diff --git a/docSite/assets/imgs/image-29.png b/docSite/assets/imgs/image-29.png new file mode 100644 index 000000000000..0f5f48a840f7 Binary files /dev/null and b/docSite/assets/imgs/image-29.png differ diff --git a/docSite/assets/imgs/image-30.png b/docSite/assets/imgs/image-30.png new file mode 100644 index 000000000000..6064e16f4697 Binary files /dev/null and b/docSite/assets/imgs/image-30.png differ diff --git a/docSite/assets/imgs/image-31.png b/docSite/assets/imgs/image-31.png new file mode 100644 index 000000000000..eab56632fe1d Binary files /dev/null and b/docSite/assets/imgs/image-31.png differ diff --git a/docSite/assets/imgs/image-32.png b/docSite/assets/imgs/image-32.png new file mode 100644 index 000000000000..079234912164 Binary files /dev/null and b/docSite/assets/imgs/image-32.png differ diff --git a/docSite/assets/imgs/image-33.png b/docSite/assets/imgs/image-33.png new file mode 100644 index 000000000000..819a58e7a1f0 Binary files /dev/null and b/docSite/assets/imgs/image-33.png differ diff --git a/docSite/assets/imgs/image-34.png b/docSite/assets/imgs/image-34.png new file mode 100644 index 000000000000..621ce9e05f7b Binary files /dev/null and b/docSite/assets/imgs/image-34.png differ diff --git a/docSite/assets/imgs/image-35.png b/docSite/assets/imgs/image-35.png new file mode 100644 index 000000000000..1838638749ae Binary files /dev/null and b/docSite/assets/imgs/image-35.png differ diff --git a/docSite/assets/imgs/image-36.png b/docSite/assets/imgs/image-36.png new file mode 100644 index 000000000000..dc15361af9c1 Binary files /dev/null and b/docSite/assets/imgs/image-36.png differ diff --git a/docSite/assets/imgs/image-37.png b/docSite/assets/imgs/image-37.png new file mode 100644 index 000000000000..8a301d85a424 Binary files /dev/null and b/docSite/assets/imgs/image-37.png differ diff --git a/docSite/assets/imgs/image-38.png b/docSite/assets/imgs/image-38.png new file mode 100644 index 000000000000..d4dcf253fd51 Binary files /dev/null and b/docSite/assets/imgs/image-38.png differ diff --git a/docSite/assets/imgs/image-39.png b/docSite/assets/imgs/image-39.png new file mode 100644 index 000000000000..4bed026c9755 Binary files /dev/null and b/docSite/assets/imgs/image-39.png differ diff --git a/docSite/assets/imgs/image-40.png b/docSite/assets/imgs/image-40.png new file mode 100644 index 000000000000..97cfac36d3ea Binary files /dev/null and b/docSite/assets/imgs/image-40.png differ diff --git a/docSite/assets/imgs/image-41.png b/docSite/assets/imgs/image-41.png new file mode 100644 index 000000000000..f988bafd86f6 Binary files /dev/null and b/docSite/assets/imgs/image-41.png differ diff --git a/docSite/assets/imgs/image-42.png b/docSite/assets/imgs/image-42.png new file mode 100644 index 000000000000..8e08110c89d7 Binary files /dev/null and b/docSite/assets/imgs/image-42.png differ diff --git a/docSite/assets/imgs/image-43.png b/docSite/assets/imgs/image-43.png new file mode 100644 index 000000000000..f88434bc7921 Binary files /dev/null and b/docSite/assets/imgs/image-43.png differ diff --git a/docSite/assets/imgs/image-44.png b/docSite/assets/imgs/image-44.png new file mode 100644 index 000000000000..bbd8daba206e Binary files /dev/null and b/docSite/assets/imgs/image-44.png differ diff --git a/docSite/assets/imgs/image-45.png b/docSite/assets/imgs/image-45.png new file mode 100644 index 000000000000..2d9dbdd1bf22 Binary files /dev/null and b/docSite/assets/imgs/image-45.png differ diff --git a/docSite/assets/imgs/image-46.png b/docSite/assets/imgs/image-46.png new file mode 100644 index 000000000000..b9d86449c386 Binary files /dev/null and b/docSite/assets/imgs/image-46.png differ diff --git a/docSite/assets/imgs/image-47.png b/docSite/assets/imgs/image-47.png new file mode 100644 index 000000000000..c84f3a918dd6 Binary files /dev/null and b/docSite/assets/imgs/image-47.png differ diff --git a/docSite/assets/imgs/image-48.png b/docSite/assets/imgs/image-48.png new file mode 100644 index 000000000000..89c802c84403 Binary files /dev/null and b/docSite/assets/imgs/image-48.png differ diff --git a/docSite/assets/imgs/image-49.png b/docSite/assets/imgs/image-49.png new file mode 100644 index 000000000000..8e032f79fb67 Binary files /dev/null and b/docSite/assets/imgs/image-49.png differ diff --git a/docSite/assets/imgs/image-50.png b/docSite/assets/imgs/image-50.png new file mode 100644 index 000000000000..dc9b8a0bfbaa Binary files /dev/null and b/docSite/assets/imgs/image-50.png differ diff --git a/docSite/assets/imgs/searxng_plugin_guide1.png b/docSite/assets/imgs/searxng_plugin_guide1.png new file mode 100644 index 000000000000..8dc3d51c443d Binary files /dev/null and b/docSite/assets/imgs/searxng_plugin_guide1.png differ diff --git a/docSite/content/zh-cn/docs/development/upgrading/4816.md b/docSite/content/zh-cn/docs/development/upgrading/4816.md index 27f9ae10c0f9..77ed2f0a9f7e 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/4816.md +++ b/docSite/content/zh-cn/docs/development/upgrading/4816.md @@ -10,14 +10,20 @@ weight: 808 ## 完整更新内容 -1. +1. 新增 - SearXNG 搜索插件[点击查看教程](/docs/guide/plugins/searxng_plugin_guide/) 2. 新增 - 商业版支持 API 知识库和链接集合定时同步。 3. 新增 - 猜你想问支持选择模型和自定义提示词。 4. 新增 - 钉钉和企微机器人 webhook 插件。 -5. 新增 - sandbox 新增 createHmac 加密全局方法。 -6. 优化 - 工作流/简易模式变量初始化代码,去除监听初始化,避免因渲染顺序不一致导致的失败。 -7. 修复 - 无法自动切换默认语言。增加分享链接,强制执行一次切换默认语言。 -8. 修复 - 数组选择器自动兼容 4.8.13 以前的数据。 -9. 修复 - 站点同步知识库,链接同步时未使用选择器。 -10. 修复 - 简易模式转工作流,没有把系统配置项转化。 -11. 修复 - 插件独立运行,变量初始值未赋上。 \ No newline at end of file +5. 新增 - 商业版支持钉钉 SSO 登录配置。[点击查看教程](/docs/guide/admin/sso_dingtalk/) +6. 新增 - 商业版支持飞书和语雀知识库导入。[点击查看教程](/docs/guide/knowledge_base/lark_dataset/) +7. 新增 - sandbox 新增 createHmac 加密全局方法。 +8. 新增 - 工作流右键支持 +9. 优化 - 工作流/简易模式变量初始化代码,去除监听初始化,避免因渲染顺序不一致导致的失败。 +10. 优化 - 工作流获取数据类型不一致数据时,增加类型转化,避免 undefined。 +11. 修复 - 无法自动切换默认语言。增加分享链接,强制执行一次切换默认语言。 +12. 修复 - 数组选择器自动兼容 4.8.13 以前的数据。 +13. 修复 - 站点同步知识库,链接同步时未使用选择器。 +14. 修复 - 简易模式转工作流,没有把系统配置项转化。 +15. 修复 - 插件独立运行,变量初始值未赋上。 +16. 修复 - 工作流使用弹窗组件时,关闭弹窗后,有时候会出现页面偏移。 +17. 修复 - 插件调试时,日志未保存插件输入参数。 \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/guide/admin/_index.md b/docSite/content/zh-cn/docs/guide/admin/_index.md new file mode 100644 index 000000000000..fba2dab4e1cc --- /dev/null +++ b/docSite/content/zh-cn/docs/guide/admin/_index.md @@ -0,0 +1,9 @@ +--- +weight: 490 +title: '商业版后台' +description: '商业版后台使用教程' +icon: 'chat_bubble' +draft: false +images: [] +--- + diff --git a/docSite/content/zh-cn/docs/guide/admin/sso_dingtalk.md b/docSite/content/zh-cn/docs/guide/admin/sso_dingtalk.md new file mode 100644 index 000000000000..18bb00c81d05 --- /dev/null +++ b/docSite/content/zh-cn/docs/guide/admin/sso_dingtalk.md @@ -0,0 +1,44 @@ +--- +weight: 490 +title: '钉钉 SSO 配置' +description: '钉钉 SSO 登录' +icon: 'chat_bubble' +draft: false +images: [] +--- + +## 1. 注册钉钉应用 + +登录 [钉钉开放平台](https://open-dev.dingtalk.com/fe/app?hash=%23%2Fcorp%2Fapp#/corp/app),创建一个应用。 + +![alt text](/imgs/image-25.png) + +## 2. 配置钉钉应用安全设置 + +点击进入创建好的应用后,点开`安全设置`,配置出口 IP(服务器 IP),和重定向 URL。重定向 URL 填写逻辑: + +`{{fastgpt 域名}}/login/provider` + +![alt text](/imgs/image-26.png) + +## 3. 设置钉钉应用权限 + +点击进入创建好的应用后,点开`权限设置`,开放两个权限: `个人手机号信息`和`通讯录个人信息读权限` + +![alt text](/imgs/image-27.png) + +## 4. 发布应用 + +点击进入创建好的应用后,点开`版本管理与发布`,随便创建一个新版本即可。 + +## 5. 在 FastGPT Admin 配置钉钉应用 id + +名字都是对应上,直接填写即可。 + +| | | +| --- | --- | +| ![alt text](/imgs/image-28.png)| ![alt text](/imgs/image-29.png) | + +## 6. 测试 + +![alt text](/imgs/image-30.png) \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/guide/knowledge_base/lark_dataset.md b/docSite/content/zh-cn/docs/guide/knowledge_base/lark_dataset.md new file mode 100644 index 000000000000..8c7a8ab55b2d --- /dev/null +++ b/docSite/content/zh-cn/docs/guide/knowledge_base/lark_dataset.md @@ -0,0 +1,59 @@ +--- +title: '飞书知识库' +description: 'FastGPT 飞书知识库功能介绍和使用方式' +icon: 'language' +draft: false +toc: true +weight: 405 +--- + +| | | +| --- | --- | +| ![alt text](/imgs/image-39.png) | ![alt text](/imgs/image-40.png) | + +FastGPT v4.8.16 版本开始,商业版用户支持飞书知识库导入,用户可以通过配置飞书应用的 appId 和 appSecret,并选中一个**文档空间的顶层文件夹**来导入飞书知识库。目前处于测试阶段,部分交互有待优化。 + +由于飞书限制,无法直接获取所有文档内容,目前仅可以获取共享空间下文件目录的内容,无法获取个人空间和知识库里的内容。 + + +## 1. 创建飞书应用 + +打开 [飞书开放平台](https://open.feishu.cn/?lang=zh-CN),点击**创建应用**,选择**自建应用**,然后填写应用名称。 + +## 2. 配置应用权限 + +创建应用后,进入应用可以配置相关权限,这里需要增加两个权限: + +1. 获取云空间文件夹下的云文档清单 +2. 查看新版文档 + +![alt text](/imgs/image-41.png) + +## 3. 获取 appId 和 appSecret + +![alt text](/imgs/image-42.png) + +## 4. 给 Folder 增加权限 + +可参考飞书教程: https://open.feishu.cn/document/server-docs/docs/drive-v1/faq#b02e5bfb + +大致总结为: + +1. 把刚刚创建的应用拉入一个群里 +2. 给这个群增加目录权限 + +如果你的目录已经给全员组增加权限了,则可以跳过上面步骤,直接获取 Folder Token。 + +![alt text](/imgs/image-43.png) + +## 5. 获取 Folder Token + +可以页面路径上获取 Folder Token,注意不要把问号复制进来。 + +![alt text](/imgs/image-44.png) + +## 6. 创建知识库 + +根据 3 和 5 获取到的 3 个参数,创建知识库,选择飞书文件库类型,然后填入对应的参数,点击创建。 + +![alt text](/imgs/image-39.png) \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/guide/knowledge_base/yuque_dataset.md b/docSite/content/zh-cn/docs/guide/knowledge_base/yuque_dataset.md new file mode 100644 index 000000000000..96b99026015e --- /dev/null +++ b/docSite/content/zh-cn/docs/guide/knowledge_base/yuque_dataset.md @@ -0,0 +1,42 @@ +--- +title: '语雀文件库' +description: 'FastGPT 语雀文件库功能介绍和使用方式' +icon: 'language' +draft: false +toc: true +weight: 405 +--- + +| | | +| --- | --- | +| ![alt text](/imgs/image-31.png) | ![alt text](/imgs/image-32.png) | + +FastGPT v4.8.16 版本开始,商业版用户支持语雀文件库导入,用户可以通过配置语雀的 token 和 uid 来导入语雀文档库。目前处于测试阶段,部分交互有待优化。 + +## 1. 获取语雀的 token 和 uid + +在语雀首页 - 个人头像 - 设置,可找到对应参数。 + +![alt text](/imgs/image-36.png) + +参考下图获取 Token 和 User ID,注意给 Token 赋值权限: + +| 获取 Token | 增加权限 | 获取 User ID | +| --- | --- | --- | +| ![alt text](/imgs/image-33.png) | ![alt text](/imgs/image-34.png) | ![alt text](/imgs/image-35.png) | + +## 2. 创建知识库 + +使用上一步获取的 token 和 uid,创建知识库,选择语雀文件库类型,然后填入对应的参数,点击创建。 + +![alt text](/imgs/image-37.png) + +![alt text](/imgs/image-31.png) + +## 3. 导入文档 + +创建完知识库后,点击`添加文件`即可导入语雀的文档库,跟随引导即可。 + +语雀知识库支持定时同步功能,每天会不定时的扫描一次,如果文档有更新,则会进行同步,也可以进行手动同步。 + +![alt text](/imgs/image-38.png) \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/guide/plugins/searxng_plugin_guide.md b/docSite/content/zh-cn/docs/guide/plugins/searxng_plugin_guide.md new file mode 100644 index 000000000000..36e1d2c60694 --- /dev/null +++ b/docSite/content/zh-cn/docs/guide/plugins/searxng_plugin_guide.md @@ -0,0 +1,178 @@ +--- +title: "SearXNG 搜索插件配置与使用说明" +description: "FastGPT SearXNG 搜索插件配置指南" +icon: "search" +draft: false +toc: true +weight: 303 +--- + +[SearXNG](https://github.com/searxng/searxng)是一款免费的互联网元搜索引擎,它汇总了来自各种搜索服务和数据库的结果。它不会跟踪或分析用户。用户可以自行部署它进行使用。本文介绍 Searxng 的部署以及接入 FastGPT 插件。 + + +## 1. 部署应用 + +这里介绍在 Sealos 中部署 SearXNG 的方法。Docker 部署,可以直接参考 [SearXNG 官方教程](https://github.com/searxng/searxng)。 + +点击打开 [Sealos 北京区](https://bja.sealos.run/),点击应用部署,并新建一个应用: + +| 打开应用部署 | 点击新建应用 | +| --- | --- | +| ![](/imgs/searxng_plugin_guide1.png) | ![alt text](/imgs/image-45.png) | + +## 2. 部署配置 + +把下面参数,填入配置中: + +* 镜像名: searxng/searxng:latest +* CPU: 0.2 +* 内存: 512M +* 容器暴露端口: 8080 +* 开启公网访问 +* 点击高级配置,填写环境变量和配置文件 + +![alt text](/imgs/image-50.png) + +**环境变量** + +填下面两个内容,主要是为了减小并发,不然内存占用非常大。 + +``` +UWSGI_WORKERS=4 +UWSGI_THREADS=4 +``` + +**配置文件** + +新增一个配置文件,文件名:`/etc/searx/settings.yml` +文件内容: + +```txt +general: + debug: false + instance_name: "searxng" + privacypolicy_url: false + donation_url: false + contact_url: false + enable_metrics: true + open_metrics: '' + +brand: + new_issue_url: https://github.com/searxng/searxng/issues/new + docs_url: https://docs.searxng.org/ + public_instances: https://searx.space + wiki_url: https://github.com/searxng/searxng/wiki + issue_url: https://github.com/searxng/searxng/issues + +search: + safe_search: 0 + autocomplete: "" + autocomplete_min: 4 + default_lang: "auto" + ban_time_on_fail: 5 + max_ban_time_on_fail: 120 + formats: + - html + +server: + port: 8080 + bind_address: "0.0.0.0" + base_url: false + limiter: false + public_instance: false + secret_key: "example" + image_proxy: false + http_protocol_version: "1.0" + method: "POST" + default_http_headers: + X-Content-Type-Options: nosniff + X-Download-Options: noopen + X-Robots-Tag: noindex, nofollow + Referrer-Policy: no-referrer + +redis: + url: false + +ui: + static_path: "" + static_use_hash: false + templates_path: "" + default_theme: simple + default_locale: "" + query_in_title: false + infinite_scroll: false + center_alignment: false + theme_args: + simple_style: auto + +outgoing: + request_timeout: 30.0 + max_request_timeout: 40.0 + pool_connections: 200 + pool_maxsize: 50 + enable_http2: false + retries: 5 + +engines: + + - name: bing + engine: bing + shortcut: bi + +doi_resolvers: + oadoi.org: 'https://oadoi.org/' + doi.org: 'https://doi.org/' + doai.io: 'https://dissem.in/' + sci-hub.se: 'https://sci-hub.se/' + sci-hub.st: 'https://sci-hub.st/' + sci-hub.ru: 'https://sci-hub.ru/' + +default_doi_resolver: 'oadoi.org' +``` + +国内目前只有 Bing 引擎可以正常用,所以上面的配置只配置了 bing 引擎。如果在海外部署,可以使用[Sealos 新加坡可用区](https://cloud.sealos.io/),并配置其他搜索引擎,可以参考[SearXNG 默认配置文件](https://github.com/searxng/searxng/blob/master/searx/settings.yml), 从里面复制一些 engine 配置。例如: + +``` + - name: duckduckgo + engine: duckduckgo + shortcut: ddg + + - name: google + engine: google + shortcut: go +``` + +## 3. FastGPT 使用 + +复制 Sealos 部署后提供的公网地址,填入 FastGPT 的 SearXNG 插件的 URL 中。 + +| 复制公网地址| 填入 URL | +| --- | --- | +| ![alt text](/imgs/image-48.png) | ![alt text](/imgs/image-49.png) | + +## 返回格式 + +* 成功时返回搜索结果数组: + +```Bash +{ + "result": "[{\"title\":\"标题1\",\"link\":\"链接1\",\"snippet\":\"摘要1\"}, ...]" +} +``` + +* 失败时通过 Promise.reject 可能返回错误信息: + +```Bash +- "缺少查询参数" +- "缺少url" +- "Failed to fetch data from Search XNG" +``` + +一般问题来源于参数缺失与服务部署,如有更多问题可在用户群提问。 + +## FAQ + +### 无搜索结果 + +1. 先直接打开外网地址,测试是否可以正常搜索。 +2. 检查是否有超时的搜索引擎,通过 API 调用时不会返回结果。 \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/use-cases/external-integration/dingtalk.md b/docSite/content/zh-cn/docs/use-cases/external-integration/dingtalk.md new file mode 100644 index 000000000000..2bdad35038ab --- /dev/null +++ b/docSite/content/zh-cn/docs/use-cases/external-integration/dingtalk.md @@ -0,0 +1,57 @@ +--- +title: "接入钉钉机器人教程" +description: "FastGPT 接入钉钉机器人教程" +icon: "chat" +draft: false +toc: true +weight: 505 +--- + +从 4.8.16 版本起,FastGPT 商业版支持直接接入钉钉机器人,无需额外的 API。 + +## 1. 创建钉钉企业内部应用 + +1. 在[钉钉开发者后台](https://open-dev.dingtalk.com/fe/app)创建企业内部应用。 + +![图片1](/imgs/dingtalk-bot-1.png) + +2. 获取**Client ID**和**Client Secret**。 + +![图片2](/imgs/dingtalk-bot-2.png) + +## 2. 为 FastGPT 添加发布渠道 + +在 FastGPT 中选择要接入的应用,在**发布渠道**页面,新建一个接入钉钉机器人的发布渠道。 + +将前面拿到的 **Client ID** 和 **Client Secret** 填入配置弹窗中。 + +![图片3](/imgs/dingtalk-bot-3.png) + +创建完成后,点击**请求地址**按钮,然后复制回调地址。 + +## 3. 为应用添加**机器人**应用能力。 + +在钉钉开发者后台,点击左侧**添加应用能力**,为刚刚创建的企业内部应用添加 **机器人** 应用能力。 + +![图片4](/imgs/dingtalk-bot-4.png) + +## 4. 配置机器人回调地址 + +点击左侧**机器人** 应用能力,然后将底部**消息接受模式**设置为**HTTP模式**,消息接收地址填入前面复制的 FastGPT 的回调地址。 + +![图片5](/imgs/dingtalk-bot-5.png) + +调试完成后,点击**发布**。 + +## 5. 发布应用 +机器人发布后,还需要在**版本管理与发布**页面发布应用版本。 + +![图片6](/imgs/dingtalk-bot-6.png) + +点击**创建新版本**后,设置版本号和版本描述后点击保存发布即可。 + +![图片7](/imgs/dingtalk-bot-7.png) + +应用发布后,即可在钉钉企业中使用机器人功能,可对机器人私聊。或者在群组添加机器人后`@机器人`,触发对话。 + +![图片8](/imgs/dingtalk-bot-8.png) diff --git a/packages/global/common/error/code/dataset.ts b/packages/global/common/error/code/dataset.ts index e5b8abd836d0..cca81331f50f 100644 --- a/packages/global/common/error/code/dataset.ts +++ b/packages/global/common/error/code/dataset.ts @@ -12,7 +12,8 @@ export enum DatasetErrEnum { unLinkCollection = 'unLinkCollection', invalidVectorModelOrQAModel = 'invalidVectorModelOrQAModel', notSupportSync = 'notSupportSync', - sameApiCollection = 'sameApiCollection' + sameApiCollection = 'sameApiCollection', + noApiServer = 'noApiServer' } const datasetErr = [ { diff --git a/packages/global/common/middle/tracks/constants.ts b/packages/global/common/middle/tracks/constants.ts new file mode 100644 index 000000000000..f8473fa05763 --- /dev/null +++ b/packages/global/common/middle/tracks/constants.ts @@ -0,0 +1,7 @@ +export enum TrackEnum { + login = 'login', + createApp = 'createApp', + useAppTemplate = 'useAppTemplate', + createDataset = 'createDataset', + appNodes = 'appNodes' +} diff --git a/packages/global/common/middle/tracks/type.d.ts b/packages/global/common/middle/tracks/type.d.ts new file mode 100644 index 000000000000..16d6be3c3004 --- /dev/null +++ b/packages/global/common/middle/tracks/type.d.ts @@ -0,0 +1,18 @@ +import { TrackEnum } from './constants'; +import { OAuthEnum } from '../../../support/user/constant'; +import { AppTypeEnum } from '../../../core/app/constants'; + +export type PushTrackCommonType = { + uid: string; + teamId: string; + tmbId: string; +}; + +export type TrackSchemaType = { + event: TrackEnum; + createTime: Date; + uid?: string; + teamId?: string; + tmbId?: string; + data: Record; +}; diff --git a/packages/global/common/system/types/index.d.ts b/packages/global/common/system/types/index.d.ts index c3e4c80524a2..77c22ba09fbb 100644 --- a/packages/global/common/system/types/index.d.ts +++ b/packages/global/common/system/types/index.d.ts @@ -63,6 +63,7 @@ export type FastGPTFeConfigsType = { github?: string; google?: string; wechat?: string; + dingtalk?: string; microsoft?: { clientId?: string; tenantId?: string; diff --git a/packages/global/core/chat/type.d.ts b/packages/global/core/chat/type.d.ts index 72be2e38869a..a9171b1568e2 100644 --- a/packages/global/core/chat/type.d.ts +++ b/packages/global/core/chat/type.d.ts @@ -16,6 +16,7 @@ import { DatasetSearchModeEnum } from '../dataset/constants'; import { DispatchNodeResponseType } from '../workflow/runtime/type.d'; import { ChatBoxInputType } from '../../../../projects/app/src/components/core/chat/ChatContainer/ChatBox/type'; import { WorkflowInteractiveResponseType } from '../workflow/template/system/interactive/type'; +import { FlowNodeInputItemType } from '../workflow/type/io'; export type ChatSchema = { _id: string; @@ -35,6 +36,7 @@ export type ChatSchema = { variableList?: VariableItemType[]; welcomeText?: string; variables: Record; + pluginInputs?: FlowNodeInputItemType[]; metadata?: Record; }; diff --git a/packages/global/core/dataset/api.d.ts b/packages/global/core/dataset/api.d.ts index 11270bd9f57e..5cf6c860f3f3 100644 --- a/packages/global/core/dataset/api.d.ts +++ b/packages/global/core/dataset/api.d.ts @@ -17,6 +17,8 @@ export type DatasetUpdateBody = { externalReadUrl?: DatasetSchemaType['externalReadUrl']; defaultPermission?: DatasetSchemaType['defaultPermission']; apiServer?: DatasetSchemaType['apiServer']; + yuqueServer?: DatasetSchemaType['yuqueServer']; + feishuServer?: DatasetSchemaType['feishuServer']; // sync schedule autoSync?: boolean; diff --git a/packages/global/core/dataset/apiDataset.d.ts b/packages/global/core/dataset/apiDataset.d.ts index 152fae12640a..1d64a8a59e8b 100644 --- a/packages/global/core/dataset/apiDataset.d.ts +++ b/packages/global/core/dataset/apiDataset.d.ts @@ -22,3 +22,14 @@ export type APIFileContentResponse = { export type APIFileReadResponse = { url: string; }; + +export type FeishuServer = { + appId: string; + appSecret: string; + folderToken: string; +}; + +export type YuqueServer = { + userId: string; + token: string; +}; diff --git a/packages/global/core/dataset/constants.ts b/packages/global/core/dataset/constants.ts index 13a466607089..a522645f9d2f 100644 --- a/packages/global/core/dataset/constants.ts +++ b/packages/global/core/dataset/constants.ts @@ -6,7 +6,9 @@ export enum DatasetTypeEnum { dataset = 'dataset', websiteDataset = 'websiteDataset', // depp link externalFile = 'externalFile', - apiDataset = 'apiDataset' + apiDataset = 'apiDataset', + feishu = 'feishu', + yuque = 'yuque' } export const DatasetTypeMap = { [DatasetTypeEnum.folder]: { @@ -33,6 +35,16 @@ export const DatasetTypeMap = { icon: 'core/dataset/externalDatasetOutline', label: 'api_file', collectionLabel: 'common.File' + }, + [DatasetTypeEnum.feishu]: { + icon: 'core/dataset/feishuDatasetOutline', + label: 'feishu_dataset', + collectionLabel: 'common.File' + }, + [DatasetTypeEnum.yuque]: { + icon: 'core/dataset/yuqueDatasetOutline', + label: 'yuque_dataset', + collectionLabel: 'common.File' } }; diff --git a/packages/global/core/dataset/type.d.ts b/packages/global/core/dataset/type.d.ts index dddea6256c2d..e45a7446398a 100644 --- a/packages/global/core/dataset/type.d.ts +++ b/packages/global/core/dataset/type.d.ts @@ -10,7 +10,7 @@ import { } from './constants'; import { DatasetPermission } from '../../support/permission/dataset/controller'; import { Permission } from '../../support/permission/controller'; -import { APIFileServer } from './apiDataset'; +import { APIFileServer, FeishuServer, YuqueServer } from './apiDataset'; export type DatasetSchemaType = { _id: string; @@ -33,6 +33,8 @@ export type DatasetSchemaType = { }; inheritPermission: boolean; apiServer?: APIFileServer; + feishuServer?: FeishuServer; + yuqueServer?: YuqueServer; autoSync?: boolean; diff --git a/packages/global/support/outLink/constant.ts b/packages/global/support/outLink/constant.ts index aaa1bafb6927..0fcba2ec4093 100644 --- a/packages/global/support/outLink/constant.ts +++ b/packages/global/support/outLink/constant.ts @@ -3,6 +3,7 @@ export enum PublishChannelEnum { iframe = 'iframe', apikey = 'apikey', feishu = 'feishu', + dingtalk = 'dingtalk', wecom = 'wecom', officialAccount = 'official_account' } diff --git a/packages/global/support/outLink/type.d.ts b/packages/global/support/outLink/type.d.ts index e33179c1a1f7..1b0447eb6e7b 100644 --- a/packages/global/support/outLink/type.d.ts +++ b/packages/global/support/outLink/type.d.ts @@ -14,6 +14,11 @@ export interface FeishuAppType { verificationToken?: string; } +export interface DingtalkAppType { + clientId: string; + clientSecret: string; +} + export interface WecomAppType { AgentId: string; CorpId: string; @@ -36,7 +41,12 @@ export interface OffiAccountAppType { // because we can not reply anything in 15s. Thus, the wechat server will treat this request as a failed request. } -export type OutlinkAppType = FeishuAppType | WecomAppType | OffiAccountAppType | undefined; +export type OutlinkAppType = + | FeishuAppType + | WecomAppType + | OffiAccountAppType + | DingtalkAppType + | undefined; export type OutLinkSchema = { _id: string; diff --git a/packages/global/support/user/api.d.ts b/packages/global/support/user/api.d.ts index 85f60bf2be2a..31da306b0b69 100644 --- a/packages/global/support/user/api.d.ts +++ b/packages/global/support/user/api.d.ts @@ -1,4 +1,5 @@ import { OAuthEnum } from './constant'; +import { TrackRegisterParams } from './login/api'; export type PostLoginProps = { username: string; @@ -9,8 +10,7 @@ export type OauthLoginProps = { type: `${OAuthEnum}`; code: string; callbackUrl: string; - inviterId?: string; -}; +} & TrackRegisterParams; export type WxLoginProps = { inviterId?: string; diff --git a/packages/global/support/user/constant.ts b/packages/global/support/user/constant.ts index 0fae675cba59..bd696afc7fef 100644 --- a/packages/global/support/user/constant.ts +++ b/packages/global/support/user/constant.ts @@ -16,5 +16,6 @@ export enum OAuthEnum { google = 'google', wechat = 'wechat', microsoft = 'microsoft', + dingtalk = 'dingtalk', sso = 'sso' } diff --git a/packages/global/support/user/login/api.d.ts b/packages/global/support/user/login/api.d.ts index d71181491344..d994a24ec4a6 100644 --- a/packages/global/support/user/login/api.d.ts +++ b/packages/global/support/user/login/api.d.ts @@ -2,3 +2,17 @@ export type GetWXLoginQRResponse = { code: string; codeUrl: string; }; + +export type TrackRegisterParams = { + inviterId?: string; + bd_vid?: string; + fastgpt_sem?: { + keyword: string; + }; + sourceDomain?: string; +}; +export type AccountRegisterBody = { + username: string; + code: string; + password: string; +} & TrackRegisterParams; diff --git a/packages/global/support/user/team/controller.d.ts b/packages/global/support/user/team/controller.d.ts index d485b39ae2a0..69fcc6bb36e1 100644 --- a/packages/global/support/user/team/controller.d.ts +++ b/packages/global/support/user/team/controller.d.ts @@ -12,6 +12,7 @@ export type CreateTeamProps = { avatar?: string; defaultTeam?: boolean; lafAccount?: LafAccountType; + memberName?: string; }; export type UpdateTeamProps = { name?: string; diff --git a/packages/global/support/wallet/usage/constants.ts b/packages/global/support/wallet/usage/constants.ts index 6e0dab53b53b..dfbff7f4df00 100644 --- a/packages/global/support/wallet/usage/constants.ts +++ b/packages/global/support/wallet/usage/constants.ts @@ -9,6 +9,7 @@ export enum UsageSourceEnum { share = 'share', wecom = 'wecom', feishu = 'feishu', + dingtalk = 'dingtalk', official_account = 'official_account' } @@ -39,5 +40,8 @@ export const UsageSourceMap = { }, [UsageSourceEnum.wecom]: { label: i18nT('user:usage.wecom') + }, + [UsageSourceEnum.dingtalk]: { + label: i18nT('user:usage.dingtalk') } }; diff --git a/packages/global/support/wallet/usage/tools.ts b/packages/global/support/wallet/usage/tools.ts index 33dae38923fe..da29816058fe 100644 --- a/packages/global/support/wallet/usage/tools.ts +++ b/packages/global/support/wallet/usage/tools.ts @@ -37,6 +37,8 @@ export const getUsageSourceByPublishChannel = (publishchannel: PublishChannelEnu return UsageSourceEnum.wecom; case PublishChannelEnum.officialAccount: return UsageSourceEnum.official_account; + case PublishChannelEnum.dingtalk: + return UsageSourceEnum.dingtalk; default: return UsageSourceEnum.fastgpt; } diff --git a/packages/plugins/package.json b/packages/plugins/package.json index 3120ad41e1a8..639a38e68e8c 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "type": "module", "dependencies": { + "cheerio": "1.0.0-rc.12", "@types/pg": "^8.6.6", "axios": "^1.5.1", "duck-duck-scrape": "^2.2.5", diff --git a/packages/plugins/register.ts b/packages/plugins/register.ts index 1085a7548687..2509422d2f7b 100644 --- a/packages/plugins/register.ts +++ b/packages/plugins/register.ts @@ -27,7 +27,8 @@ const packagePluginList = [ 'wiki', 'databaseConnection', 'Doc2X', - 'Doc2X/PDF2text' + 'Doc2X/PDF2text', + 'searchXNG' ]; export const list = [...staticPluginList, ...packagePluginList]; diff --git a/packages/plugins/src/searchXNG/index.ts b/packages/plugins/src/searchXNG/index.ts new file mode 100644 index 000000000000..c325f64a1b7b --- /dev/null +++ b/packages/plugins/src/searchXNG/index.ts @@ -0,0 +1,66 @@ +import { delay } from '@fastgpt/global/common/system/utils'; +import * as cheerio from 'cheerio'; +import { i18nT } from '../../../web/i18n/utils'; + +type Props = { + query: string; + url: string; +}; + +interface SearchResult { + title: string; + link: string; + snippet: string; +} + +type Response = Promise<{ + result: string; + error?: Record; +}>; + +const main = async (props: Props, retry = 3): Response => { + const { query, url } = props; + + if (!query) { + return Promise.reject(i18nT('chat:not_query')); + } + + if (!url) { + return Promise.reject('Can not find url'); + } + + try { + const response = await fetch(`${url}?q=${encodeURIComponent(query)}&language=auto`); + const html = await response.text(); + const $ = cheerio.load(html, { + xml: false, + decodeEntities: true + }); + + const results: SearchResult[] = []; + + $('.result').each((_: number, element: cheerio.Element) => { + const $element = $(element); + results.push({ + title: $element.find('h3').text().trim(), + link: $element.find('a').first().attr('href') || '', + snippet: $element.find('.content').text().trim() + }); + }); + + return { + result: JSON.stringify(results.slice(0, 10)) + }; + } catch (error) { + console.log(error); + if (retry <= 0) { + console.log('Search XNG error', { error }); + return Promise.reject('Failed to fetch data from Search XNG'); + } + + await delay(Math.random() * 2000); + return main(props, retry - 1); + } +}; + +export default main; diff --git a/packages/plugins/src/searchXNG/template.json b/packages/plugins/src/searchXNG/template.json new file mode 100644 index 000000000000..ea6affb8b5f8 --- /dev/null +++ b/packages/plugins/src/searchXNG/template.json @@ -0,0 +1,414 @@ +{ + "author": "", + "version": "4816", + "name": "Search XNG 搜索", + "avatar": "core/workflow/template/searxng", + "intro": "使用 Search XNG 服务进行搜索。", + "courseUrl": "/docs/guide/plugins/searxng_plugin_guide/", + "showStatus": true, + "weight": 10, + + "isTool": true, + "templateType": "search", + + "workflow": { + "nodes": [ + { + "nodeId": "pluginInput", + "name": "插件开始", + "intro": "可以配置插件需要哪些输入,利用这些输入来运行插件", + "avatar": "core/workflow/template/workflowStart", + "flowNodeType": "pluginInput", + "showStatus": false, + "position": { + "x": 482.8986144681427, + "y": -77.56127656499825 + }, + "version": "481", + "inputs": [ + { + "renderTypeList": ["input", "reference"], + "selectedTypeIndex": 0, + "valueType": "string", + "canEdit": true, + "key": "query", + "label": "query", + "description": "检索词", + "required": true, + "toolDescription": "检索词" + }, + { + "renderTypeList": ["input", "reference"], + "selectedTypeIndex": 0, + "valueType": "string", + "canEdit": true, + "key": "url", + "label": "url", + "description": "部署的searXNG服务的链接", + "defaultValue": "", + "list": [ + { + "label": "", + "value": "" + } + ], + "maxFiles": 5, + "canSelectFile": true, + "canSelectImg": true, + "required": true, + "toolDescription": "部署的searXNG服务的链接" + } + ], + "outputs": [ + { + "id": "query", + "valueType": "string", + "key": "query", + "label": "query", + "type": "hidden" + }, + { + "id": "url", + "valueType": "string", + "key": "url", + "label": "url", + "type": "hidden" + } + ] + }, + { + "nodeId": "pluginOutput", + "name": "插件输出", + "intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出", + "avatar": "core/workflow/template/pluginOutput", + "flowNodeType": "pluginOutput", + "showStatus": false, + "position": { + "x": 1763.7518709731714, + "y": -55.56127656499825 + }, + "version": "481", + "inputs": [ + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "result", + "label": "result", + "description": " 检索结果", + "value": ["hjnVuJAOwyXV", "lEyy5QqyIBrK"] + }, + { + "renderTypeList": ["reference"], + "valueType": "object", + "canEdit": true, + "key": "error", + "label": "error", + "isToolOutput": true, + "description": "请求错误", + "required": true, + "value": ["hjnVuJAOwyXV", "error"] + } + ], + "outputs": [] + }, + { + "nodeId": "hjnVuJAOwyXV", + "name": "HTTP 请求", + "intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)", + "avatar": "core/workflow/template/httpRequest", + "flowNodeType": "httpRequest468", + "showStatus": true, + "position": { + "x": 1054.6774638324207, + "y": -403.06127656499825 + }, + "version": "481", + "inputs": [ + { + "key": "system_addInputParam", + "renderTypeList": ["addInputParam"], + "valueType": "dynamic", + "label": "", + "required": false, + "description": "common:core.module.input.description.HTTP Dynamic Input", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpMethod", + "renderTypeList": ["custom"], + "valueType": "string", + "label": "", + "value": "POST", + "required": true, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpTimeout", + "renderTypeList": ["custom"], + "valueType": "number", + "label": "", + "value": 30, + "min": 5, + "max": 600, + "required": true, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpReqUrl", + "renderTypeList": ["hidden"], + "valueType": "string", + "label": "", + "description": "common:core.module.input.description.Http Request Url", + "placeholder": "https://api.ai.com/getInventory", + "required": false, + "value": "searchXNG", + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpHeader", + "renderTypeList": ["custom"], + "valueType": "any", + "value": [], + "label": "", + "description": "common:core.module.input.description.Http Request Header", + "placeholder": "common:core.module.input.description.Http Request Header", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpParams", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": [], + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpJsonBody", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": "{\n \"query\": \"{{query}}\",\n \"url\": \"{{url}}\"\n}", + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpFormBody", + "renderTypeList": ["hidden"], + "valueType": "any", + "value": [], + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "key": "system_httpContentType", + "renderTypeList": ["hidden"], + "valueType": "string", + "value": "json", + "label": "", + "required": false, + "debugLabel": "", + "toolDescription": "" + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "query", + "label": "query", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["pluginInput", "query"] + }, + { + "renderTypeList": ["reference"], + "valueType": "string", + "canEdit": true, + "key": "url", + "label": "url", + "customInputConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "arrayAny", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": true + }, + "required": true, + "value": ["pluginInput", "url"] + } + ], + "outputs": [ + { + "id": "error", + "key": "error", + "label": "workflow:request_error", + "description": "HTTP请求错误信息,成功时返回空", + "valueType": "object", + "type": "static" + }, + { + "id": "httpRawResponse", + "key": "httpRawResponse", + "required": true, + "label": "workflow:raw_response", + "description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。", + "valueType": "any", + "type": "static" + }, + { + "id": "system_addOutputParam", + "key": "system_addOutputParam", + "type": "dynamic", + "valueType": "dynamic", + "label": "", + "customFieldConfig": { + "selectValueTypeList": [ + "string", + "number", + "boolean", + "object", + "arrayString", + "arrayNumber", + "arrayBoolean", + "arrayObject", + "any", + "chatHistory", + "datasetQuote", + "dynamic", + "selectApp", + "selectDataset" + ], + "showDescription": false, + "showDefaultValue": false + } + }, + { + "id": "lEyy5QqyIBrK", + "valueType": "string", + "type": "dynamic", + "key": "result", + "label": "result" + } + ] + }, + { + "nodeId": "f1mRh1D85H2D", + "name": "系统配置", + "intro": "", + "avatar": "core/workflow/template/systemConfig", + "flowNodeType": "pluginConfig", + "position": { + "x": -28.511358745511643, + "y": -103.56127656499825 + }, + "version": "4811", + "inputs": [], + "outputs": [] + } + ], + "edges": [ + { + "source": "pluginInput", + "target": "hjnVuJAOwyXV", + "sourceHandle": "pluginInput-source-right", + "targetHandle": "hjnVuJAOwyXV-target-left" + }, + { + "source": "hjnVuJAOwyXV", + "target": "pluginOutput", + "sourceHandle": "hjnVuJAOwyXV-source-right", + "targetHandle": "pluginOutput-target-left" + } + ], + "chatConfig": { + "welcomeText": "", + "variables": [], + "questionGuide": false, + "ttsConfig": { + "type": "web" + }, + "whisperConfig": { + "open": false, + "autoSend": false, + "autoTTSResponse": false + }, + "chatInputGuide": { + "open": false, + "textList": [], + "customUrl": "" + }, + "instruction": "", + "autoExecute": { + "open": false, + "defaultPrompt": "" + }, + "_id": "67611a5eed7efa452a6e50c5" + } + } +} diff --git a/packages/service/common/middle/tracks/schema.ts b/packages/service/common/middle/tracks/schema.ts new file mode 100644 index 000000000000..5d9b3adee5b6 --- /dev/null +++ b/packages/service/common/middle/tracks/schema.ts @@ -0,0 +1,20 @@ +import { TrackSchemaType } from '@fastgpt/global/common/middle/tracks/type'; +import { getMongoModel, Schema } from '../../mongo'; +import { TrackEnum } from '@fastgpt/global/common/middle/tracks/constants'; + +const TrackSchema = new Schema({ + event: { type: String, required: true, enum: Object.values(TrackEnum) }, + uid: String, + teamId: String, + tmbId: String, + createTime: { type: Date, default: () => new Date() }, + data: Object +}); + +try { + TrackSchema.index({ event: 1 }); +} catch (error) { + console.log(error); +} + +export const TrackModel = getMongoModel('track', TrackSchema); diff --git a/packages/service/common/middle/tracks/utils.ts b/packages/service/common/middle/tracks/utils.ts new file mode 100644 index 000000000000..e56cd77c9065 --- /dev/null +++ b/packages/service/common/middle/tracks/utils.ts @@ -0,0 +1,62 @@ +import { PushTrackCommonType } from '@fastgpt/global/common/middle/tracks/type'; +import { TrackModel } from './schema'; +import { TrackEnum } from '@fastgpt/global/common/middle/tracks/constants'; +import { addLog } from '../../system/log'; +import { OAuthEnum } from '@fastgpt/global/support/user/constant'; +import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; +import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; +import { getAppLatestVersion } from '../../../core/app/version/controller'; + +const createTrack = ({ event, data }: { event: TrackEnum; data: Record }) => { + if (!global.feConfigs?.isPlus) return; + addLog.info('Push tracks', { + event, + ...data + }); + + const { uid, teamId, tmbId, ...props } = data; + + return TrackModel.create({ + event, + uid, + teamId, + tmbId, + data: props + }); +}; +export const pushTrack = { + login: (data: PushTrackCommonType & { type: `${OAuthEnum}` | 'password' }) => { + return createTrack({ + event: TrackEnum.login, + data + }); + }, + createApp: (data: PushTrackCommonType & { type: AppTypeEnum }) => { + return createTrack({ + event: TrackEnum.createApp, + data + }); + }, + createDataset: (data: PushTrackCommonType & { type: DatasetTypeEnum }) => { + return createTrack({ + event: TrackEnum.createDataset, + data + }); + }, + countAppNodes: async (data: PushTrackCommonType & { appId: string }) => { + try { + const { nodes } = await getAppLatestVersion(data.appId); + const nodeTypeList = nodes.map((node) => ({ + type: node.flowNodeType, + pluginId: node.pluginId + })); + return createTrack({ + event: TrackEnum.appNodes, + data: { + ...data, + nodeTypeList + } + }); + } catch (error) {} + } +}; diff --git a/packages/service/core/chat/chatSchema.ts b/packages/service/core/chat/chatSchema.ts index 4aae6eef2b9f..5f5a2f403d31 100644 --- a/packages/service/core/chat/chatSchema.ts +++ b/packages/service/core/chat/chatSchema.ts @@ -72,6 +72,7 @@ const ChatSchema = new Schema({ type: Object, default: {} }, + pluginInputs: Array, metadata: { //For special storage type: Object, diff --git a/packages/service/core/chat/saveChat.ts b/packages/service/core/chat/saveChat.ts index 4004a8a1cc0c..c5f7b0708f47 100644 --- a/packages/service/core/chat/saveChat.ts +++ b/packages/service/core/chat/saveChat.ts @@ -10,6 +10,7 @@ import { getAppChatConfig, getGuideModule } from '@fastgpt/global/core/workflow/ import { AppChatConfigType } from '@fastgpt/global/core/app/type'; import { mergeChatResponseData } from '@fastgpt/global/core/chat/utils'; import { pushChatLog } from './pushChatLog'; +import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; type Props = { chatId: string; @@ -62,6 +63,9 @@ export async function saveChat({ systemConfigNode: getGuideModule(nodes), isPublicFetch: false }); + const pluginInputs = nodes?.find( + (node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput + )?.inputs; await mongoSessionRun(async (session) => { const [{ _id: chatItemIdHuman }, { _id: chatItemIdAi }] = await MongoChatItem.insertMany( @@ -89,6 +93,7 @@ export async function saveChat({ variableList, welcomeText, variables: variables || {}, + pluginInputs, title: newTitle, source, shareId, diff --git a/packages/service/core/dataset/read.ts b/packages/service/core/dataset/read.ts index d870f39235a5..5d84e065b074 100644 --- a/packages/service/core/dataset/read.ts +++ b/packages/service/core/dataset/read.ts @@ -7,8 +7,9 @@ import { TextSplitProps, splitText2Chunks } from '@fastgpt/global/common/string/ import axios from 'axios'; import { readRawContentByFileBuffer } from '../../common/file/read/utils'; import { parseFileExtensionFromUrl } from '@fastgpt/global/common/string/tools'; -import { APIFileServer } from '@fastgpt/global/core/dataset/apiDataset'; +import { APIFileServer, FeishuServer, YuqueServer } from '@fastgpt/global/core/dataset/apiDataset'; import { useApiDatasetRequest } from './apiDataset/api'; +import { POST } from '../../common/api/plusRequest'; export const readFileRawTextByUrl = async ({ teamId, @@ -53,7 +54,9 @@ export const readDatasetSourceRawText = async ({ isQAImport, selector, externalFileId, - apiServer + apiServer, + feishuServer, + yuqueServer }: { teamId: string; type: DatasetSourceReadTypeEnum; @@ -63,6 +66,8 @@ export const readDatasetSourceRawText = async ({ selector?: string; // link selector externalFileId?: string; // external file dataset apiServer?: APIFileServer; // api dataset + feishuServer?: FeishuServer; // feishu dataset + yuqueServer?: YuqueServer; // yuque dataset }): Promise => { if (type === DatasetSourceReadTypeEnum.fileLocal) { const { rawText } = await readFileContentFromMongo({ @@ -88,28 +93,45 @@ export const readDatasetSourceRawText = async ({ }); return rawText; } else if (type === DatasetSourceReadTypeEnum.apiFile) { - if (!apiServer) return Promise.reject('apiServer not found'); const rawText = await readApiServerFileContent({ apiServer, + feishuServer, + yuqueServer, apiFileId: sourceId, teamId }); return rawText; } - return ''; }; export const readApiServerFileContent = async ({ apiServer, + feishuServer, + yuqueServer, apiFileId, teamId }: { - apiServer: APIFileServer; + apiServer?: APIFileServer; + feishuServer?: FeishuServer; + yuqueServer?: YuqueServer; apiFileId: string; teamId: string; }) => { - return useApiDatasetRequest({ apiServer }).getFileContent({ teamId, apiFileId }); + if (apiServer) { + return useApiDatasetRequest({ apiServer }).getFileContent({ teamId, apiFileId }); + } + + if (feishuServer || yuqueServer) { + return POST(`/core/dataset/systemApiDataset`, { + type: 'content', + feishuServer, + yuqueServer, + apiFileId + }); + } + + return Promise.reject('No apiServer or feishuServer or yuqueServer'); }; export const rawText2Chunks = ({ diff --git a/packages/service/core/dataset/schema.ts b/packages/service/core/dataset/schema.ts index 6143004d7fd6..f8f80ef4deab 100644 --- a/packages/service/core/dataset/schema.ts +++ b/packages/service/core/dataset/schema.ts @@ -90,6 +90,12 @@ const DatasetSchema = new Schema({ apiServer: { type: Object }, + feishuServer: { + type: Object + }, + yuqueServer: { + type: Object + }, autoSync: Boolean, diff --git a/packages/service/core/workflow/dispatch/index.ts b/packages/service/core/workflow/dispatch/index.ts index 53753b41b847..e02c3d743faa 100644 --- a/packages/service/core/workflow/dispatch/index.ts +++ b/packages/service/core/workflow/dispatch/index.ts @@ -42,8 +42,7 @@ import { filterWorkflowEdges, checkNodeRunStatus, textAdaptGptResponse, - replaceEditorVariable, - formatVariableValByType + replaceEditorVariable } from '@fastgpt/global/core/workflow/runtime/utils'; import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type'; import { dispatchRunTools } from './agent/runTool/index'; diff --git a/packages/service/support/permission/dataset/auth.ts b/packages/service/support/permission/dataset/auth.ts index da58c7c9b7af..dbe2c811b029 100644 --- a/packages/service/support/permission/dataset/auth.ts +++ b/packages/service/support/permission/dataset/auth.ts @@ -172,7 +172,7 @@ export async function authDatasetCollection({ collection: CollectionWithDatasetType; } > { - const { teamId, tmbId, isRoot: isRootFromHeader } = await parseHeaderCert(props); + const { teamId, tmbId, userId, isRoot: isRootFromHeader } = await parseHeaderCert(props); const collection = await getCollectionWithDataset(collectionId); if (!collection) { @@ -187,6 +187,7 @@ export async function authDatasetCollection({ }); return { + userId, teamId, tmbId, collection, diff --git a/packages/service/support/permission/type.d.ts b/packages/service/support/permission/type.d.ts index 22b79d68e7de..91d03c6da99b 100644 --- a/packages/service/support/permission/type.d.ts +++ b/packages/service/support/permission/type.d.ts @@ -24,6 +24,7 @@ type authModeType = { export type AuthModeType = RequireAtLeastOne; export type AuthResponseType = { + userId: string; teamId: string; tmbId: string; authType?: `${AuthUserTypeEnum}`; diff --git a/packages/service/support/user/schema.ts b/packages/service/support/user/schema.ts index c3dcc6852adf..6e54982efc27 100644 --- a/packages/service/support/user/schema.ts +++ b/packages/service/support/user/schema.ts @@ -49,11 +49,7 @@ const UserSchema = new Schema({ type: String, default: defaultAvatars[Math.floor(Math.random() * defaultAvatars.length)] }, - inviterId: { - // 谁邀请注册的 - type: Schema.Types.ObjectId, - ref: userCollectionName - }, + promotionRate: { type: Number, default: 15 @@ -71,9 +67,14 @@ const UserSchema = new Schema({ lastLoginTmbId: { type: Schema.Types.ObjectId }, - fastgpt_sem: { - type: Object - } + + inviterId: { + // 谁邀请注册的 + type: Schema.Types.ObjectId, + ref: userCollectionName + }, + fastgpt_sem: Object, + sourceDomain: String }); try { diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts index 6fd523576977..1308d0f1e043 100644 --- a/packages/web/components/common/Icon/constants.ts +++ b/packages/web/components/common/Icon/constants.ts @@ -91,6 +91,7 @@ export const iconPaths = { 'common/voiceLight': () => import('./icons/common/voiceLight.svg'), 'common/warn': () => import('./icons/common/warn.svg'), 'common/wechatFill': () => import('./icons/common/wechatFill.svg'), + 'common/dingtalkFill': () => import('./icons/common/dingtalkFill.svg'), configmap: () => import('./icons/configmap.svg'), copy: () => import('./icons/copy.svg'), 'core/app/aiFill': () => import('./icons/core/app/aiFill.svg'), @@ -169,6 +170,9 @@ export const iconPaths = { import('./icons/core/dataset/externalDatasetColor.svg'), 'core/dataset/externalDatasetOutline': () => import('./icons/core/dataset/externalDatasetOutline.svg'), + 'core/dataset/feishuDatasetColor': () => import('./icons/core/dataset/feishuDatasetColor.svg'), + 'core/dataset/feishuDatasetOutline': () => + import('./icons/core/dataset/feishuDatasetOutline.svg'), 'core/dataset/fileCollection': () => import('./icons/core/dataset/fileCollection.svg'), 'core/dataset/fullTextRecall': () => import('./icons/core/dataset/fullTextRecall.svg'), 'core/dataset/manualCollection': () => import('./icons/core/dataset/manualCollection.svg'), @@ -182,6 +186,8 @@ export const iconPaths = { 'core/dataset/websiteDatasetColor': () => import('./icons/core/dataset/websiteDatasetColor.svg'), 'core/dataset/websiteDatasetOutline': () => import('./icons/core/dataset/websiteDatasetOutline.svg'), + 'core/dataset/yuqueDatasetColor': () => import('./icons/core/dataset/yuqueDatasetColor.svg'), + 'core/dataset/yuqueDatasetOutline': () => import('./icons/core/dataset/yuqueDatasetOutline.svg'), 'core/modules/basicNode': () => import('./icons/core/modules/basicNode.svg'), 'core/modules/fixview': () => import('./icons/core/modules/fixview.svg'), 'core/modules/flowLight': () => import('./icons/core/modules/flowLight.svg'), @@ -250,6 +256,7 @@ export const iconPaths = { 'core/workflow/template/formInput': () => import('./icons/core/workflow/template/formInput.svg'), 'core/workflow/template/getTime': () => import('./icons/core/workflow/template/getTime.svg'), 'core/workflow/template/google': () => import('./icons/core/workflow/template/google.svg'), + 'core/workflow/template/searxng': () => import('./icons/core/workflow/template/searxng.svg'), 'core/workflow/template/httpRequest': () => import('./icons/core/workflow/template/httpRequest.svg'), 'core/workflow/template/ifelse': () => import('./icons/core/workflow/template/ifelse.svg'), diff --git a/packages/web/components/common/Icon/icons/common/dingtalkFill.svg b/packages/web/components/common/Icon/icons/common/dingtalkFill.svg new file mode 100644 index 000000000000..742a73f27684 --- /dev/null +++ b/packages/web/components/common/Icon/icons/common/dingtalkFill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/dataset/feishuDatasetColor.svg b/packages/web/components/common/Icon/icons/core/dataset/feishuDatasetColor.svg new file mode 100644 index 000000000000..b52ad1bbf994 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/dataset/feishuDatasetColor.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/web/components/common/Icon/icons/core/dataset/feishuDatasetOutline.svg b/packages/web/components/common/Icon/icons/core/dataset/feishuDatasetOutline.svg new file mode 100644 index 000000000000..7e5b373855c2 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/dataset/feishuDatasetOutline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/dataset/yuqueDatasetColor.svg b/packages/web/components/common/Icon/icons/core/dataset/yuqueDatasetColor.svg new file mode 100644 index 000000000000..fb80f52a2370 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/dataset/yuqueDatasetColor.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/web/components/common/Icon/icons/core/dataset/yuqueDatasetOutline.svg b/packages/web/components/common/Icon/icons/core/dataset/yuqueDatasetOutline.svg new file mode 100644 index 000000000000..97daba221c3a --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/dataset/yuqueDatasetOutline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/web/components/common/Icon/icons/core/workflow/template/searxng.svg b/packages/web/components/common/Icon/icons/core/workflow/template/searxng.svg new file mode 100644 index 000000000000..5b55fa275ecb --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/workflow/template/searxng.svg @@ -0,0 +1,96 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/packages/web/components/common/Icon/icons/plugins/dingding.svg b/packages/web/components/common/Icon/icons/plugins/dingding.svg index 53d772393354..c204d8506cdf 100644 --- a/packages/web/components/common/Icon/icons/plugins/dingding.svg +++ b/packages/web/components/common/Icon/icons/plugins/dingding.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/packages/web/components/common/MyModal/index.tsx b/packages/web/components/common/MyModal/index.tsx index dc6fd2138bdc..051d4813f0ab 100644 --- a/packages/web/components/common/MyModal/index.tsx +++ b/packages/web/components/common/MyModal/index.tsx @@ -50,6 +50,8 @@ const MyModal = ({ autoFocus={false} isCentered={isPc ? isCentered : true} blockScrollOnMount={false} + allowPinchZoom + scrollBehavior={'inside'} closeOnOverlayClick={closeOnOverlayClick} > diff --git a/packages/web/i18n/en/app.json b/packages/web/i18n/en/app.json index 19fe4d351cb3..1b63bdde9f03 100644 --- a/packages/web/i18n/en/app.json +++ b/packages/web/i18n/en/app.json @@ -82,6 +82,7 @@ "month.unit": "Day", "move.hint": "After moving, the selected application/folder will inherit the permission settings of the new folder, and the original permission settings will become invalid.", "move_app": "Move Application", + "node_not_intro": "This node is not introduced", "not_json_file": "Please select a JSON file", "open_auto_execute": "Enable automatic execution", "open_vision_function_tip": "Models with icon switches have image recognition capabilities. \nAfter being turned on, the model will parse the pictures in the file link and automatically parse the pictures in the user's question (user question ≤ 500 words).", diff --git a/packages/web/i18n/en/chat.json b/packages/web/i18n/en/chat.json index c77db871dd41..b930d44afb40 100644 --- a/packages/web/i18n/en/chat.json +++ b/packages/web/i18n/en/chat.json @@ -33,6 +33,7 @@ "multiple_AI_conversations": "Multiple AI Conversations", "new_input_guide_lexicon": "New Lexicon", "no_workflow_response": "No workflow data", + "not_query": "Missing query content", "not_select_file": "No file selected", "plugins_output": "Plugin Output", "question_tip": "From top to bottom, the response order of each module", diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index 500ad35514db..30505afad841 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -841,7 +841,6 @@ "dataset.Create Folder": "Create Folder", "dataset.Create manual collection": "Create Manual Dataset", "dataset.Delete Dataset Error": "Delete Dataset Error", - "dataset.Edit API Service": "Edit API Service", "dataset.Edit Folder": "Edit Folder", "dataset.Edit Info": "Edit Information", "dataset.Export": "Export", diff --git a/packages/web/i18n/en/dataset.json b/packages/web/i18n/en/dataset.json index b598e0778ca6..0a36bbc3aafb 100644 --- a/packages/web/i18n/en/dataset.json +++ b/packages/web/i18n/en/dataset.json @@ -25,6 +25,7 @@ "dataset.Unsupported operation": "dataset.Unsupported operation", "dataset.no_collections": "No datasets available", "dataset.no_tags": "No tags available", + "edit_dataset_config": "Edit knowledge base configuration", "error.collectionNotFound": "Collection not found~", "external_file": "External File Library", "external_file_dataset_desc": "Import files from an external file library to build a Dataset. The files will not be stored again.", @@ -32,6 +33,9 @@ "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", "file_model_function_tip": "Enhances indexing and QA generation", "filename": "Filename", "folder_dataset": "Folder", @@ -69,5 +73,8 @@ "training_mode": "Chunk mode", "vector_model_max_tokens_tip": "Each chunk of data has a maximum length of 3000 tokens", "website_dataset": "Website Sync", - "website_dataset_desc": "Website sync allows you to build a Dataset directly using a web link." + "website_dataset_desc": "Website sync allows you to build a Dataset directly using a web link.", + "yuque_dataset": "Yuque Dataset", + "yuque_dataset_config": "Yuque Dataset Config", + "yuque_dataset_desc": "Can build a dataset using Yuque documents by configuring permissions, without secondary storage" } diff --git a/packages/web/i18n/en/publish.json b/packages/web/i18n/en/publish.json index 69925a1bab37..47fe293b8baf 100644 --- a/packages/web/i18n/en/publish.json +++ b/packages/web/i18n/en/publish.json @@ -35,5 +35,11 @@ "wecom.bot_desc": "Connect to WeCom Bot directly via API", "wecom.create_modal_title": "Create WeCom Bot", "wecom.edit_modal_title": "Edit WeCom Bot", - "wecom.title": "Publish to WeCom Bot" -} \ No newline at end of file + "wecom.title": "Publish to WeCom Bot", + "dingtalk.bot": "DingTalk Bot", + "dingtalk.bot_desc": "Connect to DingTalk Bot directly via API", + "dingtalk.create_modal_title": "Create DingTalk Bot", + "dingtalk.edit_modal_title": "Edit DingTalk Bot", + "dingtalk.title": "Publish to DingTalk Bot", + "dingtalk.api": "DingTalk API" +} diff --git a/packages/web/i18n/en/user.json b/packages/web/i18n/en/user.json index 354c8aa325c4..473a84cf9d64 100644 --- a/packages/web/i18n/en/user.json +++ b/packages/web/i18n/en/user.json @@ -26,6 +26,7 @@ "login.error": "Login Error", "login.password_condition": "Password can be up to 60 characters", "login.success": "Login Successful", + "login.Dingtalk": "DingTalk Login", "manage_team": "Manage team", "name": "Name", "notification.Bind Notification Pipe Hint": "Please bind a notification receiving account to ensure you receive notifications such as plan expiration reminders, ensuring your service runs smoothly.", @@ -114,6 +115,7 @@ "team.no_collaborators": "No Collaborators", "team.write_role_member": "", "usage.feishu": "Feishu", + "usage.dingtalk": "DingTalk", "usage.official_account": "Official Account", "usage.share": "Share Link", "usage.wecom": "WeCom" diff --git a/packages/web/i18n/en/workflow.json b/packages/web/i18n/en/workflow.json index b1fcb729903f..5b92f9048fab 100644 --- a/packages/web/i18n/en/workflow.json +++ b/packages/web/i18n/en/workflow.json @@ -57,6 +57,7 @@ "field_required": "Required", "field_used_as_tool_input": "Used as Tool Call Parameter", "filter_description": "Currently supports filtering by tags and creation time. Fill in the format as follows:\n{\n \"tags\": {\n \"$and\": [\"Tag 1\",\"Tag 2\"],\n \"$or\": [\"When there are $and tags, and is effective, or is not effective\"]\n },\n \"createTime\": {\n \"$gte\": \"YYYY-MM-DD HH:mm format, collection creation time greater than this time\",\n \"$lte\": \"YYYY-MM-DD HH:mm format, collection creation time less than this time, can be used with $gte\"\n }\n}", + "foldAll": "Collapse all", "form_input_result": "User complete input result", "form_input_result_tip": "an object containing the complete result", "full_field_extraction": "Full Field Extraction", @@ -179,6 +180,7 @@ "tool_params.params_name_placeholder": "name/age/sql", "tool_params.tool_params_result": "Parameter configuration results", "trigger_after_application_completion": "Will be triggered after the application is fully completed", + "unFoldAll": "Expand all", "update_link_error": "Error updating link", "update_specified_node_output_or_global_variable": "Can update the output value of a specified node or update global variables", "use_user_id": "User ID", diff --git a/packages/web/i18n/zh-CN/app.json b/packages/web/i18n/zh-CN/app.json index 857bb2d5d060..fd6561aa6838 100644 --- a/packages/web/i18n/zh-CN/app.json +++ b/packages/web/i18n/zh-CN/app.json @@ -82,6 +82,7 @@ "month.unit": "号", "move.hint": "移动后,所选应用/文件夹将继承新文件夹的权限设置,原先的权限设置失效。", "move_app": "移动应用", + "node_not_intro": "这个节点没有介绍", "not_json_file": "请选择JSON文件", "open_auto_execute": "启用自动执行", "open_vision_function_tip": "有图示开关的模型即拥有图片识别能力。若开启,模型会解析文件链接里的图片,并自动解析用户问题中的图片(用户问题≤500字时生效)。", diff --git a/packages/web/i18n/zh-CN/chat.json b/packages/web/i18n/zh-CN/chat.json index 88089c87bcd0..8564012741b6 100644 --- a/packages/web/i18n/zh-CN/chat.json +++ b/packages/web/i18n/zh-CN/chat.json @@ -33,6 +33,7 @@ "multiple_AI_conversations": "多组 AI 对话", "new_input_guide_lexicon": "新词库", "no_workflow_response": "没有运行数据", + "not_query": "缺少查询内容", "not_select_file": "未选择文件", "plugins_output": "插件输出", "question_tip": "从上到下,为各个模块的响应顺序", diff --git a/packages/web/i18n/zh-CN/common.json b/packages/web/i18n/zh-CN/common.json index 69527e0e267f..2cdbd09103e9 100644 --- a/packages/web/i18n/zh-CN/common.json +++ b/packages/web/i18n/zh-CN/common.json @@ -840,7 +840,6 @@ "dataset.Create Folder": "创建文件夹", "dataset.Create manual collection": "创建手动数据集", "dataset.Delete Dataset Error": "删除知识库异常", - "dataset.Edit API Service": "编辑 API 文件接口", "dataset.Edit Folder": "编辑文件夹", "dataset.Edit Info": "编辑信息", "dataset.Export": "导出", diff --git a/packages/web/i18n/zh-CN/dataset.json b/packages/web/i18n/zh-CN/dataset.json index e60af49596df..acd35dfa2833 100644 --- a/packages/web/i18n/zh-CN/dataset.json +++ b/packages/web/i18n/zh-CN/dataset.json @@ -25,6 +25,7 @@ "dataset.Unsupported operation": "操作不支持", "dataset.no_collections": "暂无数据集", "dataset.no_tags": "暂无标签", + "edit_dataset_config": "编辑知识库配置", "error.collectionNotFound": "集合找不到了~", "external_file": "外部文件库", "external_file_dataset_desc": "可以从外部文件库导入文件构建知识库,文件不会进行二次存储", @@ -32,6 +33,9 @@ "external_read_url": "外部预览地址", "external_read_url_tip": "可以配置你文件库的阅读地址。便于对用户进行阅读鉴权操作。目前可以使用 {{fileId}} 变量来指代外部文件 ID。", "external_url": "文件访问 URL", + "feishu_dataset": "飞书知识库", + "feishu_dataset_config": "配置飞书知识库", + "feishu_dataset_desc": "可通过配置飞书文档权限,使用飞书文档构建知识库,文档不会进行二次存储", "file_model_function_tip": "用于增强索引和 QA 生成", "filename": "文件名", "folder_dataset": "文件夹", @@ -69,5 +73,8 @@ "training_mode": "处理方式", "vector_model_max_tokens_tip": "每个分块数据,最大长度为 3000 tokens", "website_dataset": "Web 站点同步", - "website_dataset_desc": "Web 站点同步允许你直接使用一个网页链接构建知识库" + "website_dataset_desc": "Web 站点同步允许你直接使用一个网页链接构建知识库", + "yuque_dataset": "语雀知识库", + "yuque_dataset_config": "配置语雀知识库", + "yuque_dataset_desc": "可通过配置语雀文档权限,使用语雀文档构建知识库,文档不会进行二次存储" } diff --git a/packages/web/i18n/zh-CN/publish.json b/packages/web/i18n/zh-CN/publish.json index 99ef17803928..fbcbe59fdd53 100644 --- a/packages/web/i18n/zh-CN/publish.json +++ b/packages/web/i18n/zh-CN/publish.json @@ -35,5 +35,11 @@ "wecom.bot_desc": "通过 API 直接接入企业微信机器人", "wecom.create_modal_title": "创建企微机器人", "wecom.edit_modal_title": "编辑企微机器人", - "wecom.title": "发布到企业微信机器人" -} \ No newline at end of file + "wecom.title": "发布到企业微信机器人", + "dingtalk.bot": "钉钉机器人", + "dingtalk.bot_desc": "通过 API 直接接入钉钉机器人", + "dingtalk.create_modal_title": "创建钉钉机器人", + "dingtalk.edit_modal_title": "编辑钉钉机器人", + "dingtalk.title": "发布到钉钉机器人", + "dingtalk.api": "钉钉 API" +} diff --git a/packages/web/i18n/zh-CN/user.json b/packages/web/i18n/zh-CN/user.json index 2430feec5a17..253c92b9443b 100644 --- a/packages/web/i18n/zh-CN/user.json +++ b/packages/web/i18n/zh-CN/user.json @@ -26,6 +26,7 @@ "login.error": "登录异常", "login.password_condition": "密码最多 60 位", "login.success": "登录成功", + "login.Dingtalk": "钉钉登录", "manage_team": "管理团队", "name": "名称", "notification.Bind Notification Pipe Hint": "请绑定通知接收账号,以确保您能正常接收套餐过期提醒等通知,保障您的服务正常运行。", @@ -114,6 +115,7 @@ "team.no_collaborators": "暂无协作者", "team.write_role_member": "可写权限", "usage.feishu": "飞书", + "usage.dingtalk": "钉钉", "usage.official_account": "公众号", "usage.share": "分享链接", "usage.wecom": "企业微信" diff --git a/packages/web/i18n/zh-CN/workflow.json b/packages/web/i18n/zh-CN/workflow.json index c23fe23b84c5..4b7b14ac405a 100644 --- a/packages/web/i18n/zh-CN/workflow.json +++ b/packages/web/i18n/zh-CN/workflow.json @@ -10,7 +10,6 @@ "Variable_name": "变量名", "add_new_input": "新增输入", "add_new_output": "新增输出", - "http_body_placeholder": "与 APIFox 类似的语法,可通过 / 来激活变量选择。字符串变量均需加双引号,其他类型变量无需加双引号。请严格检查是否符合 JSON 格式。", "append_application_reply_to_history_as_new_context": "将该应用回复内容拼接到历史记录中,作为新的上下文返回", "application_call": "应用调用", "assigned_reply": "指定回复", @@ -58,6 +57,7 @@ "field_required": "必填", "field_used_as_tool_input": "作为工具调用参数", "filter_description": "目前支持标签和创建时间过滤,需按照以下格式填写:\n{\n \"tags\": {\n \"$and\": [\"标签 1\",\"标签 2\"],\n \"$or\": [\"有 $and 标签时,and 生效,or 不生效\"]\n },\n \"createTime\": {\n \"$gte\": \"YYYY-MM-DD HH:mm 格式即可,集合的创建时间大于该时间\",\n \"$lte\": \"YYYY-MM-DD HH:mm 格式即可,集合的创建时间小于该时间,可和 $gte 共同使用\"\n }\n}", + "foldAll": "全部折叠", "form_input_result": "用户完整输入结果", "form_input_result_tip": "一个包含完整结果的对象", "full_field_extraction": "字段完全提取", @@ -65,6 +65,7 @@ "full_response_data": "完整响应数据", "greater_than": "大于", "greater_than_or_equal_to": "大于等于", + "http_body_placeholder": "与 APIFox 类似的语法,可通过 / 来激活变量选择。字符串变量均需加双引号,其他类型变量无需加双引号。请严格检查是否符合 JSON 格式。", "http_extract_output": "输出字段提取", "http_extract_output_description": "可以通过 JSONPath 语法来提取响应值中的指定字段", "http_raw_response_description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。", @@ -179,6 +180,7 @@ "tool_params.params_name_placeholder": "name/age/sql", "tool_params.tool_params_result": "参数配置结果", "trigger_after_application_completion": "将在应用完全结束后触发", + "unFoldAll": "全部展开", "update_link_error": "更新链接异常", "update_specified_node_output_or_global_variable": "可以更新指定节点的输出值或更新全局变量", "use_user_id": "使用者 ID", diff --git a/packages/web/i18n/zh-Hant/app.json b/packages/web/i18n/zh-Hant/app.json index 29a8bf152471..0f09f18ef9dc 100644 --- a/packages/web/i18n/zh-Hant/app.json +++ b/packages/web/i18n/zh-Hant/app.json @@ -82,6 +82,7 @@ "month.unit": "號", "move.hint": "移動後,所選應用程式/資料夾將會繼承新資料夾的權限設定,原先的權限設定將會失效。", "move_app": "移動應用程式", + "node_not_intro": "這個節點沒有介紹", "not_json_file": "請選擇 JSON 檔案", "open_auto_execute": "啟用自動執行", "open_vision_function_tip": "有圖示開關的模型即擁有圖片辨識功能。若開啟,模型會解析檔案連結中的圖片,並自動解析使用者問題中的圖片(使用者問題 ≤ 500 字時生效)。", diff --git a/packages/web/i18n/zh-Hant/chat.json b/packages/web/i18n/zh-Hant/chat.json index 87457041c812..cb44628d1250 100644 --- a/packages/web/i18n/zh-Hant/chat.json +++ b/packages/web/i18n/zh-Hant/chat.json @@ -33,6 +33,7 @@ "multiple_AI_conversations": "多組 AI 對話", "new_input_guide_lexicon": "新增詞彙庫", "no_workflow_response": "無工作流程資料", + "not_query": "缺少查詢內容", "not_select_file": "尚未選取檔案", "plugins_output": "外掛程式輸出", "question_tip": "由上至下,各個模組的回應順序", diff --git a/packages/web/i18n/zh-Hant/dataset.json b/packages/web/i18n/zh-Hant/dataset.json index 8c55de695fb1..f87fdea9aefd 100644 --- a/packages/web/i18n/zh-Hant/dataset.json +++ b/packages/web/i18n/zh-Hant/dataset.json @@ -25,6 +25,7 @@ "dataset.Unsupported operation": "操作不支持", "dataset.no_collections": "尚無資料集", "dataset.no_tags": "尚無標籤", + "edit_dataset_config": "編輯知識庫配置", "error.collectionNotFound": "找不到集合", "external_file": "外部檔案庫", "external_file_dataset_desc": "可以從外部檔案庫匯入檔案建立資料集,檔案不會進行二次儲存", @@ -32,6 +33,9 @@ "external_read_url": "外部預覽網址", "external_read_url_tip": "可以設定您檔案庫的讀取網址,方便對使用者進行讀取權限驗證。目前可使用 {{fileId}} 變數來代表外部檔案識別碼。", "external_url": "檔案存取網址", + "feishu_dataset": "飛書知識庫", + "feishu_dataset_config": "配置飛書知識庫", + "feishu_dataset_desc": "可通過配置飛書文檔權限,使用飛書文檔構建知識庫,文檔不會進行二次存儲", "file_model_function_tip": "用於增強索引和問答生成", "filename": "檔案名稱", "folder_dataset": "資料夾", @@ -69,5 +73,8 @@ "training_mode": "分段模式", "vector_model_max_tokens_tip": "每個分塊數據,最大長度為 3000 tokens", "website_dataset": "網站同步", - "website_dataset_desc": "網站同步功能讓您可以直接使用網頁連結建立資料集" + "website_dataset_desc": "網站同步功能讓您可以直接使用網頁連結建立資料集", + "yuque_dataset": "語雀知識庫", + "yuque_dataset_config": "配置語雀知識庫", + "yuque_dataset_desc": "可通過配置語雀文檔權限,使用語雀文檔構建知識庫,文檔不會進行二次存儲" } diff --git a/packages/web/i18n/zh-Hant/publish.json b/packages/web/i18n/zh-Hant/publish.json index 0a702abad4d0..e19c34c9fa6b 100644 --- a/packages/web/i18n/zh-Hant/publish.json +++ b/packages/web/i18n/zh-Hant/publish.json @@ -35,5 +35,11 @@ "wecom.bot_desc": "透過 API 直接連結企業微信聊天機器人", "wecom.create_modal_title": "建立企業微信聊天機器人", "wecom.edit_modal_title": "編輯企業微信聊天機器人", - "wecom.title": "發布至企業微信聊天機器人" -} \ No newline at end of file + "wecom.title": "發布至企業微信聊天機器人", + "dingtalk.bot": "釘釘聊天機器人", + "dingtalk.bot_desc": "透過 API 直接連結釘釘聊天機器人", + "dingtalk.create_modal_title": "建立釘釘聊天機器人", + "dingtalk.edit_modal_title": "編輯釘釘聊天機器人", + "dingtalk.title": "發布至釘釘聊天機器人", + "dingtalk.api": "釘釘 API" +} diff --git a/packages/web/i18n/zh-Hant/user.json b/packages/web/i18n/zh-Hant/user.json index 8b9d4418108d..ecc27df0f147 100644 --- a/packages/web/i18n/zh-Hant/user.json +++ b/packages/web/i18n/zh-Hant/user.json @@ -26,6 +26,7 @@ "login.error": "登入失敗", "login.password_condition": "密碼最多可輸入 60 個字元", "login.success": "登入成功", + "login.Dingtalk": "釘釘登入", "manage_team": "管理團隊", "name": "名稱", "notification.Bind Notification Pipe Hint": "請綁定通知接收帳號,以確保您能正常接收方案到期提醒等通知,保障您的服務正常運作。", @@ -114,6 +115,7 @@ "team.no_collaborators": "目前沒有協作者", "team.write_role_member": "可寫入權限", "usage.feishu": "飛書", + "usage.dingtalk": "釘釘", "usage.official_account": "公眾號", "usage.share": "分享連結", "usage.wecom": "企業微信" diff --git a/packages/web/i18n/zh-Hant/workflow.json b/packages/web/i18n/zh-Hant/workflow.json index 31dbb1837142..d8ec45aedc96 100644 --- a/packages/web/i18n/zh-Hant/workflow.json +++ b/packages/web/i18n/zh-Hant/workflow.json @@ -57,6 +57,7 @@ "field_required": "必填", "field_used_as_tool_input": "作為工具呼叫參數", "filter_description": "目前支援標籤和建立時間篩選,需按照以下格式填寫:\n{\n \"tags\": {\n \"$and\": [\"標籤 1\",\"標籤 2\"],\n \"$or\": [\"當有 $and 標籤時,$and 才會生效,$or 不會生效\"]\n },\n \"createTime\": {\n \"$gte\": \"YYYY-MM-DD HH:mm 格式,資料集的建立時間大於這個時間\",\n \"$lte\": \"YYYY-MM-DD HH:mm 格式,資料集的建立時間小於這個時間,可以和 $gte 一起使用\"\n }\n}", + "foldAll": "全部折疊", "form_input_result": "使用者完整輸入結果", "form_input_result_tip": "一個包含完整結果的物件", "full_field_extraction": "欄位完整擷取", @@ -179,6 +180,7 @@ "tool_params.params_name_placeholder": "name/age/sql", "tool_params.tool_params_result": "參數設定結果", "trigger_after_application_completion": "將會在應用程式完全結束後觸發", + "unFoldAll": "全部展開", "update_link_error": "更新連結發生錯誤", "update_specified_node_output_or_global_variable": "可以更新指定節點的輸出值或更新全域變數", "use_user_id": "使用者 ID", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aae58ee05a8d..85183f18c46b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -580,9 +580,6 @@ importers: '@svgr/webpack': specifier: ^6.5.1 version: 6.5.1 - '@types/faker': - specifier: ^6.6.9 - version: 6.6.9 '@types/formidable': specifier: ^2.0.5 version: 2.0.6 @@ -3358,10 +3355,6 @@ packages: '@types/express@5.0.0': resolution: {integrity: sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==} - '@types/faker@6.6.9': - resolution: {integrity: sha512-Y9YYm5L//8ooiiknO++4Gr539zzdI0j3aXnOBjo1Vk+kTvffY10GuE2wn78AFPECwZ5MYGTjiDVw1naLLdDimw==} - deprecated: This is a stub types definition. faker provides its own type definitions, so you do not need this installed. - '@types/formidable@2.0.6': resolution: {integrity: sha512-L4HcrA05IgQyNYJj6kItuIkXrInJvsXTPC5B1i64FggWKKqSL+4hgt7asiSNva75AoLQjq29oPxFfU4GAQ6Z2w==} @@ -5252,9 +5245,6 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} - faker@6.6.6: - resolution: {integrity: sha512-9tCqYEDHI5RYFQigXFwF1hnCwcWCOJl/hmll0lr5D2Ljjb0o4wphb69wikeJDz5qCEzXCoPvG6ss5SDP6IfOdg==} - fast-content-type-parse@1.1.0: resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==} @@ -12447,10 +12437,6 @@ snapshots: '@types/qs': 6.9.16 '@types/serve-static': 1.15.7 - '@types/faker@6.6.9': - dependencies: - faker: 6.6.6 - '@types/formidable@2.0.6': dependencies: '@types/node': 20.14.11 @@ -14827,8 +14813,6 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 - faker@6.6.6: {} - fast-content-type-parse@1.1.0: {} fast-decode-uri-component@1.0.1: {} diff --git a/projects/app/package.json b/projects/app/package.json index bc6ba05be08a..f9a8e6f3bf36 100644 --- a/projects/app/package.json +++ b/projects/app/package.json @@ -75,7 +75,6 @@ "@faker-js/faker": "^9.0.3", "@shelf/jest-mongodb": "^4.3.2", "@svgr/webpack": "^6.5.1", - "@types/faker": "^6.6.9", "@types/formidable": "^2.0.5", "@types/js-yaml": "^4.0.9", "@types/jsonwebtoken": "^9.0.3", diff --git a/projects/app/public/imgs/outlink/dingtalk-copylink-instruction.png b/projects/app/public/imgs/outlink/dingtalk-copylink-instruction.png new file mode 100644 index 000000000000..4f73d14d0be8 Binary files /dev/null and b/projects/app/public/imgs/outlink/dingtalk-copylink-instruction.png differ diff --git a/projects/app/src/global/core/dataset/api.d.ts b/projects/app/src/global/core/dataset/api.d.ts index 70a55425108c..b0feccd520c6 100644 --- a/projects/app/src/global/core/dataset/api.d.ts +++ b/projects/app/src/global/core/dataset/api.d.ts @@ -2,7 +2,7 @@ import { PushDatasetDataChunkProps, PushDatasetDataResponse } from '@fastgpt/global/core/dataset/api'; -import { APIFileServer } from '@fastgpt/global/core/dataset/apiDataset'; +import { APIFileServer, FeishuServer, YuqueServer } from '@fastgpt/global/core/dataset/apiDataset'; import { DatasetSearchModeEnum, DatasetSourceReadTypeEnum, @@ -27,6 +27,8 @@ export type CreateDatasetParams = { vectorModel?: string; agentModel?: string; apiServer?: APIFileServer; + feishuServer?: FeishuServer; + yuqueServer?: YuqueServer; }; export type RebuildEmbeddingProps = { diff --git a/projects/app/src/pages/api/common/file/previewContent.ts b/projects/app/src/pages/api/common/file/previewContent.ts index f9d06349b708..cfe4d16814dc 100644 --- a/projects/app/src/pages/api/common/file/previewContent.ts +++ b/projects/app/src/pages/api/common/file/previewContent.ts @@ -29,7 +29,7 @@ async function handler(req: ApiRequestProps, res: NextApiRe throw new Error('fileId is empty'); } - const { teamId, apiServer } = await (async () => { + const { teamId, apiServer, feishuServer, yuqueServer } = await (async () => { if (type === DatasetSourceReadTypeEnum.fileLocal) { const res = await authCollectionFile({ req, @@ -51,7 +51,9 @@ async function handler(req: ApiRequestProps, res: NextApiRe }); return { teamId: dataset.teamId, - apiServer: dataset.apiServer + apiServer: dataset.apiServer, + feishuServer: dataset.feishuServer, + yuqueServer: dataset.yuqueServer }; })(); @@ -62,6 +64,8 @@ async function handler(req: ApiRequestProps, res: NextApiRe isQAImport, selector, apiServer, + feishuServer, + yuqueServer, externalFileId }); diff --git a/projects/app/src/pages/api/common/tracks/push.ts b/projects/app/src/pages/api/common/tracks/push.ts new file mode 100644 index 000000000000..8298c2f1fdb1 --- /dev/null +++ b/projects/app/src/pages/api/common/tracks/push.ts @@ -0,0 +1,49 @@ +import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; +import { NextAPI } from '@/service/middleware/entry'; +import { addLog } from '@fastgpt/service/common/system/log'; +import { TrackEnum } from '@fastgpt/global/common/middle/tracks/constants'; +import { TrackModel } from '@fastgpt/service/common/middle/tracks/schema'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; +import { useReqFrequencyLimit } from '@fastgpt/service/common/middle/reqFrequencyLimit'; + +export type pushQuery = {}; + +export type pushBody = { + event: TrackEnum; + data: any; +}; + +export type pushResponse = {}; + +async function handler( + req: ApiRequestProps, + res: ApiResponseType +): Promise { + if (!global.feConfigs?.isPlus) return {}; + + const { teamId, tmbId, userId } = await authCert({ + req, + authToken: true + }); + + const data = { + teamId, + tmbId, + uid: userId, + event: req.body.event, + data: req.body.data + }; + + addLog.info('Push tracks', data); + return TrackModel.create(data); +} + +export default NextAPI(useReqFrequencyLimit(1, 5), handler); + +export const config = { + api: { + bodyParser: { + sizeLimit: '5kb' + } + } +}; diff --git a/projects/app/src/pages/api/core/app/create.ts b/projects/app/src/pages/api/core/app/create.ts index 6122b58c9292..bacc1d7b8197 100644 --- a/projects/app/src/pages/api/core/app/create.ts +++ b/projects/app/src/pages/api/core/app/create.ts @@ -16,6 +16,7 @@ import { authApp } from '@fastgpt/service/support/permission/app/auth'; import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema'; import { MongoUser } from '@fastgpt/service/support/user/schema'; +import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils'; export type CreateAppBody = { parentId?: ParentIdType; @@ -35,7 +36,7 @@ async function handler(req: ApiRequestProps) { } // 凭证校验 - const [{ teamId, tmbId }] = await Promise.all([ + const [{ teamId, tmbId, userId }] = await Promise.all([ authUserPer({ req, authToken: true, per: WritePermissionVal }), ...(parentId ? [authApp({ req, appId: parentId, per: WritePermissionVal, authToken: true })] @@ -62,6 +63,13 @@ async function handler(req: ApiRequestProps) { username: user?.username }); + pushTrack.createApp({ + type, + uid: userId, + teamId, + tmbId + }); + return appId; } diff --git a/projects/app/src/pages/api/core/app/del.ts b/projects/app/src/pages/api/core/app/del.ts index d4386da1bb18..1b3bac50b80e 100644 --- a/projects/app/src/pages/api/core/app/del.ts +++ b/projects/app/src/pages/api/core/app/del.ts @@ -16,6 +16,8 @@ import { findAppAndAllChildren } from '@fastgpt/service/core/app/controller'; import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema'; import { ClientSession } from '@fastgpt/service/common/mongo'; import { deleteChatFiles } from '@fastgpt/service/core/chat/controller'; +import { getAppLatestVersion } from '@fastgpt/service/core/app/version/controller'; +import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils'; async function handler(req: NextApiRequest, res: NextApiResponse) { const { appId } = req.query as { appId: string }; @@ -25,12 +27,20 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } // Auth owner (folder owner, can delete all apps in the folder) - const { teamId } = await authApp({ req, authToken: true, appId, per: OwnerPermissionVal }); + const { teamId, tmbId, userId, app } = await authApp({ + req, + authToken: true, + appId, + per: OwnerPermissionVal + }); await onDelOneApp({ teamId, appId }); + + // Tracks + pushTrack.countAppNodes({ teamId, tmbId, uid: userId, appId }); } export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/app/httpPlugin/create.ts b/projects/app/src/pages/api/core/app/httpPlugin/create.ts index 471bb09983a3..8e2346c693e1 100644 --- a/projects/app/src/pages/api/core/app/httpPlugin/create.ts +++ b/projects/app/src/pages/api/core/app/httpPlugin/create.ts @@ -8,6 +8,7 @@ import { NextAPI } from '@/service/middleware/entry'; import { onCreateApp, type CreateAppBody } from '../create'; import { AppSchema } from '@fastgpt/global/core/app/type'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; +import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils'; export type createHttpPluginQuery = {}; @@ -28,7 +29,11 @@ async function handler( return Promise.reject('缺少参数'); } - const { teamId, tmbId } = await authUserPer({ req, authToken: true, per: WritePermissionVal }); + const { teamId, tmbId, userId } = await authUserPer({ + req, + authToken: true, + per: WritePermissionVal + }); await mongoSessionRun(async (session) => { // create http plugin folder @@ -62,6 +67,13 @@ async function handler( } }); + pushTrack.createApp({ + type: AppTypeEnum.httpPlugin, + uid: userId, + teamId, + tmbId + }); + return {}; } diff --git a/projects/app/src/pages/api/core/chat/init.ts b/projects/app/src/pages/api/core/chat/init.ts index b2a9c25ec87a..e4c73569bd35 100644 --- a/projects/app/src/pages/api/core/chat/init.ts +++ b/projects/app/src/pages/api/core/chat/init.ts @@ -45,7 +45,9 @@ async function handler( const { nodes, chatConfig } = await getAppLatestVersion(app._id, app); const pluginInputs = - app?.modules?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)?.inputs ?? []; + chat?.pluginInputs ?? + nodes?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)?.inputs ?? + []; return { chatId, diff --git a/projects/app/src/pages/api/core/chat/outLink/init.ts b/projects/app/src/pages/api/core/chat/outLink/init.ts index ade17f1dc2e5..c1383f7553be 100644 --- a/projects/app/src/pages/api/core/chat/outLink/init.ts +++ b/projects/app/src/pages/api/core/chat/outLink/init.ts @@ -35,6 +35,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { } const { nodes, chatConfig } = await getAppLatestVersion(app._id, app); + const pluginInputs = + chat?.pluginInputs ?? + nodes?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)?.inputs ?? + []; jsonRes(res, { data: { @@ -56,9 +60,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { avatar: app.avatar, intro: app.intro, type: app.type, - pluginInputs: - app?.modules?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput) - ?.inputs ?? [] + pluginInputs } } }); diff --git a/projects/app/src/pages/api/core/chat/team/init.ts b/projects/app/src/pages/api/core/chat/team/init.ts index 3783a7eef245..e0517fd16399 100644 --- a/projects/app/src/pages/api/core/chat/team/init.ts +++ b/projects/app/src/pages/api/core/chat/team/init.ts @@ -43,6 +43,10 @@ async function handler(req: ApiRequestProps, res: NextApiResp // get app and history const { nodes, chatConfig } = await getAppLatestVersion(app._id, app); + const pluginInputs = + chat?.pluginInputs ?? + nodes?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput)?.inputs ?? + []; jsonRes(res, { data: { @@ -64,9 +68,7 @@ async function handler(req: ApiRequestProps, res: NextApiResp avatar: app.avatar, intro: app.intro, type: app.type, - pluginInputs: - app?.modules?.find((node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput) - ?.inputs ?? [] + pluginInputs } } }); diff --git a/projects/app/src/pages/api/core/dataset/apiDataset/list.ts b/projects/app/src/pages/api/core/dataset/apiDataset/list.ts index 658dac446b71..28d1c63821bd 100644 --- a/projects/app/src/pages/api/core/dataset/apiDataset/list.ts +++ b/projects/app/src/pages/api/core/dataset/apiDataset/list.ts @@ -1,4 +1,6 @@ +import { getFeishuAndYuqueDatasetFileList } from '@/service/core/dataset/apiDataset/controller'; import { NextAPI } from '@/service/middleware/entry'; +import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset'; import { ParentIdType } from '@fastgpt/global/common/parentFolder/type'; import { APIFileItem } from '@fastgpt/global/core/dataset/apiDataset'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; @@ -26,11 +28,21 @@ async function handler(req: NextApiRequest) { }); const apiServer = dataset.apiServer; - if (!apiServer) { - return Promise.reject('apiServer is required'); + const feishuServer = dataset.feishuServer; + const yuqueServer = dataset.yuqueServer; + + if (apiServer) { + return useApiDatasetRequest({ apiServer }).listFiles({ searchKey, parentId }); + } + if (feishuServer || yuqueServer) { + return getFeishuAndYuqueDatasetFileList({ + feishuServer, + yuqueServer, + parentId + }); } - return useApiDatasetRequest({ apiServer }).listFiles({ searchKey, parentId }); + return Promise.reject(DatasetErrEnum.noApiServer); } export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/dataset/collection/create/apiCollection.ts b/projects/app/src/pages/api/core/dataset/collection/create/apiCollection.ts index 18e7ef774689..1950e42f44ea 100644 --- a/projects/app/src/pages/api/core/dataset/collection/create/apiCollection.ts +++ b/projects/app/src/pages/api/core/dataset/collection/create/apiCollection.ts @@ -34,12 +34,8 @@ async function handler(req: NextApiRequest): CreateCollectionResponse { }); const apiServer = dataset.apiServer; - if (!apiServer) { - return Promise.reject('Api server not found'); - } - if (!apiFileId) { - return Promise.reject('ApiFileId not found'); - } + const feishuServer = dataset.feishuServer; + const yuqueServer = dataset.yuqueServer; // Auth same apiFileId const storeCol = await MongoDatasetCollection.findOne( @@ -57,6 +53,8 @@ async function handler(req: NextApiRequest): CreateCollectionResponse { const content = await readApiServerFileContent({ apiServer, + feishuServer, + yuqueServer, apiFileId, teamId }); diff --git a/projects/app/src/pages/api/core/dataset/collection/create/reTrainingCollection.ts b/projects/app/src/pages/api/core/dataset/collection/create/reTrainingCollection.ts index 09ab69942ea4..3200ddfb70e5 100644 --- a/projects/app/src/pages/api/core/dataset/collection/create/reTrainingCollection.ts +++ b/projects/app/src/pages/api/core/dataset/collection/create/reTrainingCollection.ts @@ -66,7 +66,9 @@ async function handler( return { type: DatasetSourceReadTypeEnum.apiFile, sourceId: collection.apiFileId, - apiServer: collection.datasetId.apiServer + apiServer: collection.datasetId.apiServer, + feishuServer: collection.datasetId.feishuServer, + yuqueServer: collection.datasetId.yuqueServer }; } if (collection.type === DatasetCollectionTypeEnum.externalFile) { diff --git a/projects/app/src/pages/api/core/dataset/collection/read.ts b/projects/app/src/pages/api/core/dataset/collection/read.ts index 20b80ace0981..54ba19a77688 100644 --- a/projects/app/src/pages/api/core/dataset/collection/read.ts +++ b/projects/app/src/pages/api/core/dataset/collection/read.ts @@ -12,6 +12,7 @@ import { AIChatItemType, ChatHistoryItemResType } from '@fastgpt/global/core/cha import { authChatCrud } from '@/service/support/permission/auth/chat'; import { getCollectionWithDataset } from '@fastgpt/service/core/dataset/controller'; import { useApiDatasetRequest } from '@fastgpt/service/core/dataset/apiDataset/api'; +import { POST } from '@fastgpt/service/common/api/plusRequest'; export type readCollectionSourceQuery = {}; @@ -148,11 +149,25 @@ async function handler( } if (collection.type === DatasetCollectionTypeEnum.apiFile && collection.apiFileId) { const apiServer = collection.datasetId.apiServer; - if (!apiServer) return Promise.reject('apiServer not found'); + const feishuServer = collection.datasetId.feishuServer; + const yuqueServer = collection.datasetId.yuqueServer; - return useApiDatasetRequest({ apiServer }).getFilePreviewUrl({ - apiFileId: collection.apiFileId - }); + if (apiServer) { + return useApiDatasetRequest({ apiServer }).getFilePreviewUrl({ + apiFileId: collection.apiFileId + }); + } + + if (feishuServer || yuqueServer) { + return POST(`/core/dataset/systemApiDataset`, { + type: 'read', + apiFileId: collection.apiFileId, + feishuServer, + yuqueServer + }); + } + + return ''; } if (collection.type === DatasetCollectionTypeEnum.externalFile) { if (collection.externalFileId && collection.datasetId.externalReadUrl) { diff --git a/projects/app/src/pages/api/core/dataset/create.ts b/projects/app/src/pages/api/core/dataset/create.ts index ca5d7ae0a8af..373cf4fe5e3e 100644 --- a/projects/app/src/pages/api/core/dataset/create.ts +++ b/projects/app/src/pages/api/core/dataset/create.ts @@ -10,6 +10,7 @@ import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset'; import type { ApiRequestProps } from '@fastgpt/service/type/next'; import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils'; import { authDataset } from '@fastgpt/service/support/permission/dataset/auth'; +import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils'; export type DatasetCreateQuery = {}; export type DatasetCreateBody = CreateDatasetParams; @@ -26,11 +27,13 @@ async function handler( avatar, vectorModel = global.vectorModels[0].model, agentModel = getDatasetModel().model, - apiServer + apiServer, + feishuServer, + yuqueServer } = req.body; // auth - const [{ teamId, tmbId }] = await Promise.all([ + const [{ teamId, tmbId, userId }] = await Promise.all([ authUserPer({ req, authToken: true, @@ -70,7 +73,16 @@ async function handler( agentModel, avatar, type, - apiServer + apiServer, + feishuServer, + yuqueServer + }); + + pushTrack.createDataset({ + type, + teamId, + tmbId, + uid: userId }); return _id; diff --git a/projects/app/src/pages/api/core/dataset/detail.ts b/projects/app/src/pages/api/core/dataset/detail.ts index 21680c7ffa4c..6f4957b69bf4 100644 --- a/projects/app/src/pages/api/core/dataset/detail.ts +++ b/projects/app/src/pages/api/core/dataset/detail.ts @@ -36,6 +36,19 @@ async function handler(req: ApiRequestProps): Promise { authorization: '' } : undefined, + yuqueServer: dataset.yuqueServer + ? { + userId: dataset.yuqueServer.userId, + token: '' + } + : undefined, + feishuServer: dataset.feishuServer + ? { + appId: dataset.feishuServer.appId, + appSecret: '', + folderToken: dataset.feishuServer.folderToken + } + : undefined, permission, vectorModel: getVectorModel(dataset.vectorModel), agentModel: getLLMModel(dataset.agentModel) diff --git a/projects/app/src/pages/api/core/dataset/file/getPreviewChunks.ts b/projects/app/src/pages/api/core/dataset/file/getPreviewChunks.ts index 89236a2c03c4..d1d22b3fac4e 100644 --- a/projects/app/src/pages/api/core/dataset/file/getPreviewChunks.ts +++ b/projects/app/src/pages/api/core/dataset/file/getPreviewChunks.ts @@ -50,7 +50,7 @@ async function handler( throw new Error('chunkSize is too large, should be less than 30000'); } - const { teamId, apiServer } = await (async () => { + const { teamId, apiServer, feishuServer, yuqueServer } = await (async () => { if (type === DatasetSourceReadTypeEnum.fileLocal) { const res = await authCollectionFile({ req, @@ -72,7 +72,9 @@ async function handler( }); return { teamId: dataset.teamId, - apiServer: dataset.apiServer + apiServer: dataset.apiServer, + feishuServer: dataset.feishuServer, + yuqueServer: dataset.yuqueServer }; })(); @@ -83,6 +85,8 @@ async function handler( selector, isQAImport, apiServer, + feishuServer, + yuqueServer, externalFileId }); diff --git a/projects/app/src/pages/api/core/dataset/update.ts b/projects/app/src/pages/api/core/dataset/update.ts index 7ec361ecbef8..d57ab7f4efc2 100644 --- a/projects/app/src/pages/api/core/dataset/update.ts +++ b/projects/app/src/pages/api/core/dataset/update.ts @@ -57,6 +57,8 @@ async function handler( websiteConfig, externalReadUrl, apiServer, + yuqueServer, + feishuServer, status, autoSync } = req.body; @@ -124,6 +126,13 @@ async function handler( ...(!!apiServer?.authorization && { 'apiServer.authorization': apiServer.authorization }), + ...(!!yuqueServer?.userId && { 'yuqueServer.userId': yuqueServer.userId }), + ...(!!yuqueServer?.token && { 'yuqueServer.token': yuqueServer.token }), + ...(!!feishuServer?.appId && { 'feishuServer.appId': feishuServer.appId }), + ...(!!feishuServer?.appSecret && { 'feishuServer.appSecret': feishuServer.appSecret }), + ...(!!feishuServer?.folderToken && { + 'feishuServer.folderToken': feishuServer.folderToken + }), ...(isMove && { inheritPermission: true }), ...(typeof autoSync === 'boolean' && { autoSync }) }, diff --git a/projects/app/src/pages/api/support/outLink/dingtalk/[token].ts b/projects/app/src/pages/api/support/outLink/dingtalk/[token].ts new file mode 100644 index 000000000000..49732f5b5fb4 --- /dev/null +++ b/projects/app/src/pages/api/support/outLink/dingtalk/[token].ts @@ -0,0 +1,27 @@ +import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; +import { POST } from '@fastgpt/service/common/api/plusRequest'; +import { NextAPI } from '@/service/middleware/entry'; + +export type OutLinkDingtalkQuery = any; +export type OutLinkDingtalkBody = any; +export type OutLinkFeishuResponse = {}; + +async function handler( + req: ApiRequestProps, + res: ApiResponseType +): Promise { + if (req.method === 'GET') { + return { + success: true + }; + } + // send to pro + const { token } = req.query; + const result = await POST(`support/outLink/dingtalk/${token}`, req.body, { + headers: req.headers as any + }); + + return result; +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/support/outLink/offiaccount/[token].ts b/projects/app/src/pages/api/support/outLink/offiaccount/[token].ts index b7470027c4d1..dd7d8a3f1b85 100644 --- a/projects/app/src/pages/api/support/outLink/offiaccount/[token].ts +++ b/projects/app/src/pages/api/support/outLink/offiaccount/[token].ts @@ -9,13 +9,11 @@ async function handler( req: ApiRequestProps, res: ApiResponseType ): Promise { - const { token, type } = req.query; + const { token } = req.query; const result = await plusRequest({ + method: req.method, url: `support/outLink/offiaccount/${token}`, - params: { - ...req.query, - type - }, + params: req.query, data: req.body }); diff --git a/projects/app/src/pages/api/support/user/account/loginByPassword.ts b/projects/app/src/pages/api/support/user/account/loginByPassword.ts index cc35b74c7885..529d608574b0 100644 --- a/projects/app/src/pages/api/support/user/account/loginByPassword.ts +++ b/projects/app/src/pages/api/support/user/account/loginByPassword.ts @@ -6,6 +6,7 @@ import type { PostLoginProps } from '@fastgpt/global/support/user/api.d'; import { UserStatusEnum } from '@fastgpt/global/support/user/constant'; import { NextAPI } from '@/service/middleware/entry'; import { useReqFrequencyLimit } from '@fastgpt/service/common/middle/reqFrequencyLimit'; +import { pushTrack } from '@fastgpt/service/common/middle/tracks/utils'; async function handler(req: NextApiRequest, res: NextApiResponse) { const { username, password } = req.body as PostLoginProps; @@ -47,6 +48,13 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { lastLoginTmbId: userDetail.team.tmbId }); + pushTrack.login({ + type: 'password', + uid: user._id, + teamId: userDetail.team.teamId, + tmbId: userDetail.team.tmbId + }); + const token = createJWT({ ...userDetail, isRoot: username === 'root' diff --git a/projects/app/src/pages/app/detail/components/Logs/DetailLogsModal.tsx b/projects/app/src/pages/app/detail/components/Logs/DetailLogsModal.tsx index fb123b297f2f..d034f21247c5 100644 --- a/projects/app/src/pages/app/detail/components/Logs/DetailLogsModal.tsx +++ b/projects/app/src/pages/app/detail/components/Logs/DetailLogsModal.tsx @@ -10,7 +10,6 @@ import dynamic from 'next/dynamic'; import LightRowTabs from '@fastgpt/web/components/common/Tabs/LightRowTabs'; import { PluginRunBoxTabEnum } from '@/components/core/chat/ChatContainer/PluginRunBox/constants'; import CloseIcon from '@fastgpt/web/components/common/Icon/close'; -import ChatBox from '@/components/core/chat/ChatContainer/ChatBox'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; import { PcHeader } from '@/pages/chat/components/ChatHeader'; import { GetChatTypeEnum } from '@/global/core/chat/constants'; @@ -22,6 +21,7 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useContextSelector } from 'use-context-selector'; const PluginRunBox = dynamic(() => import('@/components/core/chat/ChatContainer/PluginRunBox')); +const ChatBox = dynamic(() => import('@/components/core/chat/ChatContainer/ChatBox')); type Props = { appId: string; @@ -148,10 +148,12 @@ const DetailLogsModal = ({ appId, chatId, onClose }: Props) => { )} {/* Chat container */} - + {isPlugin ? ( - - + + + + ) : ( ; + onClose: () => void; + onCreate: (id: string) => void; + onEdit: () => void; + isEdit?: boolean; +}) => { + const { t } = useTranslation(); + const { + register, + setValue, + handleSubmit: submitShareChat + } = useForm({ + defaultValues: defaultData + }); + + const { runAsync: onclickCreate, loading: creating } = useRequest2( + (e: Omit, 'appId' | 'type'>) => + createShareChat({ + ...e, + appId, + type: PublishChannelEnum.dingtalk, + app: { + clientId: e?.app?.clientId?.trim(), + clientSecret: e.app?.clientSecret?.trim() + } + }), + { + errorToast: t('common:common.Create Failed'), + successToast: t('common:common.Create Success'), + onSuccess: onCreate + } + ); + + const { runAsync: onclickUpdate, loading: updating } = useRequest2( + (e) => + updateShareChat({ + ...e, + app: { + clientId: e?.app?.clientId?.trim(), + clientSecret: e.app?.clientSecret?.trim() + } + }), + { + errorToast: t('common:common.Update Failed'), + successToast: t('common:common.Update Success'), + onSuccess: onEdit + } + ); + + const { feConfigs } = useSystemStore(); + + return ( + + + + + + + + {t('publish:dingtalk.api')} + {feConfigs?.docUrl && ( + + + + {t('common:common.Read document')} + + + )} + + + + Client ID + + + + + + Client Secret + + + + + + + + + + + + + ); +}; + +export default DingTalkEditModal; diff --git a/projects/app/src/pages/app/detail/components/Publish/DingTalk/index.tsx b/projects/app/src/pages/app/detail/components/Publish/DingTalk/index.tsx new file mode 100644 index 000000000000..e6666c0e3443 --- /dev/null +++ b/projects/app/src/pages/app/detail/components/Publish/DingTalk/index.tsx @@ -0,0 +1,248 @@ +import React, { useMemo, useState } from 'react'; +import { + Flex, + Box, + Button, + TableContainer, + Table, + Thead, + Tr, + Th, + Td, + Tbody, + useDisclosure, + Link, + HStack +} from '@chakra-ui/react'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import { useLoading } from '@fastgpt/web/hooks/useLoading'; +import { getShareChatList, delShareChatById } from '@/web/support/outLink/api'; +import { formatTimeToChatTime } from '@fastgpt/global/common/string/time'; +import { defaultDingtalkOutlinkForm } from '@/web/core/app/constants'; +import type { DingtalkAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d'; +import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant'; +import { useTranslation } from 'next-i18next'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; +import dayjs from 'dayjs'; +import dynamic from 'next/dynamic'; +import MyMenu from '@fastgpt/web/components/common/MyMenu'; +import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { getDocPath } from '@/web/common/system/doc'; + +const DingTalkEditModal = dynamic(() => import('./DingTalkEditModal')); +const ShowShareLinkModal = dynamic(() => import('../components/showShareLinkModal')); + +const DingTalk = ({ appId }: { appId: string }) => { + const { t } = useTranslation(); + const { Loading, setIsLoading } = useLoading(); + const { feConfigs } = useSystemStore(); + const [editDingTalkLinkData, setEditDingTalkLinkData] = + useState>(); + const [isEdit, setIsEdit] = useState(false); + + const baseUrl = useMemo( + () => feConfigs?.customApiDomain || `${location.origin}/api`, + [feConfigs?.customApiDomain] + ); + + const { + data: shareChatList = [], + loading: isFetching, + runAsync: refetchShareChatList + } = useRequest2( + () => getShareChatList({ appId, type: PublishChannelEnum.dingtalk }), + { + manual: false + } + ); + + const { + onOpen: openShowShareLinkModal, + isOpen: showShareLinkModalOpen, + onClose: closeShowShareLinkModal + } = useDisclosure(); + + const [showShareLink, setShowShareLink] = useState(null); + + return ( + + + + + {t('publish:dingtalk.title')} + + {feConfigs?.docUrl && ( + + + + {t('common:common.Read document')} + + + )} + + + + + + + + + + {feConfigs?.isPlus && ( + <> + + + + )} + + + + + + {shareChatList.map((item) => ( + + + + {feConfigs?.isPlus && ( + <> + + + + )} + + + + ))} + +
{t('common:common.Name')}{t('common:support.outlink.Usage points')}{t('common:core.app.share.Ip limit title')}{t('common:common.Expired Time')}{t('common:common.Last use time')}
{item.name} + {Math.round(item.usagePoints)} + {feConfigs?.isPlus + ? `${ + item.limit?.maxUsagePoints && item.limit.maxUsagePoints > -1 + ? ` / ${item.limit.maxUsagePoints}` + : ` / ${t('common:common.Unlimited')}` + }` + : ''} + {item?.limit?.QPM || '-'} + {item?.limit?.expiredTime + ? dayjs(item.limit?.expiredTime).format('YYYY/MM/DD\nHH:mm') + : '-'} + + {item.lastTime + ? t(formatTimeToChatTime(item.lastTime) as any).replace('#', ':') + : t('common:common.Un used')} + + + + } + menuList={[ + { + children: [ + { + label: t('common:common.Edit'), + icon: 'edit', + onClick: () => { + setEditDingTalkLinkData({ + _id: item._id, + name: item.name, + limit: item.limit, + app: item.app, + responseDetail: item.responseDetail, + defaultResponse: item.defaultResponse, + immediateResponse: item.immediateResponse + }); + setIsEdit(true); + } + }, + { + label: t('common:common.Delete'), + icon: 'delete', + onClick: async () => { + setIsLoading(true); + try { + await delShareChatById(item._id); + refetchShareChatList(); + } catch (error) { + console.log(error); + } + setIsLoading(false); + } + } + ] + } + ]} + /> +
+
+ {editDingTalkLinkData && ( + Promise.all([refetchShareChatList(), setEditDingTalkLinkData(undefined)])} + onEdit={() => Promise.all([refetchShareChatList(), setEditDingTalkLinkData(undefined)])} + onClose={() => setEditDingTalkLinkData(undefined)} + isEdit={isEdit} + /> + )} + {shareChatList.length === 0 && !isFetching && ( + + )} + + {showShareLinkModalOpen && ( + + )} +
+ ); +}; + +export default React.memo(DingTalk); diff --git a/projects/app/src/pages/app/detail/components/Publish/index.tsx b/projects/app/src/pages/app/detail/components/Publish/index.tsx index c6f769cc574c..ae0ceeedbf21 100644 --- a/projects/app/src/pages/app/detail/components/Publish/index.tsx +++ b/projects/app/src/pages/app/detail/components/Publish/index.tsx @@ -16,6 +16,7 @@ import { useToast } from '@fastgpt/web/hooks/useToast'; const Link = dynamic(() => import('./Link')); const API = dynamic(() => import('./API')); const FeiShu = dynamic(() => import('./FeiShu')); +const DingTalk = dynamic(() => import('./DingTalk')); // const Wecom = dynamic(() => import('./Wecom')); const OffiAccount = dynamic(() => import('./OffiAccount')); @@ -48,6 +49,13 @@ const OutLink = () => { value: PublishChannelEnum.feishu, isProFn: true }, + { + icon: 'common/dingtalkFill', + title: t('publish:dingtalk.bot'), + desc: t('publish:dingtalk.bot_desc'), + value: PublishChannelEnum.dingtalk, + isProFn: true + }, // { // icon: 'core/app/publish/wecom', // title: t('publish:wecom.bot'), @@ -114,6 +122,7 @@ const OutLink = () => { )} {linkType === PublishChannelEnum.apikey && } {linkType === PublishChannelEnum.feishu && } + {linkType === PublishChannelEnum.dingtalk && } {/* {linkType === PublishChannelEnum.wecom && } */} {linkType === PublishChannelEnum.officialAccount && } diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ContextMenu.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ContextMenu.tsx index d194939159ec..d939f1c13e22 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ContextMenu.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/components/ContextMenu.tsx @@ -1,5 +1,5 @@ import { Box, Flex } from '@chakra-ui/react'; -import React from 'react'; +import React, { useMemo } from 'react'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { useTranslation } from 'next-i18next'; import { nodeTemplate2FlowNode } from '@/web/core/workflow/utils'; @@ -8,12 +8,14 @@ import { useContextSelector } from 'use-context-selector'; import { useReactFlow } from 'reactflow'; import { WorkflowNodeEdgeContext } from '../../context/workflowInitContext'; import { WorkflowEventContext } from '../../context/workflowEventContext'; +import { WorkflowContext } from '../../context'; const ContextMenu = () => { const { t } = useTranslation(); const setNodes = useContextSelector(WorkflowNodeEdgeContext, (v) => v.setNodes); const menu = useContextSelector(WorkflowEventContext, (v) => v.menu); const setMenu = useContextSelector(WorkflowEventContext, (ctx) => ctx.setMenu); + const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); const { screenToFlowPosition } = useReactFlow(); const newNode = nodeTemplate2FlowNode({ @@ -22,38 +24,44 @@ const ContextMenu = () => { t }); - return ( - !!menu && ( - - + const allUnFolded = useMemo(() => { + return !!menu ? nodeList.some((node) => node.isFolded) : false; + }, [nodeList, menu]); + + return !!menu ? ( + + + { setMenu(null); setNodes((state) => { @@ -67,16 +75,41 @@ const ContextMenu = () => { return newState; }); }} - zIndex={1} > - + {t('workflow:context_menu.add_comment')} + { + setMenu(null); + setNodes((state) => { + return state.map((node) => ({ + ...node, + data: { + ...node.data, + isFolded: !allUnFolded + } + })); + }); + }} + > + + + {allUnFolded ? t('workflow:unFoldAll') : t('workflow:foldAll')} + + - ) - ); + + ) : null; }; export default React.memo(ContextMenu); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx index 7c28d6bf083e..5f9e182c93c9 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/index.tsx @@ -126,7 +126,6 @@ const Workflow = () => { - { : {})} onNodeDragStop={onNodeDragStop} > + diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx index 4915e20b656d..c21c0fd82f69 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/NodeCard.tsx @@ -282,7 +282,7 @@ const NodeCard = (props: Props) => { )} - {intro && } + )} @@ -580,7 +580,7 @@ const NodeIntro = React.memo(function NodeIntro({ <> - {t(intro as any)} + {t(intro as any) || t('app:node_not_intro')} {NodeIsTool && ( { + webPushTrack.useAppTemplate({ + id, + name: templateDetail.name + }); + + return res; }); }, { diff --git a/projects/app/src/pages/dataset/component/ApiDatasetForm.tsx b/projects/app/src/pages/dataset/component/ApiDatasetForm.tsx new file mode 100644 index 000000000000..51c3f73be0b0 --- /dev/null +++ b/projects/app/src/pages/dataset/component/ApiDatasetForm.tsx @@ -0,0 +1,166 @@ +import React from 'react'; +import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; +import { Flex, Input } from '@chakra-ui/react'; +import { UseFormReturn } from 'react-hook-form'; +import { useTranslation } from 'next-i18next'; +import type { + APIFileServer, + FeishuServer, + YuqueServer +} from '@fastgpt/global/core/dataset/apiDataset'; + +const ApiDatasetForm = ({ + type, + form +}: { + type: `${DatasetTypeEnum}`; + form: UseFormReturn< + { + apiServer?: APIFileServer; + feishuServer?: FeishuServer; + yuqueServer?: YuqueServer; + }, + any + >; +}) => { + const { t } = useTranslation(); + const { register } = form; + + return ( + <> + {type === DatasetTypeEnum.apiDataset && ( + <> + + + {t('dataset:api_url')} + + + + + + Authorization + + + + + )} + {type === DatasetTypeEnum.feishu && ( + <> + + + App ID + + + + + + App Secret + + + + + + Folder Token + + + + + )} + {type === DatasetTypeEnum.yuque && ( + <> + + + User ID + + + + + + Token + + + + + )} + + ); +}; + +export default ApiDatasetForm; diff --git a/projects/app/src/pages/dataset/detail/components/CollectionCard/Header.tsx b/projects/app/src/pages/dataset/detail/components/CollectionCard/Header.tsx index 0bf2e8009b93..45ecf4ed6b45 100644 --- a/projects/app/src/pages/dataset/detail/components/CollectionCard/Header.tsx +++ b/projects/app/src/pages/dataset/detail/components/CollectionCard/Header.tsx @@ -386,7 +386,9 @@ const Header = ({}: {}) => { /> )} {/* apiDataset */} - {datasetDetail?.type === DatasetTypeEnum.apiDataset && ( + {(datasetDetail?.type === DatasetTypeEnum.apiDataset || + datasetDetail?.type === DatasetTypeEnum.feishu || + datasetDetail?.type === DatasetTypeEnum.yuque) && ( { router.replace({ query: { datasetId: datasetDetail._id, - currentTab: retrainNewCollectionId.current ? TabEnum.dataCard : TabEnum.collectionCard, - collectionId: retrainNewCollectionId.current + currentTab: TabEnum.collectionCard } }); }, diff --git a/projects/app/src/pages/dataset/detail/components/Import/diffSource/APIDataset.tsx b/projects/app/src/pages/dataset/detail/components/Import/diffSource/APIDataset.tsx index d1ad43043de4..e261a959a0c5 100644 --- a/projects/app/src/pages/dataset/detail/components/Import/diffSource/APIDataset.tsx +++ b/projects/app/src/pages/dataset/detail/components/Import/diffSource/APIDataset.tsx @@ -49,23 +49,26 @@ const CustomAPIFileInput = () => { parentId: '', parentName: '' }); + const [parentUuid, setParentUuid] = useState(''); + const [paths, setPaths] = useState([]); + const [searchKey, setSearchKey] = useState(''); const { data: fileList = [], loading } = useRequest2( - async () => - datasetDetail?.apiServer - ? getApiDatasetFileList({ - datasetId: datasetDetail._id, - parentId: parent?.parentId, - searchKey: searchKey - }) - : [], + async () => { + return getApiDatasetFileList({ + datasetId: datasetDetail._id, + parentId: parent?.parentId, + searchKey: searchKey + }); + }, { refreshDeps: [datasetDetail._id, datasetDetail.apiServer, parent, searchKey], throttleWait: 500, manual: false } ); + const { data: existIdList = [] } = useRequest2( () => getApiDatasetFileListExistId({ datasetId: datasetDetail._id }), { @@ -90,6 +93,7 @@ const CustomAPIFileInput = () => { datasetId: datasetDetail._id, parentId: file?.id }); + const subFiles = await getFilesRecursively(folderFiles); allFiles.push(...subFiles); } else { @@ -125,6 +129,7 @@ const CustomAPIFileInput = () => { const handleItemClick = useCallback( (item: APIFileItem) => { if (item.type === 'folder') { + setPaths((state) => [...state, { parentId: item.id, parentName: item.name }]); return setParent({ parentId: item.id, parentName: item.name @@ -138,7 +143,7 @@ const CustomAPIFileInput = () => { setSelectFiles((state) => [...state, item]); } }, - [selectFiles, setSelectFiles] + [selectFiles] ); const handleSelectAll = useCallback(() => { @@ -151,8 +156,6 @@ const CustomAPIFileInput = () => { } }, [fileList, selectFiles]); - const paths = useMemo(() => [parent || { parentId: '', parentName: '' }], [parent]); - return ( @@ -160,21 +163,21 @@ const CustomAPIFileInput = () => { { - if (parentId !== parent?.parentId) { - setParent({ - parentId, - parentName: '' - }); - } + const index = paths.findIndex((item) => item.parentId === parentId); + + setParent(paths[index]); + setPaths(paths.slice(0, index + 1)); }} /> - - setSearchKey(e.target.value)} - placeholder={t('common:core.workflow.template.Search')} - /> - + {datasetDetail.apiServer && ( + + setSearchKey(e.target.value)} + placeholder={t('common:core.workflow.template.Search')} + /> + + )} diff --git a/projects/app/src/pages/dataset/detail/components/Info/components/EditApiServiceModal.tsx b/projects/app/src/pages/dataset/detail/components/Info/components/EditApiServiceModal.tsx index 0cc919c1ce8c..d46a6ee81f72 100644 --- a/projects/app/src/pages/dataset/detail/components/Info/components/EditApiServiceModal.tsx +++ b/projects/app/src/pages/dataset/detail/components/Info/components/EditApiServiceModal.tsx @@ -1,15 +1,23 @@ import React from 'react'; -import { ModalFooter, ModalBody, Input, Button, Flex } from '@chakra-ui/react'; +import { ModalFooter, ModalBody, Button, Flex } from '@chakra-ui/react'; import MyModal from '@fastgpt/web/components/common/MyModal/index'; import { useTranslation } from 'next-i18next'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useForm } from 'react-hook-form'; import { useToast } from '@fastgpt/web/hooks/useToast'; -import { APIFileServer } from '@fastgpt/global/core/dataset/apiDataset'; +import { APIFileServer, FeishuServer, YuqueServer } from '@fastgpt/global/core/dataset/apiDataset'; +import ApiDatasetForm from '@/pages/dataset/component/ApiDatasetForm'; +import { useContextSelector } from 'use-context-selector'; +import { DatasetPageContext } from '@/web/core/dataset/context/datasetPageContext'; +import { datasetTypeCourseMap } from '@/web/core/dataset/constants'; +import { getDocPath } from '@/web/common/system/doc'; +import MyIcon from '@fastgpt/web/components/common/Icon'; export type EditAPIDatasetInfoFormType = { id: string; apiServer?: APIFileServer; + yuqueServer?: YuqueServer; + feishuServer?: FeishuServer; }; const EditAPIDatasetInfoModal = ({ @@ -24,7 +32,11 @@ const EditAPIDatasetInfoModal = ({ }) => { const { t } = useTranslation(); const { toast } = useToast(); - const { register, handleSubmit } = useForm({ + + const datasetDetail = useContextSelector(DatasetPageContext, (v) => v.datasetDetail); + const type = datasetDetail.type; + + const form = useForm({ defaultValues: defaultForm }); @@ -44,43 +56,24 @@ const EditAPIDatasetInfoModal = ({ return ( - - - {t('dataset:api_url')} - - - - + {datasetTypeCourseMap[type] && ( window.open(getDocPath(datasetTypeCourseMap[type]), '_blank')} > - Authorization + + {t('common:Instructions')} - - + )} + {/* @ts-ignore */} + - diff --git a/projects/app/src/pages/dataset/detail/components/Info/index.tsx b/projects/app/src/pages/dataset/detail/components/Info/index.tsx index 1d5dcd181b76..0533e016adea 100644 --- a/projects/app/src/pages/dataset/detail/components/Info/index.tsx +++ b/projects/app/src/pages/dataset/detail/components/Info/index.tsx @@ -29,13 +29,12 @@ import { } from '@/web/core/dataset/api/collaborator'; import DatasetTypeTag from '@/components/core/dataset/DatasetTypeTag'; import dynamic from 'next/dynamic'; -import EditAPIDatasetInfoModal, { - EditAPIDatasetInfoFormType -} from './components/EditApiServiceModal'; +import type { EditAPIDatasetInfoFormType } from './components/EditApiServiceModal'; import { EditResourceInfoFormType } from '@/components/common/Modal/EditResourceModal'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; const EditResourceModal = dynamic(() => import('@/components/common/Modal/EditResourceModal')); +const EditAPIDatasetInfoModal = dynamic(() => import('./components/EditApiServiceModal')); const Info = ({ datasetId }: { datasetId: string }) => { const { t } = useTranslation(); @@ -326,6 +325,56 @@ const Info = ({ datasetId }: { datasetId: string }) => { )} + + {datasetDetail.type === DatasetTypeEnum.yuque && ( + <> + + + + {t('dataset:yuque_dataset_config')} + + + setEditedAPIDataset({ + id: datasetDetail._id, + yuqueServer: datasetDetail.yuqueServer + }) + } + /> + + {datasetDetail.yuqueServer?.userId} + + + )} + + {datasetDetail.type === DatasetTypeEnum.feishu && ( + <> + + + + {t('dataset:feishu_dataset_config')} + + + setEditedAPIDataset({ + id: datasetDetail._id, + feishuServer: datasetDetail.feishuServer + }) + } + /> + + {datasetDetail.feishuServer?.folderToken} + + + )} {datasetDetail.permission.hasManagePer && ( @@ -384,12 +433,14 @@ const Info = ({ datasetId }: { datasetId: string }) => { {editedAPIDataset && ( setEditedAPIDataset(undefined)} onEdit={(data) => updateDataset({ id: datasetId, - apiServer: data.apiServer + apiServer: data.apiServer, + yuqueServer: data.yuqueServer, + feishuServer: data.feishuServer }) } /> diff --git a/projects/app/src/pages/dataset/list/component/CreateModal.tsx b/projects/app/src/pages/dataset/list/component/CreateModal.tsx index 11b855794c9a..ff20d9e6884c 100644 --- a/projects/app/src/pages/dataset/list/component/CreateModal.tsx +++ b/projects/app/src/pages/dataset/list/component/CreateModal.tsx @@ -22,11 +22,15 @@ import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import ComplianceTip from '@/components/common/ComplianceTip/index'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { getDocPath } from '@/web/common/system/doc'; +import { datasetTypeCourseMap } from '@/web/core/dataset/constants'; +import ApiDatasetForm from '../../component/ApiDatasetForm'; export type CreateDatasetType = | DatasetTypeEnum.dataset | DatasetTypeEnum.apiDataset - | DatasetTypeEnum.websiteDataset; + | DatasetTypeEnum.websiteDataset + | DatasetTypeEnum.feishu + | DatasetTypeEnum.yuque; const CreateModal = ({ onClose, @@ -43,35 +47,45 @@ const CreateModal = ({ const { vectorModelList, datasetModelList } = useSystemStore(); const { isPc } = useSystem(); - const databaseNameMap = useMemo(() => { + const datasetTypeMap = useMemo(() => { return { - [DatasetTypeEnum.dataset]: t('dataset:common_dataset'), - [DatasetTypeEnum.apiDataset]: t('dataset:api_file'), - [DatasetTypeEnum.websiteDataset]: t('dataset:website_dataset') + [DatasetTypeEnum.dataset]: { + name: t('dataset:common_dataset'), + icon: 'core/dataset/commonDatasetColor' + }, + [DatasetTypeEnum.apiDataset]: { + name: t('dataset:api_file'), + icon: 'core/dataset/externalDatasetColor' + }, + [DatasetTypeEnum.websiteDataset]: { + name: t('dataset:website_dataset'), + icon: 'core/dataset/websiteDatasetColor' + }, + [DatasetTypeEnum.feishu]: { + name: t('dataset:feishu_dataset'), + icon: 'core/dataset/feishuDatasetColor' + }, + [DatasetTypeEnum.yuque]: { + name: t('dataset:yuque_dataset'), + icon: 'core/dataset/yuqueDatasetColor' + } }; }, [t]); - const iconMap = useMemo(() => { - return { - [DatasetTypeEnum.dataset]: 'core/dataset/commonDatasetColor', - [DatasetTypeEnum.apiDataset]: 'core/dataset/externalDatasetColor', - [DatasetTypeEnum.websiteDataset]: 'core/dataset/websiteDatasetColor' - }; - }, []); - const filterNotHiddenVectorModelList = vectorModelList.filter((item) => !item.hidden); - const { register, setValue, handleSubmit, watch } = useForm({ + const form = useForm({ defaultValues: { parentId, type: type || DatasetTypeEnum.dataset, - avatar: iconMap[type] || 'core/dataset/commonDatasetColor', + avatar: datasetTypeMap[type].icon, name: '', intro: '', vectorModel: filterNotHiddenVectorModelList[0].model, agentModel: datasetModelList[0].model } }); + const { register, setValue, handleSubmit, watch } = form; const avatar = watch('avatar'); const vectorModel = watch('vectorModel'); const agentModel = watch('agentModel'); @@ -119,8 +133,14 @@ const CreateModal = ({ - - {t('common:core.dataset.Create dataset', { name: databaseNameMap[type] })} + + {t('common:core.dataset.Create dataset', { name: datasetTypeMap[type].name })} } isOpen @@ -134,16 +154,14 @@ const CreateModal = ({ {t('common:common.Set Name')} - {type === DatasetTypeEnum.apiDataset && ( + {datasetTypeCourseMap[type] && ( - window.open(getDocPath('/docs/guide/knowledge_base/api_dataset/'), '_blank') - } + onClick={() => window.open(getDocPath(datasetTypeCourseMap[type]), '_blank')} > {t('common:Instructions')} @@ -242,44 +260,8 @@ const CreateModal = ({ )} - {type === DatasetTypeEnum.apiDataset && ( - <> - - - {t('dataset:api_url')} - - - - - - Authorization - - - - - )} + {/* @ts-ignore */} + diff --git a/projects/app/src/pages/dataset/list/component/SideTag.tsx b/projects/app/src/pages/dataset/list/component/SideTag.tsx index 6cf30efbb787..861039a8ac36 100644 --- a/projects/app/src/pages/dataset/list/component/SideTag.tsx +++ b/projects/app/src/pages/dataset/list/component/SideTag.tsx @@ -24,6 +24,14 @@ const SideTag = ({ type, ...props }: { type: `${DatasetTypeEnum}` } & FlexProps) [DatasetTypeEnum.apiDataset]: { icon: 'core/dataset/externalDatasetOutline', label: t('dataset:api_file') + }, + [DatasetTypeEnum.feishu]: { + icon: 'core/dataset/feishuDatasetOutline', + label: t('dataset:feishu_dataset') + }, + [DatasetTypeEnum.yuque]: { + icon: 'core/dataset/yuqueDatasetOutline', + label: t('dataset:yuque_dataset') } }; }, [t]); diff --git a/projects/app/src/pages/dataset/list/index.tsx b/projects/app/src/pages/dataset/list/index.tsx index adea6dfab935..43f87d6a68a5 100644 --- a/projects/app/src/pages/dataset/list/index.tsx +++ b/projects/app/src/pages/dataset/list/index.tsx @@ -64,7 +64,10 @@ const Dataset = () => { const onSelectDatasetType = useCallback( (e: CreateDatasetType) => { - if (!feConfigs?.isPlus && e === DatasetTypeEnum.websiteDataset) { + if ( + !feConfigs?.isPlus && + [DatasetTypeEnum.websiteDataset, DatasetTypeEnum.feishu, DatasetTypeEnum.yuque].includes(e) + ) { return toast({ status: 'warning', title: t('common:common.system.Commercial version function') @@ -168,6 +171,18 @@ const Dataset = () => { label: t('dataset:website_dataset'), description: t('dataset:website_dataset_desc'), onClick: () => onSelectDatasetType(DatasetTypeEnum.websiteDataset) + }, + { + icon: 'core/dataset/feishuDatasetColor', + label: t('dataset:feishu_dataset'), + description: t('dataset:feishu_dataset_desc'), + onClick: () => onSelectDatasetType(DatasetTypeEnum.feishu) + }, + { + icon: 'core/dataset/yuqueDatasetColor', + label: t('dataset:yuque_dataset'), + description: t('dataset:yuque_dataset_desc'), + onClick: () => onSelectDatasetType(DatasetTypeEnum.yuque) } ] }, diff --git a/projects/app/src/pages/login/components/LoginForm/components/FormLayout.tsx b/projects/app/src/pages/login/components/LoginForm/components/FormLayout.tsx index 00f9d2cf164a..4532e582a704 100644 --- a/projects/app/src/pages/login/components/LoginForm/components/FormLayout.tsx +++ b/projects/app/src/pages/login/components/LoginForm/components/FormLayout.tsx @@ -6,7 +6,7 @@ import { OAuthEnum } from '@fastgpt/global/support/user/constant'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { customAlphabet } from 'nanoid'; import { useRouter } from 'next/router'; -import { Dispatch, useRef } from 'react'; +import { Dispatch, useMemo, useRef } from 'react'; import { useTranslation } from 'next-i18next'; import I18nLngSelector from '@/components/Select/I18nLngSelector'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; @@ -42,6 +42,16 @@ const FormLayout = ({ children, setPageType, pageType }: Props) => { } ] : []), + ...(feConfigs?.oauth?.dingtalk + ? [ + { + label: t('user:login.Dingtalk'), + provider: OAuthEnum.dingtalk, + icon: 'common/dingtalkFill', + redirectUrl: `https://login.dingtalk.com/oauth2/auth?client_id=${feConfigs?.oauth?.dingtalk}&redirect_uri=${redirectUri}&state=${state.current}&response_type=code&scope=openid&prompt=consent` + } + ] + : []), ...(feConfigs?.oauth?.google ? [ { @@ -85,8 +95,10 @@ const FormLayout = ({ children, setPageType, pageType }: Props) => { : []) ]; - const show_oauth = - !sessionStorage.getItem('bd_vid') && !!(feConfigs?.sso?.url || oAuthList.length > 0); + const show_oauth = useMemo( + () => !sessionStorage.getItem('bd_vid') && !!(feConfigs?.sso?.url || oAuthList.length > 0), + [feConfigs?.sso?.url, oAuthList.length] + ); return ( diff --git a/projects/app/src/pages/login/components/RegisterForm.tsx b/projects/app/src/pages/login/components/RegisterForm.tsx index 55391d5dab3f..9da7a6962e35 100644 --- a/projects/app/src/pages/login/components/RegisterForm.tsx +++ b/projects/app/src/pages/login/components/RegisterForm.tsx @@ -45,16 +45,6 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => { const { runAsync: onclickRegister, loading: requesting } = useRequest2( async ({ username, password, code }: RegisterType) => { - const fastgpt_sem = (() => { - try { - return sessionStorage.getItem('fastgpt_sem') - ? JSON.parse(sessionStorage.getItem('fastgpt_sem')!) - : undefined; - } catch { - return undefined; - } - })(); - loginSuccess( await postRegister({ username, @@ -62,7 +52,16 @@ const RegisterForm = ({ setPageType, loginSuccess }: Props) => { password, inviterId: localStorage.getItem('inviterId') || undefined, bd_vid: sessionStorage.getItem('bd_vid') || undefined, - fastgpt_sem: fastgpt_sem + fastgpt_sem: (() => { + try { + return sessionStorage.getItem('fastgpt_sem') + ? JSON.parse(sessionStorage.getItem('fastgpt_sem')!) + : undefined; + } catch { + return undefined; + } + })(), + sourceDomain: sessionStorage.getItem('sourceDomain') || undefined }) ); diff --git a/projects/app/src/pages/login/provider.tsx b/projects/app/src/pages/login/provider.tsx index d504b282236e..ce25800c6596 100644 --- a/projects/app/src/pages/login/provider.tsx +++ b/projects/app/src/pages/login/provider.tsx @@ -43,7 +43,18 @@ const provider = () => { type: loginStore?.provider as `${OAuthEnum}`, code, callbackUrl: `${location.origin}/login/provider`, - inviterId: localStorage.getItem('inviterId') || undefined + inviterId: localStorage.getItem('inviterId') || undefined, + bd_vid: sessionStorage.getItem('bd_vid') || undefined, + fastgpt_sem: (() => { + try { + return sessionStorage.getItem('fastgpt_sem') + ? JSON.parse(sessionStorage.getItem('fastgpt_sem')!) + : undefined; + } catch { + return undefined; + } + })(), + sourceDomain: sessionStorage.getItem('sourceDomain') || undefined }); if (!res) { @@ -79,12 +90,8 @@ const provider = () => { return; } - if ( - !code || - !loginStore || - (loginStore.provider !== OAuthEnum.sso && (!loginStore.state || !state)) - ) - return; + console.log('SSO', { loginStore, code, state }); + if (!code || !loginStore) return; if (isOauthLogging) return; diff --git a/projects/app/src/service/core/dataset/apiDataset/controller.ts b/projects/app/src/service/core/dataset/apiDataset/controller.ts new file mode 100644 index 000000000000..d7ed11143ea6 --- /dev/null +++ b/projects/app/src/service/core/dataset/apiDataset/controller.ts @@ -0,0 +1,15 @@ +import { GetApiDatasetFileListResponse } from '@/pages/api/core/dataset/apiDataset/list'; +import { FeishuServer, YuqueServer } from '@fastgpt/global/core/dataset/apiDataset'; +import { POST } from '@fastgpt/service/common/api/plusRequest'; + +export const getFeishuAndYuqueDatasetFileList = async (data: { + feishuServer?: FeishuServer; + yuqueServer?: YuqueServer; + parentId?: string; +}) => { + const res = await POST('/core/dataset/systemApiDataset', { + type: 'list', + ...data + }); + return res; +}; diff --git a/projects/app/src/web/common/middle/tracks/utils.ts b/projects/app/src/web/common/middle/tracks/utils.ts new file mode 100644 index 000000000000..b008b7d04154 --- /dev/null +++ b/projects/app/src/web/common/middle/tracks/utils.ts @@ -0,0 +1,21 @@ +import { POST } from '@/web/common/api/request'; +import { TrackEnum } from '@fastgpt/global/common/middle/tracks/constants'; +import { useSystemStore } from '../../system/useSystemStore'; + +const createTrack = ({ event, data }: { event: TrackEnum; data: any }) => { + if (!useSystemStore.getState()?.feConfigs?.isPlus) return; + + return POST('/common/tracks/push', { + event, + data + }); +}; + +export const webPushTrack = { + useAppTemplate: (data: { id: string; name: string }) => { + return createTrack({ + event: TrackEnum.useAppTemplate, + data + }); + } +}; diff --git a/projects/app/src/web/context/useInitApp.ts b/projects/app/src/web/context/useInitApp.ts index f175c21157f7..beb92357216a 100644 --- a/projects/app/src/web/context/useInitApp.ts +++ b/projects/app/src/web/context/useInitApp.ts @@ -8,7 +8,12 @@ import { TrackEventName } from '../common/system/constants'; export const useInitApp = () => { const router = useRouter(); - const { hiId, bd_vid, k } = router.query as { hiId?: string; bd_vid?: string; k?: string }; + const { hiId, bd_vid, k, sourceDomain } = router.query as { + hiId?: string; + bd_vid?: string; + k?: string; + sourceDomain?: string; + }; const { loadGitStar, setInitd, feConfigs } = useSystemStore(); const [scripts, setScripts] = useState([]); const [title, setTitle] = useState(process.env.SYSTEM_NAME || 'AI'); @@ -61,7 +66,16 @@ export const useInitApp = () => { hiId && localStorage.setItem('inviterId', hiId); bd_vid && sessionStorage.setItem('bd_vid', bd_vid); k && sessionStorage.setItem('fastgpt_sem', JSON.stringify({ keyword: k })); - }, [bd_vid, hiId, k]); + + const formatSourceDomain = (() => { + if (sourceDomain) return sourceDomain; + return document.referrer; + })(); + console.log(formatSourceDomain, '-=-='); + if (formatSourceDomain && !sessionStorage.getItem('sourceDomain')) { + sessionStorage.setItem('sourceDomain', formatSourceDomain); + } + }, [bd_vid, hiId, k, sourceDomain]); return { feConfigs, diff --git a/projects/app/src/web/core/app/constants.ts b/projects/app/src/web/core/app/constants.ts index b5be9147463f..0e201bb4864f 100644 --- a/projects/app/src/web/core/app/constants.ts +++ b/projects/app/src/web/core/app/constants.ts @@ -1,6 +1,10 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { AppDetailType } from '@fastgpt/global/core/app/type.d'; -import type { FeishuAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d'; +import type { + DingtalkAppType, + FeishuAppType, + OutLinkEditType +} from '@fastgpt/global/support/outLink/type.d'; import { AppPermission } from '@fastgpt/global/support/permission/app/controller'; export const defaultApp: AppDetailType = { _id: '', @@ -39,6 +43,14 @@ export const defaultFeishuOutLinkForm: OutLinkEditType = { } }; +export const defaultDingtalkOutlinkForm: OutLinkEditType = { + name: '', + limit: { + QPM: 100, + maxUsagePoints: -1 + } +}; + export enum TTSTypeEnum { none = 'none', web = 'web', diff --git a/projects/app/src/web/core/dataset/api.ts b/projects/app/src/web/core/dataset/api.ts index 60fdc3055e9c..d5cc2229130b 100644 --- a/projects/app/src/web/core/dataset/api.ts +++ b/projects/app/src/web/core/dataset/api.ts @@ -66,6 +66,7 @@ import type { listExistIdQuery, listExistIdResponse } from '@/pages/api/core/dataset/apiDataset/listExistId'; +import { FeishuServer, YuqueServer } from '@fastgpt/global/core/dataset/apiDataset'; /* ======================== dataset ======================= */ export const getDatasets = (data: GetDatasetListBody) => diff --git a/projects/app/src/web/core/dataset/constants.ts b/projects/app/src/web/core/dataset/constants.ts index 9a815d013c6a..c2c869fdb6ab 100644 --- a/projects/app/src/web/core/dataset/constants.ts +++ b/projects/app/src/web/core/dataset/constants.ts @@ -65,3 +65,13 @@ export enum ImportProcessWayEnum { auto = 'auto', custom = 'custom' } + +export const datasetTypeCourseMap: Record<`${DatasetTypeEnum}`, string> = { + [DatasetTypeEnum.folder]: '', + [DatasetTypeEnum.dataset]: '', + [DatasetTypeEnum.apiDataset]: '/docs/guide/knowledge_base/api_dataset/', + [DatasetTypeEnum.websiteDataset]: '/docs/guide/knowledge_base/websync/', + [DatasetTypeEnum.feishu]: '/docs/guide/knowledge_base/lark_dataset/', + [DatasetTypeEnum.yuque]: '/docs/guide/knowledge_base/yuque_dataset/', + [DatasetTypeEnum.externalFile]: '' +}; diff --git a/projects/app/src/web/core/dataset/context/datasetPageContext.tsx b/projects/app/src/web/core/dataset/context/datasetPageContext.tsx index 0ee5abffc4cd..46ab863617ab 100644 --- a/projects/app/src/web/core/dataset/context/datasetPageContext.tsx +++ b/projects/app/src/web/core/dataset/context/datasetPageContext.tsx @@ -121,6 +121,19 @@ export const DatasetPageContextProvider = ({ baseUrl: data.apiServer.baseUrl, authorization: '' } + : undefined, + yuqueServer: data.yuqueServer + ? { + userId: data.yuqueServer.userId, + token: '' + } + : undefined, + feishuServer: data.feishuServer + ? { + appId: data.feishuServer.appId, + appSecret: '', + folderToken: data.feishuServer.folderToken + } : undefined })); } diff --git a/projects/app/src/web/styles/reactflow.scss b/projects/app/src/web/styles/reactflow.scss index 6ff3989b54a0..2f1c29942fc1 100644 --- a/projects/app/src/web/styles/reactflow.scss +++ b/projects/app/src/web/styles/reactflow.scss @@ -1,3 +1,6 @@ +.react-flow { + overflow: visible; +} .react-flow__panel.react-flow__attribution { z-index: 0; left: 0; diff --git a/projects/app/src/web/support/user/api.ts b/projects/app/src/web/support/user/api.ts index 955e47c077c0..6c6e17022632 100644 --- a/projects/app/src/web/support/user/api.ts +++ b/projects/app/src/web/support/user/api.ts @@ -9,7 +9,10 @@ import type { OauthLoginProps, PostLoginProps } from '@fastgpt/global/support/user/api.d'; -import { GetWXLoginQRResponse } from '@fastgpt/global/support/user/login/api.d'; +import { + AccountRegisterBody, + GetWXLoginQRResponse +} from '@fastgpt/global/support/user/login/api.d'; export const sendAuthCode = (data: { username: string; @@ -33,16 +36,7 @@ export const postRegister = ({ inviterId, bd_vid, fastgpt_sem -}: { - username: string; - code: string; - password: string; - inviterId?: string; - bd_vid?: string; - fastgpt_sem?: { - keyword: string; - }; -}) => +}: AccountRegisterBody) => POST(`/proApi/support/user/account/register/emailAndPhone`, { username, code,