Commit 36d1ff36 by Archer Committed by GitHub

feat: custom domain (#6067)

* perf: faq

* index

* delete dataset

* delete dataset

* perf: delete dataset

* init

* fix: faq

* doc

* fix: share link auth (#6063)

* standard plan add custom domain config (#6061)

* standard plan add custom domain config

* bill detail modal

* perf: vector count api

* feat: custom domain & wecom bot SaaS integration (#6047)

* feat: custom Domain type define

* feat: custom domain

* feat: wecom custom domain

* chore: i18n

* chore: i18n; team auth

* feat: wecom multi-model message support

* chore: wecom edit modal

* chore(doc): custom domain && wecom bot

* fix: type

* fix: type

* fix: file detect

* feat: fe

* fix: img name

* fix: test

* compress img

* rename

* editor initial status

* fix: chat url

* perf: s3 upload by buffer

* img

* refresh

* fix: custom domain selector (#6069)

* empty tip

* perf: s3 init

* sort provider

* fix: extend

* perf: extract filename

---------

Co-authored-by: Roy <whoeverimf5@gmail.com>
Co-authored-by: heheer <heheer@sealos.io>
Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
parent d354fd4d
...@@ -28,7 +28,7 @@ description: FastGPT 分享链接身份鉴权 ...@@ -28,7 +28,7 @@ description: FastGPT 分享链接身份鉴权
`FastGPT` 将会判断`success`是否为`true`决定是允许用户继续操作。`message`与`msg`是等同的,你可以选择返回其中一个,当`success`不为`true`时,将会提示这个错误。 `FastGPT` 将会判断`success`是否为`true`决定是允许用户继续操作。`message`与`msg`是等同的,你可以选择返回其中一个,当`success`不为`true`时,将会提示这个错误。
`uid` 是用户的唯一凭证,必须返回该 ID 且 ID 的格式为不包含 "|"、"/“、"\" 字符的、长度小于等于 255 的字符串,否则会返回 `Invalid UID` 的错误。`uid` 将会用于拉取对话记录以及保存对话记录,可参考下方实践案例。 `uid` 是用户的唯一凭证,必须返回该 ID 且 ID 的格式为不包含 "|"、"/“、"\\" 字符的、小于等于 255 **字节长度**的字符串,否则会返回 `Invalid UID` 的错误。`uid` 将会用于拉取对话记录以及保存对话记录,可参考下方实践案例。
### 触发流程 ### 触发流程
......
---
title: 配置自定义域名
description: 如何在 FastGPT 中配置自定义域名
---
FastGPT 云服务版自 v4.14.4 后支持配置自定义域名。
## 如何配置自定义域名
### 1. 打开“自定义域名”页面
在侧边栏选择“账号” -> “自定义域名”,打开自定义域名配置页。
如果您的套餐等级不支持配置,请根据页面的指引升级套餐。
![打开配置页面](/imgs/guide/team_permissions/customDomain/1.png)
### 2. 添加自定义域名
1. 准备好您的域名。您的域名必须先经过备案,目前支持“阿里云”、“腾讯云”、“火山引擎”三家服务商的备案域名。
2. 点击“编辑”按钮,进入编辑状态。
3. 填入您的域名,例如 www.example.com
4. 在域名服务商的域名解析处,添加界面中提示的 DNS 纪录,注意纪录类型为 CNAME。
5. 添加解析纪录后,点击“保存”按钮。系统将自动检查 DNS 解析情况,一般情况下,在一分钟内就可以获取到解析纪录。如果长时间没有获取到纪录,可以重试一次。
6. 待状态提示显示为“已生效”后,点击“确认”按钮即可。
![配置自定义域名](/imgs/guide/team_permissions/customDomain/2.png)
现在您可以通过您自己的域名访问 fastgpt 服务、调用 fastgpt 的 API 了。
## 域名解析失效
系统会每天对 DNS 解析进行检查,如果发现 DNS 解析纪录失效,则会停用该自定义域名,可以在“自定义域名”管理界面中点击“编辑”进行重新解析。
![编辑](/imgs/guide/team_permissions/customDomain/3.png)
如果您需要修改自定义域名、或修改服务商,则需要删除自定义域名配置后进行重新配置。
## 使用案例
- [接入企业微信智能机器人](/docs/use-cases/external-integration/wecom)
{ {
"title": "团队与权限", "title": "团队与权限",
"description": "团队管理、成员组与权限设置,确保团队协作中的数据安全和权限分配合理。", "description": "团队管理、成员组与权限设置,确保团队协作中的数据安全和权限分配合理。",
"pages": ["team_roles_permissions","invitation_link"] "pages": ["team_roles_permissions","invitation_link", "customDomain"]
} }
\ No newline at end of file
...@@ -92,6 +92,7 @@ description: FastGPT 文档目录 ...@@ -92,6 +92,7 @@ description: FastGPT 文档目录
- [/docs/introduction/guide/plugins/google_search_plugin_guide](/docs/introduction/guide/plugins/google_search_plugin_guide) - [/docs/introduction/guide/plugins/google_search_plugin_guide](/docs/introduction/guide/plugins/google_search_plugin_guide)
- [/docs/introduction/guide/plugins/searxng_plugin_guide](/docs/introduction/guide/plugins/searxng_plugin_guide) - [/docs/introduction/guide/plugins/searxng_plugin_guide](/docs/introduction/guide/plugins/searxng_plugin_guide)
- [/docs/introduction/guide/plugins/upload_system_tool](/docs/introduction/guide/plugins/upload_system_tool) - [/docs/introduction/guide/plugins/upload_system_tool](/docs/introduction/guide/plugins/upload_system_tool)
- [/docs/introduction/guide/team_permissions/customDomain](/docs/introduction/guide/team_permissions/customDomain)
- [/docs/introduction/guide/team_permissions/invitation_link](/docs/introduction/guide/team_permissions/invitation_link) - [/docs/introduction/guide/team_permissions/invitation_link](/docs/introduction/guide/team_permissions/invitation_link)
- [/docs/introduction/guide/team_permissions/team_roles_permissions](/docs/introduction/guide/team_permissions/team_roles_permissions) - [/docs/introduction/guide/team_permissions/team_roles_permissions](/docs/introduction/guide/team_permissions/team_roles_permissions)
- [/docs/introduction/index](/docs/introduction/index) - [/docs/introduction/index](/docs/introduction/index)
......
...@@ -31,6 +31,7 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4144' \ ...@@ -31,6 +31,7 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4144' \
5. 新版订阅套餐逻辑。 5. 新版订阅套餐逻辑。
6. 支持配置对话文件白名单。 6. 支持配置对话文件白名单。
7. S3 支持 pathStyle 配置。 7. S3 支持 pathStyle 配置。
8. 支持通过 Sealos 来进行多租户自定义域名配置。
## ⚙️ 优化 ## ⚙️ 优化
...@@ -56,3 +57,4 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4144' \ ...@@ -56,3 +57,4 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4144' \
## 插件 ## 插件
1. 新增 GLM4.6 与 DS3.2 系列模型预设。
...@@ -3,119 +3,70 @@ title: 接入企微机器人教程 ...@@ -3,119 +3,70 @@ title: 接入企微机器人教程
description: FastGPT 接入企微机器人教程 description: FastGPT 接入企微机器人教程
--- ---
从 4.12.4 版本起,FastGPT 商业版支持直接接入企微机器人,无需额外的 API。 - 从 4.12.4 版本起,FastGPT 商业版支持直接接入企微机器人,无需额外的 API。
- 从 4.14.4 版本起,FastGPT 云服务版支持通过配置自定义域名的方式接入企微智能机器人。
## 1. 配置可信域名和可信IP ## 1. (云服务版必须)配置自定义域名
点击企微头像,打开管理企业 企微要求智能机器人消息推送地址必须使用企业主体域名,因此云服务版本用户必须先配置自定义域名才能使用企微机器人。
![图片](/imgs/wecom-bot-1.png) - [配置自定义域名](/docs/introduction/guide/team_permissions/customDomain)
在应用管理中找到"自建"-"创建应用" 若您是商业版用户,请继续使用您企业的域名。
![图片](/imgs/wecom-bot-2.png) ## 2. 创建智能机器人
创建好应用后, 下拉, 依次配置"网页授权及JS-SDK"和"企业可信IP" ### 2.1 超级管理员登录
![图片](/imgs/wecom-bot-3.png) [点击打开企业微信管理后台](https://work.weixin.qq.com/)
其中, 网页授权及JS-SDK要求按照企微指引,完成域名归属认证 ### 2.2 找到智能机器人入口
![图片](/imgs/wecom-bot-4.png) 在"安全与管理" - "管理工具"页面点击"智能机器人" ( 注意: 只有企业创建者或超级管理员才有权限看到这个入口 )
企业可信IP要求为企业服务器IP, 后续企微的回调URL将请求到此IP ![图片](/imgs/use-cases/external-integration/wecom/1.png)
![图片](/imgs/wecom-bot-5.png) ### 2.3 选择 “API模式创建” 智能机器人
## 2. 创建企业自建应用 在创建机器人页面, 下拉, 点击 "API模式创建"
前往 FastGPT ,选择想要接入的应用,在 发布渠道 页面,新建一个接入企微智能机器人的发布渠道,填写好基础信息。 ![图片](/imgs/use-cases/external-integration/wecom/2.png)
![图片](/imgs/wecom-bot-6.png) ### 2.4 获取关键密钥
现在回到企业微信平台,找到 Corp ID, Secret, Agent ID, Token, AES Key 信息并填写回 FastGPT 平台 随机生成或者手动输入 Token 和 Encoding-AESKey,并且纪录下来
![图片](/imgs/wecom-bot-7.png) ![图片](/imgs/use-cases/external-integration/wecom/3.png)
在"我的企业"里找到企业 ID, 填写到 FastGPT 的 Corp ID 中 ### 2.5 创建企微机器人发布渠道
![图片](/imgs/wecom-bot-8.png) 在 FastGPT 中,选择要使用 Agent,在发布渠道页面,选择“企业微信机器人”,点击“创建”
在应用中找到 Agent Id 和 Secret, 并填写回 FastGPT ![图片](/imgs/use-cases/external-integration/wecom/4.png)
![图片](/imgs/wecom-bot-9.png) ### 2.6 配置发布渠道信息
点击"消息接收"-"设置API接收" 配置该发布渠道的信息,需要填入 Token 和 AESKey,也就是第四步中纪录下来的 Token 和 Encoding-AESKey
![图片](/imgs/wecom-bot-10.png) ![图片](/imgs/use-cases/external-integration/wecom/5.png)
随机生成或者手动输入 Token 和 Encoding-Key, 分别填写到 FastGPT 的 Token 和 AES Key 中 ### 2.7 复制回调地址
![图片](/imgs/wecom-bot-11.png) 点击“确认”后,选择您配置的自定义域名,复制回调地址,填回企微智能机器人配置页中。
填写完成后确认创建 ![图片](/imgs/use-cases/external-integration/wecom/6.png)
然后点击请求地址, 复制页面中的链接 ## 3. 使用智能机器人
![图片](/imgs/wecom-bot-12.png)
回到刚才的配置详情, 将刚才复制的链接填入 URL 框中, 并点击下方的保存 ,即可完成自建应用的创建
注意: 若复制的链接是以 "http://localhost" 开头, 需要将本地地址改为企业主体域名
因为企微会给填写的 URL 发送验证密文, 若 URL 为本地地址, 则本地接收不到企微的密文
若 URL 不是企业主体域名, 则验证会失败
## 3. 创建智能机器人
第二步创建企业自建应用是为了验证域名和IP的合规性, 并获取 secret 参数, 下面创建智能机器人才是正式的配置流程
在"安全与管理" - "管理工具"页面找到"智能机器人" ( 注意: 只有企业创建者或超级管理员才有权限看到这个入口 )
![图片](/imgs/wecom-bot-13.png)
创建机器人页面,下拉,找到,点击"API模式创建"
![图片](/imgs/wecom-bot-14.png)
与刚才配置自建应用同理, 在 FastGPT 平台再新增一个发布渠道, 并回到企业微信配置参数
![图片](/imgs/wecom-bot-19.png)
随机生成或者手动输入 Token 和 Encoding-AESKey, 分别填写到 FastGPT 的 Token 和 AES Key 中
![图片](/imgs/wecom-bot-15.png)
Corp ID 和 Secret 这两个参数和刚才的自建应用保持一致
Agent ID 和自建应用的不同, 需要先填写一个自定义值, 后续会根据企业微信提供的数据重新更改
在 FastGPT 将Corp ID, Secret, Agent ID, Token, AES Key 等参数都填写完毕后, 点击确认
然后点击请求地址, 复制页面中的链接
回到企业微信, 将链接粘贴到智能机器人的 URL 配置栏, 点击创建
创建完成后, 找到智能机器人的配置详情
![图片](/imgs/wecom-bot-16.png)
复制 Bot ID, 填写到 FastGPT 的 Agent ID 中, 即可完成智能机器人配置
![图片](/imgs/wecom-bot-17.png)
## 4. 使用智能机器人
在企业微信平台的"通讯录",即可找到创建的机器人,就可以发送消息了 在企业微信平台的"通讯录",即可找到创建的机器人,就可以发送消息了
![图片](/imgs/wecom-bot-18.png) ![图片](/imgs/use-cases/external-integration/wecom/7.png)
## FAQ ## FAQ
### 发送了消息,没响应 ### 发送了消息,没响应
1. 检查可信域名和可信IP是否配置正确。 1. 检查可信域名是否配置正确。
2. 检查自建应用的 Secret 参数是否与智能机器人一致。 2. 检查 Token 和 Encoding-AESKey 是否正确。
3. 查看 FastGPT 对话日志,是否有对应的提问记录 3. 查看 FastGPT 对话日志,是否有对应的提问记录。
4. 如果没记录,则可能是应用运行报错了,可以先试试最简单的机器人. 4. 如果没记录,则可能是应用运行报错了,可以先试试最简单的机器人.
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
"document/content/docs/introduction/development/openapi/chat.mdx": "2025-11-14T13:21:17+08:00", "document/content/docs/introduction/development/openapi/chat.mdx": "2025-11-14T13:21:17+08:00",
"document/content/docs/introduction/development/openapi/dataset.mdx": "2025-09-29T11:34:11+08:00", "document/content/docs/introduction/development/openapi/dataset.mdx": "2025-09-29T11:34:11+08:00",
"document/content/docs/introduction/development/openapi/intro.mdx": "2025-09-29T11:34:11+08:00", "document/content/docs/introduction/development/openapi/intro.mdx": "2025-09-29T11:34:11+08:00",
"document/content/docs/introduction/development/openapi/share.mdx": "2025-12-08T16:10:51+08:00", "document/content/docs/introduction/development/openapi/share.mdx": "2025-12-09T12:18:15+08:00",
"document/content/docs/introduction/development/proxy/cloudflare.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/development/proxy/cloudflare.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/development/proxy/http_proxy.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/development/proxy/http_proxy.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/development/proxy/nginx.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/development/proxy/nginx.mdx": "2025-07-23T21:35:03+08:00",
...@@ -89,6 +89,7 @@ ...@@ -89,6 +89,7 @@
"document/content/docs/introduction/guide/plugins/google_search_plugin_guide.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/plugins/google_search_plugin_guide.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/guide/plugins/searxng_plugin_guide.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/plugins/searxng_plugin_guide.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/guide/plugins/upload_system_tool.mdx": "2025-11-04T16:58:12+08:00", "document/content/docs/introduction/guide/plugins/upload_system_tool.mdx": "2025-11-04T16:58:12+08:00",
"document/content/docs/introduction/guide/team_permissions/customDomain.mdx": "2025-12-09T18:14:22+08:00",
"document/content/docs/introduction/guide/team_permissions/invitation_link.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/team_permissions/invitation_link.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/guide/team_permissions/team_roles_permissions.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/team_permissions/team_roles_permissions.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/index.en.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/index.en.mdx": "2025-07-23T21:35:03+08:00",
...@@ -101,7 +102,7 @@ ...@@ -101,7 +102,7 @@
"document/content/docs/protocol/terms.en.mdx": "2025-08-03T22:37:45+08:00", "document/content/docs/protocol/terms.en.mdx": "2025-08-03T22:37:45+08:00",
"document/content/docs/protocol/terms.mdx": "2025-08-03T22:37:45+08:00", "document/content/docs/protocol/terms.mdx": "2025-08-03T22:37:45+08:00",
"document/content/docs/toc.en.mdx": "2025-08-04T13:42:36+08:00", "document/content/docs/toc.en.mdx": "2025-08-04T13:42:36+08:00",
"document/content/docs/toc.mdx": "2025-11-29T09:24:47+08:00", "document/content/docs/toc.mdx": "2025-12-09T18:14:22+08:00",
"document/content/docs/upgrading/4-10/4100.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-10/4100.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/upgrading/4-10/4101.mdx": "2025-09-08T20:07:20+08:00", "document/content/docs/upgrading/4-10/4101.mdx": "2025-09-08T20:07:20+08:00",
"document/content/docs/upgrading/4-11/4110.mdx": "2025-08-05T23:20:39+08:00", "document/content/docs/upgrading/4-11/4110.mdx": "2025-08-05T23:20:39+08:00",
...@@ -118,7 +119,7 @@ ...@@ -118,7 +119,7 @@
"document/content/docs/upgrading/4-14/4141.mdx": "2025-11-19T10:15:27+08:00", "document/content/docs/upgrading/4-14/4141.mdx": "2025-11-19T10:15:27+08:00",
"document/content/docs/upgrading/4-14/4142.mdx": "2025-11-18T19:27:14+08:00", "document/content/docs/upgrading/4-14/4142.mdx": "2025-11-18T19:27:14+08:00",
"document/content/docs/upgrading/4-14/4143.mdx": "2025-11-26T20:52:05+08:00", "document/content/docs/upgrading/4-14/4143.mdx": "2025-11-26T20:52:05+08:00",
"document/content/docs/upgrading/4-14/4144.mdx": "2025-12-08T17:57:59+08:00", "document/content/docs/upgrading/4-14/4144.mdx": "2025-12-08T21:45:21+08:00",
"document/content/docs/upgrading/4-8/40.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-8/40.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/upgrading/4-8/41.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-8/41.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/upgrading/4-8/42.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-8/42.mdx": "2025-08-02T19:38:37+08:00",
...@@ -198,6 +199,6 @@ ...@@ -198,6 +199,6 @@
"document/content/docs/use-cases/external-integration/feishu.mdx": "2025-07-24T14:23:04+08:00", "document/content/docs/use-cases/external-integration/feishu.mdx": "2025-07-24T14:23:04+08:00",
"document/content/docs/use-cases/external-integration/official_account.mdx": "2025-08-05T23:20:39+08:00", "document/content/docs/use-cases/external-integration/official_account.mdx": "2025-08-05T23:20:39+08:00",
"document/content/docs/use-cases/external-integration/openapi.mdx": "2025-09-29T11:34:11+08:00", "document/content/docs/use-cases/external-integration/openapi.mdx": "2025-09-29T11:34:11+08:00",
"document/content/docs/use-cases/external-integration/wecom.mdx": "2025-09-16T14:22:28+08:00", "document/content/docs/use-cases/external-integration/wecom.mdx": "2025-12-09T20:03:29+08:00",
"document/content/docs/use-cases/index.mdx": "2025-07-24T14:23:04+08:00" "document/content/docs/use-cases/index.mdx": "2025-07-24T14:23:04+08:00"
} }
\ No newline at end of file
...@@ -2543,7 +2543,6 @@ ...@@ -2543,7 +2543,6 @@
"integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"csstype": "^3.0.2" "csstype": "^3.0.2"
} }
...@@ -2554,7 +2553,6 @@ ...@@ -2554,7 +2553,6 @@
"integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@types/react": "^19.0.0" "@types/react": "^19.0.0"
} }
...@@ -2576,7 +2574,6 @@ ...@@ -2576,7 +2574,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
...@@ -3244,7 +3241,6 @@ ...@@ -3244,7 +3241,6 @@
"resolved": "https://registry.npmjs.org/fumadocs-core/-/fumadocs-core-15.6.3.tgz", "resolved": "https://registry.npmjs.org/fumadocs-core/-/fumadocs-core-15.6.3.tgz",
"integrity": "sha512-71IPC6Y0ZLPHlavYormnF1r2uX/lNrTFTYCEh6Akll8hWxRNbKG9Hk4xpFJDTkU83c8eLtHk2iow/ccQkcV6Hw==", "integrity": "sha512-71IPC6Y0ZLPHlavYormnF1r2uX/lNrTFTYCEh6Akll8hWxRNbKG9Hk4xpFJDTkU83c8eLtHk2iow/ccQkcV6Hw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@formatjs/intl-localematcher": "^0.6.1", "@formatjs/intl-localematcher": "^0.6.1",
"@orama/orama": "^3.1.9", "@orama/orama": "^3.1.9",
...@@ -5177,7 +5173,6 @@ ...@@ -5177,7 +5173,6 @@
"resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz", "resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz",
"integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==", "integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@next/env": "15.5.7", "@next/env": "15.5.7",
"@swc/helpers": "0.5.15", "@swc/helpers": "0.5.15",
...@@ -5353,7 +5348,6 @@ ...@@ -5353,7 +5348,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
...@@ -5469,7 +5463,6 @@ ...@@ -5469,7 +5463,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
...@@ -5479,7 +5472,6 @@ ...@@ -5479,7 +5472,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"scheduler": "^0.26.0" "scheduler": "^0.26.0"
}, },
...@@ -6101,8 +6093,7 @@ ...@@ -6101,8 +6093,7 @@
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
"integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==", "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT"
"peer": true
}, },
"node_modules/tapable": { "node_modules/tapable": {
"version": "2.2.2", "version": "2.2.2",
...@@ -6198,7 +6189,6 @@ ...@@ -6198,7 +6189,6 @@
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
......
import { detect } from 'jschardet'; import { detect } from 'jschardet';
import { documentFileType } from './constants'; import { imageFileType } from './constants';
import { ChatFileTypeEnum } from '../../core/chat/constants'; import { ChatFileTypeEnum } from '../../core/chat/constants';
import { type UserChatItemFileItemType } from '../../core/chat/type'; import { type UserChatItemFileItemType } from '../../core/chat/type';
import * as fs from 'fs'; import * as fs from 'fs';
...@@ -66,18 +66,17 @@ export const parseUrlToFileType = (url: string): UserChatItemFileItemType | unde ...@@ -66,18 +66,17 @@ export const parseUrlToFileType = (url: string): UserChatItemFileItemType | unde
})(); })();
const extension = filename?.split('.').pop()?.toLowerCase() || ''; const extension = filename?.split('.').pop()?.toLowerCase() || '';
// If it's a document type, return as file, otherwise treat as image if (extension && imageFileType.includes(extension)) {
if (extension && documentFileType.includes(extension)) { // Default to file type for non-extension files
return { return {
type: ChatFileTypeEnum.file, type: ChatFileTypeEnum.image,
name: filename || 'null', name: filename || 'null',
url url
}; };
} }
// If it's a document type, return as file, otherwise treat as image
// Default to image type for non-document files
return { return {
type: ChatFileTypeEnum.image, type: ChatFileTypeEnum.file,
name: filename || 'null', name: filename || 'null',
url url
}; };
......
...@@ -4,3 +4,25 @@ export const isCSVFile = (filename: string) => { ...@@ -4,3 +4,25 @@ export const isCSVFile = (filename: string) => {
const extension = path.extname(filename).toLowerCase(); const extension = path.extname(filename).toLowerCase();
return extension === '.csv'; return extension === '.csv';
}; };
export function detectImageContentType(buffer: Buffer) {
if (!buffer || buffer.length < 12) return 'text/plain';
// JPEG
if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) return 'image/jpeg';
// PNG
const pngSig = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
if (pngSig.every((v, i) => buffer.readUInt8(i) === v)) return 'image/png';
// GIF
const gifSig = buffer.subarray(0, 6).toString('ascii');
if (gifSig === 'GIF87a' || gifSig === 'GIF89a') return 'image/gif';
// WEBP
const riff = buffer.subarray(0, 4).toString('ascii');
const webp = buffer.subarray(8, 12).toString('ascii');
if (riff === 'RIFF' && webp === 'WEBP') return 'image/webp';
return 'text/plain';
}
...@@ -128,6 +128,17 @@ export type FastGPTFeConfigsType = { ...@@ -128,6 +128,17 @@ export type FastGPTFeConfigsType = {
alipay?: boolean; alipay?: boolean;
bank?: boolean; bank?: boolean;
}; };
fileUrlWhitelist?: string[];
customDomain?: {
enable?: boolean;
domain?: {
aliyun?: string;
tencent?: string;
volcengine?: string;
};
};
ip_whitelist?: string;
}; };
export type SystemEnvType = { export type SystemEnvType = {
...@@ -147,6 +158,30 @@ export type SystemEnvType = { ...@@ -147,6 +158,30 @@ export type SystemEnvType = {
customPdfParse?: customPdfParseType; customPdfParse?: customPdfParseType;
fileUrlWhitelist?: string[]; fileUrlWhitelist?: string[];
customDomain?: customDomainType;
};
export type customDomainType = {
kc?: {
aliyun?: string;
tencent?: string;
volcengine?: string;
};
domain?: {
aliyun?: string;
tencent?: string;
volcengine?: string;
};
issuerServiceName?: {
aliyun?: string;
tencent?: string;
volcengine?: string;
};
nginxServiceName?: {
aliyun?: string;
tencent?: string;
volcengine?: string;
};
}; };
export type customPdfParseType = { export type customPdfParseType = {
......
import { createDocument } from 'zod-openapi';
import { DashboardPath } from './admin/core/dashboard';
import { TagsMap } from './tag';
export const adminOpenAPIDocument = createDocument({
openapi: '3.1.0',
info: {
title: 'FastGPT Admin API',
version: '0.1.0',
description: 'FastGPT Admin API 文档'
},
paths: {
...DashboardPath
},
servers: [{ url: '/api' }],
'x-tagGroups': [
{
name: '仪表盘',
tags: [TagsMap.adminDashboard]
}
]
});
import { z } from 'zod';
import { UsageSourceEnum } from '../../../../support/wallet/usage/constants';
// Common query schema
export const GetDataChartsQuerySchema = z.object({
startTime: z.string().meta({ description: '查询起始时间(ISO 8601 格式)' }),
sources: z.array(z.enum(UsageSourceEnum)).optional().meta({ description: '使用来源筛选' })
});
export type GetDataChartsQueryType = z.infer<typeof GetDataChartsQuerySchema>;
// Get user form data response
export const RegisteredUserCountSchema = z.object({
date: z.string().meta({ description: '注册日期' }),
count: z.number().meta({ description: '该日期注册的用户数' })
});
export const GetUserFormDataResponseSchema = z.object({
startUserCount: z.number().meta({ description: '起始时间之前的用户总数' }),
registeredUserCount: z.array(RegisteredUserCountSchema).meta({ description: '用户注册时间序列' })
});
export type GetUserFormDataResponseType = z.infer<typeof GetUserFormDataResponseSchema>;
// Get Pays Form Data Response
export const OrderAmountSchema = z.object({
date: z.string().meta({ description: '数据点日期' }),
totalCount: z.number().meta({ description: '订单总数' }),
successCount: z.number().meta({ description: '成功订单数' })
});
export const PayAmountSchema = z.object({
date: z.string().meta({ description: '数据点日期' }),
totalCount: z.number().meta({ description: '支付总金额' })
});
export const PayTeamSchema = z.object({
date: z.string().meta({ description: '数据点日期' }),
totalCount: z.number().meta({ description: '支付团队数' })
});
export const GetPaysFormDataResponseSchema = z.object({
orderAmounts: z.array(OrderAmountSchema).meta({ description: '订单数量时间序列' }),
payAmounts: z.array(PayAmountSchema).meta({ description: '支付金额时间序列' }),
payTeams: z.array(PayTeamSchema).meta({ description: '支付团队时间序列' })
});
export type GetPaysFormDataResponseType = z.infer<typeof GetPaysFormDataResponseSchema>;
// Get chat form data response
export const ChatAmountSchema = z.object({
date: z.string().meta({ description: '数据点日期' }),
totalCount: z.number().meta({ description: '对话总数' })
});
export const ChatItemAmountSchema = z.object({
date: z.string().meta({ description: '数据点日期' }),
totalCount: z.number().meta({ description: '对话消息总数' }),
averageCount: z.number().meta({ description: '每个对话的平均消息数' })
});
export const GetChatFormDataResponseSchema = z.object({
chatAmounts: z.array(ChatAmountSchema).meta({ description: '对话数量时间序列' }),
chatItemAmounts: z.array(ChatItemAmountSchema).meta({ description: '对话消息数量时间序列' })
});
export type GetChatFormDataResponseType = z.infer<typeof GetChatFormDataResponseSchema>;
// Get QPM range distribution response
export const QpmRangeSchema = z.object({
range: z.string().meta({ description: 'QPM 范围标签' }),
count: z.number().meta({ description: '范围内的团队数量' })
});
export const GetQpmRangeResponseSchema = z.object({
ranges: z.array(QpmRangeSchema).meta({ description: 'QPM 范围统计列表' })
});
export type GetQpmRangeResponseType = z.infer<typeof GetQpmRangeResponseSchema>;
// Get cost form data response
export const PointUsageSchema = z.object({
date: z.string().meta({ description: '数据点日期' }),
totalCount: z.number().meta({ description: '积分使用总数' })
});
export const GetCostFormDataResponseSchema = z.object({
pointUsages: z.array(PointUsageSchema).meta({ description: '积分使用时间序列' })
});
export type GetCostFormDataResponseType = z.infer<typeof GetCostFormDataResponseSchema>;
// Get user stats response
export const GetUserStatsResponseSchema = z.object({
usersCount: z.number().meta({ description: '用户总数' }),
rechargeCount: z.number().meta({ description: '充值总数' })
});
export type GetUserStatsResponseType = z.infer<typeof GetUserStatsResponseSchema>;
// Get app stats response
export const GetAppStatsResponseSchema = z.object({
workflowCount: z.number().meta({ description: '工作流总数' }),
simpleAppCount: z.number().meta({ description: '简易应用总数' }),
workflowToolCount: z.number().meta({ description: '工作流工具总数' }),
httpToolCount: z.number().meta({ description: 'HTTP 工具总数' }),
mcpToolCount: z.number().meta({ description: 'MCP 工具总数' })
});
export type GetAppStatsResponseType = z.infer<typeof GetAppStatsResponseSchema>;
// Get dataset stats response
export const GetDatasetStatsResponseSchema = z.object({
commonDatasetCount: z.number().meta({ description: '通用知识库总数' }),
websiteDatasetCount: z.number().meta({ description: 'Web 站点同步总数' }),
apiDatasetCount: z.number().meta({ description: 'API 知识库总数' }),
yuqueDatasetCount: z.number().meta({ description: '语雀知识库总数' }),
feishuDatasetCount: z.number().meta({ description: '飞书知识库总数' }),
totalIndexCount: z.number().meta({ description: '索引总量' })
});
export type GetDatasetStatsResponseType = z.infer<typeof GetDatasetStatsResponseSchema>;
import { z } from 'zod';
import type { OpenAPIPath } from '../../../type';
import {
GetDataChartsQuerySchema,
GetChatFormDataResponseSchema,
GetCostFormDataResponseSchema,
GetPaysFormDataResponseSchema,
GetUserFormDataResponseSchema,
GetQpmRangeResponseSchema,
GetUserStatsResponseSchema,
GetAppStatsResponseSchema,
GetDatasetStatsResponseSchema
} from './api';
import { TagsMap } from '../../../tag';
export * from './api';
export const DashboardPath: OpenAPIPath = {
'/admin/core/dashboard/getUserStats': {
get: {
summary: '获取用户全局统计',
description: '获取用户总数和充值总数',
tags: [TagsMap.adminDashboard],
responses: {
200: {
description: '成功获取用户统计',
content: {
'application/json': {
schema: GetUserStatsResponseSchema
}
}
}
}
}
},
'/admin/core/dashboard/getAppStats': {
get: {
summary: '获取应用全局统计',
description: '获取工作流、简易应用、工作流工具、HTTP 工具和 MCP 工具的总数',
tags: [TagsMap.adminDashboard],
responses: {
200: {
description: '成功获取应用统计',
content: {
'application/json': {
schema: GetAppStatsResponseSchema
}
}
}
}
}
},
'/admin/core/dashboard/getDatasetStats': {
get: {
summary: '获取知识库全局统计',
description: '获取通用知识库、Web 站点同步、API、语雀、飞书知识库的总数以及索引总量',
tags: [TagsMap.adminDashboard],
responses: {
200: {
description: '成功获取知识库统计',
content: {
'application/json': {
schema: GetDatasetStatsResponseSchema
}
}
}
}
}
},
'/admin/core/dashboard/getChatFormData': {
get: {
summary: '获取对话统计数据',
description: '获取对话数量和对话消息数量的时间序列统计数据',
tags: [TagsMap.adminDashboard],
requestParams: {
query: z.object({
startTime: z.string().meta({
description: '查询起始时间(ISO 8601 格式)'
})
})
},
responses: {
200: {
description: '成功获取对话统计数据',
content: {
'application/json': {
schema: GetChatFormDataResponseSchema
}
}
}
}
}
},
'/admin/core/dashboard/getWorkflowQpmRange': {
get: {
summary: '获取工作流 QPM 范围分布',
description: '按团队最大 QPM 统计各范围的团队数量',
tags: [TagsMap.adminDashboard],
requestParams: {
query: z.object({
startTime: z.string().meta({
description: '查询起始时间(ISO 8601 格式)'
})
})
},
responses: {
200: {
description: '成功获取 QPM 范围分布',
content: {
'application/json': {
schema: GetQpmRangeResponseSchema
}
}
}
}
}
},
'/admin/core/dashboard/getCostFormData': {
post: {
summary: '获取消费统计数据',
description: '获取积分消耗的时间序列统计数据',
tags: [TagsMap.adminDashboard],
requestBody: {
content: {
'application/json': {
schema: GetDataChartsQuerySchema
}
}
},
responses: {
200: {
description: '成功获取消费统计数据',
content: {
'application/json': {
schema: GetCostFormDataResponseSchema
}
}
}
}
}
},
'/admin/core/dashboard/getPaysFormData': {
get: {
summary: '获取支付统计数据',
description: '获取订单和支付金额的时间序列统计数据',
tags: [TagsMap.adminDashboard],
requestParams: {
query: z.object({
startTime: z.string().meta({
description: '查询起始时间(ISO 8601 格式)'
})
})
},
responses: {
200: {
description: '成功获取支付统计数据',
content: {
'application/json': {
schema: GetPaysFormDataResponseSchema
}
}
}
}
}
},
'/admin/core/dashboard/getUserFormData': {
get: {
summary: '获取用户注册统计数据',
description: '获取用户注册数量的时间序列统计数据',
tags: [TagsMap.adminDashboard],
requestParams: {
query: z.object({
startTime: z.string().meta({
description: '查询起始时间(ISO 8601 格式)'
})
})
},
responses: {
200: {
description: '成功获取用户统计数据',
content: {
'application/json': {
schema: GetUserFormDataResponseSchema
}
}
}
}
}
}
};
...@@ -8,5 +8,7 @@ export const TagsMap = { ...@@ -8,5 +8,7 @@ export const TagsMap = {
pluginTeam: '团队插件管理', pluginTeam: '团队插件管理',
apiKey: 'APIKey', apiKey: 'APIKey',
walletBill: '订单', walletBill: '订单',
walletDiscountCoupon: '优惠券' walletDiscountCoupon: '优惠券',
adminDashboard: '管理员仪表盘'
}; };
import z from 'zod';
export const CustomDomainStatusEnum = z.enum([
'active', // confirm to active
'inactive' // scheduled task find DNS Resolve is down
]);
export const ProviderEnum = z.enum(['aliyun', 'tencent', 'volcengine']);
export const VerifyFileType = z.object({
path: z.string(),
content: z.string()
});
export const CustomDomainType = z.object({
teamId: z.string(),
domain: z.string(),
cnameDomain: z.string(),
status: CustomDomainStatusEnum,
verifyFile: VerifyFileType.optional(),
provider: ProviderEnum
});
export type VerifyFileType = z.infer<typeof VerifyFileType>;
export type CustomDomainType = z.infer<typeof CustomDomainType>;
export type CreateCustomDomainBody = {
domain: string;
provider: ProviderEnum;
cnameDomain: string;
};
export type ProviderEnum = z.infer<typeof ProviderEnum>;
export type CustomDomainStatusEnum = z.infer<typeof CustomDomainStatusEnum>;
export type UpdateDomainVerifyFileBody = {
domain: string;
path: string;
content: string;
};
import { customAlphabet } from 'nanoid';
/**
* @param domain : should be like sealosbja.site (secondary domain)
* @returns CNAME domain: fastgpt-<random_string>.<domain>
*/
export const generateCNAMEDomain = (domain: string): string => {
const str = customAlphabet('abcdefghijklmnopqrstuvwxyz', 8);
return `fastgpt-${str()}.${domain}`;
};
...@@ -20,11 +20,15 @@ export interface DingtalkAppType { ...@@ -20,11 +20,15 @@ export interface DingtalkAppType {
} }
export interface WecomAppType { export interface WecomAppType {
AgentId: string;
CorpId: string;
SuiteSecret: string;
CallbackToken: string; CallbackToken: string;
CallbackEncodingAesKey: string; CallbackEncodingAesKey: string;
/** @deprecated */
// AgentId: string;
/** @deprecated */
// CorpId: string;
/** @deprecated */
// SuiteSecret: string;
} }
// TODO: unused // TODO: unused
......
...@@ -12,6 +12,7 @@ export type CustomSubConfig = { ...@@ -12,6 +12,7 @@ export type CustomSubConfig = {
appRegistrationCount: number; appRegistrationCount: number;
auditLogStoreDuration: number; auditLogStoreDuration: number;
ticketResponseTime: number; ticketResponseTime: number;
customDomain: number;
}; };
export type TeamCouponSub = { export type TeamCouponSub = {
......
...@@ -20,6 +20,7 @@ export type TeamStandardSubPlanItemType = { ...@@ -20,6 +20,7 @@ export type TeamStandardSubPlanItemType = {
websiteSyncPerDataset?: number; websiteSyncPerDataset?: number;
auditLogStoreDuration?: number; auditLogStoreDuration?: number;
ticketResponseTime?: number; ticketResponseTime?: number;
customDomain?: number;
// Custom plan specific fields // Custom plan specific fields
priceDescription?: string; priceDescription?: string;
...@@ -73,6 +74,7 @@ export type TeamSubSchema = { ...@@ -73,6 +74,7 @@ export type TeamSubSchema = {
appRegistrationCount?: number; appRegistrationCount?: number;
auditLogStoreDuration?: number; auditLogStoreDuration?: number;
ticketResponseTime?: number; ticketResponseTime?: number;
customDomain?: number;
totalPoints: number; totalPoints: number;
surplusPoints: number; surplusPoints: number;
......
...@@ -11,19 +11,24 @@ const TrackSchema = new Schema({ ...@@ -11,19 +11,24 @@ const TrackSchema = new Schema({
data: Object data: Object
}); });
try { TrackSchema.index({ event: 1 });
TrackSchema.index({ event: 1 }); // Dataset search index
TrackSchema.index(
TrackSchema.index( { event: 1, teamId: 1, 'data.datasetId': 1, createTime: -1 },
{ event: 1, teamId: 1, 'data.datasetId': 1, createTime: -1 }, {
{ partialFilterExpression: {
partialFilterExpression: { event: TrackEnum.datasetSearch
'data.datasetId': { $exists: true } }
} }
);
// QPM index
TrackSchema.index(
{ event: 1, createTime: -1, 'data.requestCount': 1 },
{
partialFilterExpression: {
event: TrackEnum.teamChatQPM
} }
); }
} catch (error) { );
console.log(error);
}
export const TrackModel = getMongoModel<TrackSchemaType>('track', TrackSchema); export const TrackModel = getMongoModel<TrackSchemaType>('tracks', TrackSchema);
...@@ -10,11 +10,11 @@ import { ...@@ -10,11 +10,11 @@ import {
import { defaultS3Options, getSystemMaxFileSize, Mimes } from '../constants'; import { defaultS3Options, getSystemMaxFileSize, Mimes } from '../constants';
import path from 'node:path'; import path from 'node:path';
import { MongoS3TTL } from '../schema'; import { MongoS3TTL } from '../schema';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { addHours, addMinutes } from 'date-fns'; import { addHours, addMinutes } from 'date-fns';
import { addLog } from '../../system/log'; import { addLog } from '../../system/log';
import { addS3DelJob } from '../mq'; import { addS3DelJob } from '../mq';
import { type Readable } from 'node:stream'; import { type Readable } from 'node:stream';
import { type UploadFileByBufferParams, UploadFileByBufferSchema } from '../type';
export class S3BaseBucket { export class S3BaseBucket {
private _client: Client; private _client: Client;
...@@ -62,7 +62,9 @@ export class S3BaseBucket { ...@@ -62,7 +62,9 @@ export class S3BaseBucket {
await this.options.afterInit?.(); await this.options.afterInit?.();
console.log(`S3 init success: ${this.name}`); console.log(`S3 init success: ${this.name}`);
}; };
init(); if (this.options.init) {
init();
}
} }
get name(): string { get name(): string {
...@@ -251,4 +253,25 @@ export class S3BaseBucket { ...@@ -251,4 +253,25 @@ export class S3BaseBucket {
return await this.client.presignedGetObject(this.name, key, expires); return await this.client.presignedGetObject(this.name, key, expires);
} }
async uploadFileByBuffer(params: UploadFileByBufferParams) {
const { key, buffer, contentType } = UploadFileByBufferSchema.parse(params);
await MongoS3TTL.create({
minioKey: key,
bucketName: this.name,
expiredTime: addHours(new Date(), 1)
});
await this.putObject(key, buffer, undefined, {
'Content-Type': contentType || 'application/octet-stream'
});
return {
key,
accessUrl: await this.createExternalUrl({
key,
expiredHours: 2
})
};
}
} }
...@@ -28,6 +28,7 @@ export const Mimes = { ...@@ -28,6 +28,7 @@ export const Mimes = {
export const defaultS3Options: { export const defaultS3Options: {
externalBaseURL?: string; externalBaseURL?: string;
afterInit?: () => Promise<void> | void; afterInit?: () => Promise<void> | void;
init?: boolean;
} & ClientOptions = { } & ClientOptions = {
useSSL: process.env.S3_USE_SSL === 'true', useSSL: process.env.S3_USE_SSL === 'true',
endPoint: process.env.S3_ENDPOINT || 'localhost', endPoint: process.env.S3_ENDPOINT || 'localhost',
...@@ -53,8 +54,4 @@ export const getSystemMaxFileSize = () => { ...@@ -53,8 +54,4 @@ export const getSystemMaxFileSize = () => {
return config; // bytes return config; // bytes
}; };
export const S3_KEY_PATH_INVALID_CHARS_MAP: Record<string, boolean> = { export const S3_KEY_PATH_INVALID_CHARS = /[|\\/]/;
'/': true,
'\\': true,
'|': true
};
...@@ -4,8 +4,8 @@ import { addLog } from '../system/log'; ...@@ -4,8 +4,8 @@ import { addLog } from '../system/log';
import { startS3DelWorker } from './mq'; import { startS3DelWorker } from './mq';
export function initS3Buckets() { export function initS3Buckets() {
const publicBucket = new S3PublicBucket(); const publicBucket = new S3PublicBucket({ init: true });
const privateBucket = new S3PrivateBucket(); const privateBucket = new S3PrivateBucket({ init: true });
global.s3BucketMap = { global.s3BucketMap = {
[publicBucket.name]: publicBucket, [publicBucket.name]: publicBucket,
......
...@@ -7,16 +7,11 @@ import { getFileS3Key } from '../utils'; ...@@ -7,16 +7,11 @@ import { getFileS3Key } from '../utils';
class S3AvatarSource { class S3AvatarSource {
private bucket: S3PublicBucket; private bucket: S3PublicBucket;
private static instance: S3AvatarSource;
constructor() { constructor() {
this.bucket = new S3PublicBucket(); this.bucket = new S3PublicBucket();
} }
static getInstance() {
return (this.instance ??= new S3AvatarSource());
}
get prefix(): string { get prefix(): string {
return imageBaseUrl; return imageBaseUrl;
} }
...@@ -89,5 +84,13 @@ class S3AvatarSource { ...@@ -89,5 +84,13 @@ class S3AvatarSource {
} }
export function getS3AvatarSource() { export function getS3AvatarSource() {
return S3AvatarSource.getInstance(); if (global.avatarBucket) {
return global.avatarBucket;
}
global.avatarBucket = new S3AvatarSource();
return global.avatarBucket;
}
declare global {
var avatarBucket: S3AvatarSource;
} }
...@@ -5,7 +5,9 @@ import { ...@@ -5,7 +5,9 @@ import {
type CheckChatFileKeys, type CheckChatFileKeys,
type DelChatFileByPrefixParams, type DelChatFileByPrefixParams,
ChatFileUploadSchema, ChatFileUploadSchema,
DelChatFileByPrefixSchema DelChatFileByPrefixSchema,
UploadChatFileSchema,
type UploadFileParams
} from './type'; } from './type';
import { differenceInHours } from 'date-fns'; import { differenceInHours } from 'date-fns';
import { S3Buckets } from '../../constants'; import { S3Buckets } from '../../constants';
...@@ -14,16 +16,11 @@ import { getFileS3Key } from '../../utils'; ...@@ -14,16 +16,11 @@ import { getFileS3Key } from '../../utils';
export class S3ChatSource { export class S3ChatSource {
private bucket: S3PrivateBucket; private bucket: S3PrivateBucket;
private static instance: S3ChatSource;
constructor() { constructor() {
this.bucket = new S3PrivateBucket(); this.bucket = new S3PrivateBucket();
} }
static getInstance() {
return (this.instance ??= new S3ChatSource());
}
static parseChatUrl(url: string | URL) { static parseChatUrl(url: string | URL) {
try { try {
const parseUrl = new URL(url); const parseUrl = new URL(url);
...@@ -95,7 +92,7 @@ export class S3ChatSource { ...@@ -95,7 +92,7 @@ export class S3ChatSource {
const { fileKey } = getFileS3Key.chat({ appId, chatId, uId, filename }); const { fileKey } = getFileS3Key.chat({ appId, chatId, uId, filename });
return await this.bucket.createPostPresignedUrl( return await this.bucket.createPostPresignedUrl(
{ rawKey: fileKey, filename }, { rawKey: fileKey, filename },
{ expiredHours: expiredTime ? differenceInHours(new Date(), expiredTime) : 24 } { expiredHours: expiredTime ? differenceInHours(expiredTime, new Date()) : 24 }
); );
} }
...@@ -109,8 +106,33 @@ export class S3ChatSource { ...@@ -109,8 +106,33 @@ export class S3ChatSource {
deleteChatFileByKey(key: string) { deleteChatFileByKey(key: string) {
return this.bucket.addDeleteJob({ key }); return this.bucket.addDeleteJob({ key });
} }
async uploadChatFileByBuffer(params: UploadFileParams) {
const { appId, chatId, uId, filename, buffer, contentType } =
UploadChatFileSchema.parse(params);
const { fileKey } = getFileS3Key.chat({
appId,
chatId,
uId,
filename
});
return this.bucket.uploadFileByBuffer({
key: fileKey,
buffer,
contentType
});
}
} }
export function getS3ChatSource() { export function getS3ChatSource() {
return S3ChatSource.getInstance(); if (global.chatBucket) {
return global.chatBucket;
}
global.chatBucket = new S3ChatSource();
return global.chatBucket;
}
declare global {
var chatBucket: S3ChatSource;
} }
...@@ -16,3 +16,14 @@ export const DelChatFileByPrefixSchema = z.object({ ...@@ -16,3 +16,14 @@ export const DelChatFileByPrefixSchema = z.object({
uId: z.string().nonempty().optional() uId: z.string().nonempty().optional()
}); });
export type DelChatFileByPrefixParams = z.infer<typeof DelChatFileByPrefixSchema>; export type DelChatFileByPrefixParams = z.infer<typeof DelChatFileByPrefixSchema>;
export const UploadChatFileSchema = z.object({
appId: ObjectIdSchema,
chatId: z.string().nonempty(),
uId: z.string().nonempty(),
filename: z.string().nonempty(),
buffer: z.instanceof(Buffer),
contentType: z.string().optional()
});
export type UploadFileParams = z.infer<typeof UploadChatFileSchema>;
...@@ -29,16 +29,11 @@ import { S3Error } from 'minio'; ...@@ -29,16 +29,11 @@ import { S3Error } from 'minio';
export class S3DatasetSource { export class S3DatasetSource {
public bucket: S3PrivateBucket; public bucket: S3PrivateBucket;
private static instance: S3DatasetSource;
constructor() { constructor() {
this.bucket = new S3PrivateBucket(); this.bucket = new S3PrivateBucket();
} }
static getInstance() {
return (this.instance ??= new S3DatasetSource());
}
// 下载链接 // 下载链接
async createGetDatasetFileURL(params: CreateGetDatasetFileURLParams) { async createGetDatasetFileURL(params: CreateGetDatasetFileURLParams) {
const { key, expiredHours, external } = CreateGetDatasetFileURLParamsSchema.parse(params); const { key, expiredHours, external } = CreateGetDatasetFileURLParamsSchema.parse(params);
...@@ -260,5 +255,13 @@ export class S3DatasetSource { ...@@ -260,5 +255,13 @@ export class S3DatasetSource {
} }
export function getS3DatasetSource() { export function getS3DatasetSource() {
return S3DatasetSource.getInstance(); if (global.datasetBucket) {
return global.datasetBucket;
}
global.datasetBucket = new S3DatasetSource();
return global.datasetBucket;
}
declare global {
var datasetBucket: S3DatasetSource;
} }
...@@ -56,6 +56,13 @@ export const UploadImage2S3BucketParamsSchema = z.object({ ...@@ -56,6 +56,13 @@ export const UploadImage2S3BucketParamsSchema = z.object({
}); });
export type UploadImage2S3BucketParams = z.infer<typeof UploadImage2S3BucketParamsSchema>; export type UploadImage2S3BucketParams = z.infer<typeof UploadImage2S3BucketParamsSchema>;
export const UploadFileByBufferSchema = z.object({
buffer: z.instanceof(Buffer),
contentType: z.string().optional(),
key: z.string().nonempty()
});
export type UploadFileByBufferParams = z.infer<typeof UploadFileByBufferSchema>;
declare global { declare global {
var s3BucketMap: { var s3BucketMap: {
[key: string]: S3BaseBucket; [key: string]: S3BaseBucket;
......
...@@ -11,7 +11,6 @@ import { getNanoid } from '@fastgpt/global/common/string/tools'; ...@@ -11,7 +11,6 @@ import { getNanoid } from '@fastgpt/global/common/string/tools';
import path from 'node:path'; import path from 'node:path';
import type { ParsedFileContentS3KeyParams } from './sources/dataset/type'; import type { ParsedFileContentS3KeyParams } from './sources/dataset/type';
import { EndpointUrl } from '@fastgpt/global/common/file/constants'; import { EndpointUrl } from '@fastgpt/global/common/file/constants';
import type { NextApiRequest } from 'next';
// S3文件名最大长度配置 // S3文件名最大长度配置
export const S3_FILENAME_MAX_LENGTH = 50; export const S3_FILENAME_MAX_LENGTH = 50;
...@@ -246,130 +245,3 @@ export function isS3ObjectKey<T extends keyof typeof S3Sources>( ...@@ -246,130 +245,3 @@ export function isS3ObjectKey<T extends keyof typeof S3Sources>(
): key is `${T}/${string}` { ): key is `${T}/${string}` {
return typeof key === 'string' && key.startsWith(`${S3Sources[source]}/`); return typeof key === 'string' && key.startsWith(`${S3Sources[source]}/`);
} }
// export const multer = {
// _storage: multer.diskStorage({
// filename: (_, file, cb) => {
// if (!file?.originalname) {
// cb(new Error('File not found'), '');
// } else {
// const ext = path.extname(decodeURIComponent(file.originalname));
// cb(null, `${getNanoid()}${ext}`);
// }
// }
// }),
// singleStore(maxFileSize: number = 500) {
// const fileSize = maxFileSize * 1024 * 1024;
// return multer({
// limits: {
// fileSize
// },
// preservePath: true,
// storage: this._storage
// }).single('file');
// },
// multipleStore(maxFileSize: number = 500) {
// const fileSize = maxFileSize * 1024 * 1024;
// return multer({
// limits: {
// fileSize
// },
// preservePath: true,
// storage: this._storage
// }).array('file', global.feConfigs?.uploadFileMaxSize);
// },
// resolveFormData({ request, maxFileSize }: { request: NextApiRequest; maxFileSize?: number }) {
// return new Promise<{
// data: Record<string, any>;
// fileMetadata: Express.Multer.File;
// getBuffer: () => Buffer;
// getReadStream: () => fs.ReadStream;
// }>((resolve, reject) => {
// const handler = this.singleStore(maxFileSize);
// // @ts-expect-error it can accept a NextApiRequest
// handler(request, null, (error) => {
// if (error) {
// return reject(error);
// }
// // @ts-expect-error `file` will be injected by multer
// const file = request.file as Express.Multer.File;
// if (!file) {
// return reject(new Error('File not found'));
// }
// const data = (() => {
// if (!request.body?.data) return {};
// try {
// return JSON.parse(request.body.data);
// } catch {
// return {};
// }
// })();
// resolve({
// data,
// fileMetadata: file,
// getBuffer: () => fs.readFileSync(file.path),
// getReadStream: () => fs.createReadStream(file.path)
// });
// });
// });
// },
// resolveMultipleFormData({
// request,
// maxFileSize
// }: {
// request: NextApiRequest;
// maxFileSize?: number;
// }) {
// return new Promise<{
// data: Record<string, any>;
// fileMetadata: Array<Express.Multer.File>;
// }>((resolve, reject) => {
// const handler = this.multipleStore(maxFileSize);
// // @ts-expect-error it can accept a NextApiRequest
// handler(request, null, (error) => {
// if (error) {
// return reject(error);
// }
// // @ts-expect-error `files` will be injected by multer
// const files = request.files as Array<Express.Multer.File>;
// if (!files || files.length === 0) {
// return reject(new Error('File not found'));
// }
// const data = (() => {
// if (!request.body?.data) return {};
// try {
// return JSON.parse(request.body.data);
// } catch {
// return {};
// }
// })();
// resolve({
// data,
// fileMetadata: files
// });
// });
// });
// },
// clearDiskTempFiles(filepaths: string[]) {
// for (const filepath of filepaths) {
// fs.unlink(filepath, (_) => {});
// }
// }
// };
...@@ -64,13 +64,14 @@ export const recallFromVectorStore = (props: EmbeddingRecallCtrlProps) => ...@@ -64,13 +64,14 @@ export const recallFromVectorStore = (props: EmbeddingRecallCtrlProps) =>
retryFn(() => Vector.embRecall(props)); retryFn(() => Vector.embRecall(props));
export const getVectorDataByTime = Vector.getVectorDataByTime; export const getVectorDataByTime = Vector.getVectorDataByTime;
// Count vector
export const getVectorCountByTeamId = async (teamId: string) => { export const getVectorCountByTeamId = async (teamId: string) => {
const cacheCount = await teamVectorCache.get(teamId); const cacheCount = await teamVectorCache.get(teamId);
if (cacheCount !== undefined) { if (cacheCount !== undefined) {
return cacheCount; return cacheCount;
} }
const count = await Vector.getVectorCountByTeamId(teamId); const count = await Vector.getVectorCount({ teamId });
teamVectorCache.set({ teamVectorCache.set({
teamId, teamId,
...@@ -79,9 +80,7 @@ export const getVectorCountByTeamId = async (teamId: string) => { ...@@ -79,9 +80,7 @@ export const getVectorCountByTeamId = async (teamId: string) => {
return count; return count;
}; };
export const getVectorCount = Vector.getVectorCount;
export const getVectorCountByDatasetId = Vector.getVectorCountByDatasetId;
export const getVectorCountByCollectionId = Vector.getVectorCountByCollectionId;
export const insertDatasetDataVector = async ({ export const insertDatasetDataVector = async ({
model, model,
......
...@@ -257,43 +257,36 @@ export class MilvusCtrl { ...@@ -257,43 +257,36 @@ export class MilvusCtrl {
}; };
}; };
getVectorCountByTeamId = async (teamId: string) => { getVectorCount = async (props: {
teamId?: string;
datasetId?: string;
collectionId?: string;
}) => {
const { teamId, datasetId, collectionId } = props;
const client = await this.getClient(); const client = await this.getClient();
const result = await client.query({ // Build filter conditions dynamically (each condition wrapped in parentheses)
collection_name: DatasetVectorTableName, const filterConditions: string[] = [];
output_fields: ['count(*)'],
filter: `teamId == "${String(teamId)}"`
});
const total = result.data?.[0]?.['count(*)'] as number;
return total; if (teamId) {
}; filterConditions.push(`(teamId == "${String(teamId)}")`);
getVectorCountByDatasetId = async (teamId: string, datasetId: string) => { }
const client = await this.getClient();
const result = await client.query({ if (datasetId) {
collection_name: DatasetVectorTableName, filterConditions.push(`(datasetId == "${String(datasetId)}")`);
output_fields: ['count(*)'], }
filter: `(teamId == "${String(teamId)}") and (dataset == "${String(datasetId)}")`
});
const total = result.data?.[0]?.['count(*)'] as number; if (collectionId) {
filterConditions.push(`(collectionId == "${String(collectionId)}")`);
}
return total; // If no conditions provided, count all (empty filter)
}; const filter = filterConditions.length > 0 ? filterConditions.join(' and ') : '';
getVectorCountByCollectionId = async (
teamId: string,
datasetId: string,
collectionId: string
) => {
const client = await this.getClient();
const result = await client.query({ const result = await client.query({
collection_name: DatasetVectorTableName, collection_name: DatasetVectorTableName,
output_fields: ['count(*)'], output_fields: ['count(*)'],
filter: `(teamId == "${String(teamId)}") and (datasetId == "${String(datasetId)}") and (collectionId == "${String(collectionId)}")` filter: filter || undefined
}); });
const total = result.data?.[0]?.['count(*)'] as number; const total = result.data?.[0]?.['count(*)'] as number;
......
...@@ -180,33 +180,34 @@ export class ObVectorCtrl { ...@@ -180,33 +180,34 @@ export class ObVectorCtrl {
datasetId: item.dataset_id datasetId: item.dataset_id
})); }));
}; };
getVectorCountByTeamId = async (teamId: string) => {
const total = await ObClient.count(DatasetVectorTableName, {
where: [['team_id', String(teamId)]]
});
return total; getVectorCount = async (props: {
}; teamId?: string;
getVectorCountByDatasetId = async (teamId: string, datasetId: string) => { datasetId?: string;
const total = await ObClient.count(DatasetVectorTableName, { collectionId?: string;
where: [['team_id', String(teamId)], 'and', ['dataset_id', String(datasetId)]] }) => {
}); const { teamId, datasetId, collectionId } = props;
return total; // Build where conditions dynamically
}; const whereConditions: any[] = [];
getVectorCountByCollectionId = async (
teamId: string, if (teamId) {
datasetId: string, whereConditions.push(['team_id', String(teamId)]);
collectionId: string }
) => {
if (datasetId) {
if (whereConditions.length > 0) whereConditions.push('and');
whereConditions.push(['dataset_id', String(datasetId)]);
}
if (collectionId) {
if (whereConditions.length > 0) whereConditions.push('and');
whereConditions.push(['collection_id', String(collectionId)]);
}
// If no conditions provided, count all
const total = await ObClient.count(DatasetVectorTableName, { const total = await ObClient.count(DatasetVectorTableName, {
where: [ where: whereConditions.length > 0 ? whereConditions : undefined
['team_id', String(teamId)],
'and',
['dataset_id', String(datasetId)],
'and',
['collection_id', String(collectionId)]
]
}); });
return total; return total;
......
...@@ -204,33 +204,34 @@ export class PgVectorCtrl { ...@@ -204,33 +204,34 @@ export class PgVectorCtrl {
datasetId: item.dataset_id datasetId: item.dataset_id
})); }));
}; };
getVectorCountByTeamId = async (teamId: string) => {
const total = await PgClient.count(DatasetVectorTableName, {
where: [['team_id', String(teamId)]]
});
return total; getVectorCount = async (props: {
}; teamId?: string;
getVectorCountByDatasetId = async (teamId: string, datasetId: string) => { datasetId?: string;
const total = await PgClient.count(DatasetVectorTableName, { collectionId?: string;
where: [['team_id', String(teamId)], 'and', ['dataset_id', String(datasetId)]] }) => {
}); const { teamId, datasetId, collectionId } = props;
return total; // Build where conditions dynamically
}; const whereConditions: any[] = [];
getVectorCountByCollectionId = async (
teamId: string, if (teamId) {
datasetId: string, whereConditions.push(['team_id', String(teamId)]);
collectionId: string }
) => {
if (datasetId) {
if (whereConditions.length > 0) whereConditions.push('and');
whereConditions.push(['dataset_id', String(datasetId)]);
}
if (collectionId) {
if (whereConditions.length > 0) whereConditions.push('and');
whereConditions.push(['collection_id', String(collectionId)]);
}
// If no conditions provided, count all
const total = await PgClient.count(DatasetVectorTableName, { const total = await PgClient.count(DatasetVectorTableName, {
where: [ where: whereConditions.length > 0 ? whereConditions : undefined
['team_id', String(teamId)],
'and',
['dataset_id', String(datasetId)],
'and',
['collection_id', String(collectionId)]
]
}); });
return total; return total;
......
...@@ -126,7 +126,6 @@ const AppSchema = new Schema( ...@@ -126,7 +126,6 @@ const AppSchema = new Schema(
} }
); );
AppSchema.index({ type: 1 });
AppSchema.index({ teamId: 1, updateTime: -1 }); AppSchema.index({ teamId: 1, updateTime: -1 });
AppSchema.index({ teamId: 1, type: 1 }); AppSchema.index({ teamId: 1, type: 1 });
AppSchema.index( AppSchema.index(
...@@ -137,5 +136,7 @@ AppSchema.index( ...@@ -137,5 +136,7 @@ AppSchema.index(
} }
} }
); );
// Admin count
AppSchema.index({ type: 1 });
export const MongoApp = getMongoModel<AppType>(AppCollectionName, AppSchema); export const MongoApp = getMongoModel<AppType>(AppCollectionName, AppSchema);
...@@ -96,40 +96,20 @@ export async function delDatasetRelevantData({ ...@@ -96,40 +96,20 @@ export async function delDatasetRelevantData({
datasetId: { $in: datasetIds } datasetId: { $in: datasetIds }
}); });
// Delete dataset_data_texts in batches by datasetId
for (const datasetId of datasetIds) { for (const datasetId of datasetIds) {
// Delete dataset_data_texts in batches by datasetId
await MongoDatasetDataText.deleteMany({ await MongoDatasetDataText.deleteMany({
teamId, teamId,
datasetId datasetId
}).maxTimeMS(300000); // Reduce timeout for single batch }).maxTimeMS(300000); // Reduce timeout for single batch
} // Delete dataset_datas in batches by datasetId
// Delete dataset_datas in batches by datasetId
for (const datasetId of datasetIds) {
await MongoDatasetData.deleteMany({
teamId,
datasetId
}).maxTimeMS(300000);
}
await delCollectionRelatedSource({ collections });
// Delete vector data
await deleteDatasetDataVector({ teamId, datasetIds });
// Delete dataset_data_texts in batches by datasetId
for (const datasetId of datasetIds) {
await MongoDatasetDataText.deleteMany({
teamId,
datasetId
}).maxTimeMS(300000); // Reduce timeout for single batch
}
// Delete dataset_datas in batches by datasetId
for (const datasetId of datasetIds) {
await MongoDatasetData.deleteMany({ await MongoDatasetData.deleteMany({
teamId, teamId,
datasetId datasetId
}).maxTimeMS(300000); }).maxTimeMS(300000);
} }
// Delete source: 兼容旧版的图片
await delCollectionRelatedSource({ collections }); await delCollectionRelatedSource({ collections });
// Delete vector data // Delete vector data
await deleteDatasetDataVector({ teamId, datasetIds }); await deleteDatasetDataVector({ teamId, datasetIds });
......
...@@ -148,7 +148,7 @@ const DatasetSchema = new Schema({ ...@@ -148,7 +148,7 @@ const DatasetSchema = new Schema({
try { try {
DatasetSchema.index({ teamId: 1 }); DatasetSchema.index({ teamId: 1 });
DatasetSchema.index({ type: 1 }); DatasetSchema.index({ type: 1 }); // Admin count
DatasetSchema.index({ deleteTime: 1 }); // 添加软删除字段索引 DatasetSchema.index({ deleteTime: 1 }); // 添加软删除字段索引
} catch (error) { } catch (error) {
console.log(error); console.log(error);
......
...@@ -179,13 +179,13 @@ export const getFileContentFromLinks = async ({ ...@@ -179,13 +179,13 @@ export const getFileContentFromLinks = async ({
sourceId: url, sourceId: url,
customPdfParse customPdfParse
}); });
if (rawTextBuffer) { // if (rawTextBuffer) {
return formatResponseObject({ // return formatResponseObject({
filename: rawTextBuffer.filename || url, // filename: rawTextBuffer.filename || url,
url, // url,
content: rawTextBuffer.text // content: rawTextBuffer.text
}); // });
} // }
try { try {
if (isInternalAddress(url)) { if (isInternalAddress(url)) {
...@@ -207,23 +207,39 @@ export const getFileContentFromLinks = async ({ ...@@ -207,23 +207,39 @@ export const getFileContentFromLinks = async ({
// Get file name // Get file name
const { filename, extension, imageParsePrefix } = (() => { const { filename, extension, imageParsePrefix } = (() => {
const contentDisposition = response.headers['content-disposition'];
if (contentDisposition) {
const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
const matches = filenameRegex.exec(contentDisposition);
if (matches != null && matches[1]) {
const filename = decodeURIComponent(matches[1].replace(/['"]/g, ''));
return {
filename,
extension: path.extname(filename).replace('.', ''),
imageParsePrefix: `` // TODO: 需要根据是否是聊天对话里面的外部链接来决定
};
}
}
if (isChatExternalUrl) { if (isChatExternalUrl) {
const filename = urlObj.pathname.split('/').pop() || 'file'; const contentDisposition = response.headers['content-disposition'] || '';
// Priority: filename* (RFC 5987, UTF-8 encoded) > filename (traditional)
const extractFilename = (contentDisposition: string): string => {
// Try RFC 5987 filename* first (e.g., filename*=UTF-8''encoded-name)
const filenameStarRegex = /filename\*=([^']*)'([^']*)'([^;\n]*)/i;
const starMatches = filenameStarRegex.exec(contentDisposition);
if (starMatches && starMatches[3]) {
const charset = starMatches[1].toLowerCase();
const encodedFilename = starMatches[3];
// Decode percent-encoded UTF-8 filename
try {
return decodeURIComponent(encodedFilename);
} catch (error) {
addLog.warn('Failed to decode filename*', { encodedFilename, error });
}
}
// Fallback to traditional filename parameter
const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/i;
const matches = filenameRegex.exec(contentDisposition);
if (matches && matches[1]) {
return matches[1].replace(/['"]/g, '');
}
return '';
};
const matchFilename = extractFilename(contentDisposition);
const filename = matchFilename || urlObj.pathname.split('/').pop() || 'file';
const extension = path.extname(filename).replace('.', ''); const extension = path.extname(filename).replace('.', '');
return { return {
filename, filename,
extension, extension,
......
...@@ -64,6 +64,7 @@ const SubSchema = new Schema({ ...@@ -64,6 +64,7 @@ const SubSchema = new Schema({
appRegistrationCount: Number, appRegistrationCount: Number,
auditLogStoreDuration: Number, auditLogStoreDuration: Number,
ticketResponseTime: Number, ticketResponseTime: Number,
customDomain: Number,
// stand sub and extra points sub. Plan total points // stand sub and extra points sub. Plan total points
totalPoints: { totalPoints: {
......
...@@ -74,7 +74,8 @@ export const getTeamStandPlan = async ({ teamId }: { teamId: string }) => { ...@@ -74,7 +74,8 @@ export const getTeamStandPlan = async ({ teamId }: { teamId: string }) => {
standard?.appRegistrationCount ?? standardConstants.appRegistrationCount, standard?.appRegistrationCount ?? standardConstants.appRegistrationCount,
auditLogStoreDuration: auditLogStoreDuration:
standard?.auditLogStoreDuration ?? standardConstants.auditLogStoreDuration, standard?.auditLogStoreDuration ?? standardConstants.auditLogStoreDuration,
ticketResponseTime: standard?.ticketResponseTime ?? standardConstants.ticketResponseTime ticketResponseTime: standard?.ticketResponseTime ?? standardConstants.ticketResponseTime,
customDomain: standard?.customDomain ?? standardConstants.customDomain
} }
: undefined : undefined
}; };
...@@ -210,7 +211,8 @@ export const getTeamPlanStatus = async ({ ...@@ -210,7 +211,8 @@ export const getTeamPlanStatus = async ({
auditLogStoreDuration: auditLogStoreDuration:
standardPlan?.auditLogStoreDuration ?? standardConstants.auditLogStoreDuration, standardPlan?.auditLogStoreDuration ?? standardConstants.auditLogStoreDuration,
ticketResponseTime: ticketResponseTime:
standardPlan?.ticketResponseTime ?? standardConstants.ticketResponseTime standardPlan?.ticketResponseTime ?? standardConstants.ticketResponseTime,
customDomain: standardPlan?.customDomain ?? standardConstants.customDomain
} }
: undefined, : undefined,
......
...@@ -61,6 +61,7 @@ export const iconPaths = { ...@@ -61,6 +61,7 @@ export const iconPaths = {
'common/gitFill': () => import('./icons/common/gitFill.svg'), 'common/gitFill': () => import('./icons/common/gitFill.svg'),
'common/gitInlight': () => import('./icons/common/gitInlight.svg'), 'common/gitInlight': () => import('./icons/common/gitInlight.svg'),
'common/gitLight': () => import('./icons/common/gitLight.svg'), 'common/gitLight': () => import('./icons/common/gitLight.svg'),
'common/globalLine': () => import('./icons/common/globalLine.svg'),
'common/googleFill': () => import('./icons/common/googleFill.svg'), 'common/googleFill': () => import('./icons/common/googleFill.svg'),
'common/help': () => import('./icons/common/help.svg'), 'common/help': () => import('./icons/common/help.svg'),
'common/importLight': () => import('./icons/common/importLight.svg'), 'common/importLight': () => import('./icons/common/importLight.svg'),
...@@ -472,6 +473,12 @@ export const iconPaths = { ...@@ -472,6 +473,12 @@ export const iconPaths = {
star: () => import('./icons/star.svg'), star: () => import('./icons/star.svg'),
stop: () => import('./icons/stop.svg'), stop: () => import('./icons/stop.svg'),
'support/account/coupon': () => import('./icons/support/account/coupon.svg'), 'support/account/coupon': () => import('./icons/support/account/coupon.svg'),
'support/account/customDomain/provider/aliyun': () =>
import('./icons/support/account/customDomain/provider/aliyun.svg'),
'support/account/customDomain/provider/tencent': () =>
import('./icons/support/account/customDomain/provider/tencent.svg'),
'support/account/customDomain/provider/volcengine': () =>
import('./icons/support/account/customDomain/provider/volcengine.svg'),
'support/account/laf': () => import('./icons/support/account/laf.svg'), 'support/account/laf': () => import('./icons/support/account/laf.svg'),
'support/account/loginoutLight': () => import('./icons/support/account/loginoutLight.svg'), 'support/account/loginoutLight': () => import('./icons/support/account/loginoutLight.svg'),
'support/account/plans': () => import('./icons/support/account/plans.svg'), 'support/account/plans': () => import('./icons/support/account/plans.svg'),
......
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_30360_927)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 0.833336C15.0626 0.833336 19.1667 4.93739 19.1667 10C19.1667 15.0626 15.0626 19.1667 10 19.1667C4.93739 19.1667 0.833336 15.0626 0.833336 10C0.833336 4.93739 4.93739 0.833336 10 0.833336ZM10 17.5C10.0951 17.5 10.7259 17.3391 11.429 15.7921C11.742 15.1036 12.0067 14.2572 12.1946 13.2943H7.80539C7.99334 14.2572 8.25805 15.1036 8.571 15.7921C9.27415 17.3391 9.90487 17.5 10 17.5ZM7.5715 11.6277C7.52492 11.1056 7.5 10.5614 7.5 10C7.5 9.42872 7.5258 8.87537 7.57397 8.34492H12.426C12.4742 8.87537 12.5 9.42872 12.5 10C12.5 10.5614 12.4751 11.1056 12.4285 11.6277H7.5715ZM13.8895 13.2943C13.6355 14.7449 13.2187 16.0175 12.6883 17.0038C14.4614 16.3228 15.9103 14.9879 16.7396 13.2943H13.8895ZM17.3229 11.6277H14.1012C14.1442 11.0994 14.1667 10.5555 14.1667 10C14.1667 9.43491 14.1434 8.88176 14.0989 8.34492H17.3168C17.4367 8.87739 17.5 9.4313 17.5 10C17.5 10.5589 17.4389 11.1036 17.3229 11.6277ZM5.89882 11.6277H2.67711C2.56115 11.1036 2.5 10.5589 2.5 10C2.5 9.4313 2.5633 8.87739 2.68323 8.34492H5.90108C5.85658 8.88176 5.83334 9.43491 5.83334 10C5.83334 10.5555 5.85579 11.0994 5.89882 11.6277ZM3.26036 13.2943H6.1105C6.36451 14.7449 6.78134 16.0175 7.31173 17.0038C5.53862 16.3228 4.08972 14.9879 3.26036 13.2943ZM7.81077 6.67825H12.1892C12.0016 5.72674 11.739 4.88982 11.429 4.20786C10.7259 2.66093 10.0951 2.5 10 2.5C9.90487 2.5 9.27415 2.66093 8.571 4.20786C8.26102 4.88982 7.99838 5.72674 7.81077 6.67825ZM13.8847 6.67825H16.7261C15.8946 4.99771 14.4518 3.6735 12.6883 2.99618C13.2153 3.97628 13.6302 5.23909 13.8847 6.67825ZM7.31173 2.99618C6.78469 3.97628 6.36977 5.23909 6.11533 6.67825H3.27386C4.10539 4.99771 5.5482 3.6735 7.31173 2.99618Z" fill="#485264"/>
</g>
<defs>
<clipPath id="clip0_30360_927">
<rect width="20" height="20" fill="white"/>
</clipPath>
</defs>
</svg>
<svg viewBox="0 0 91 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M65.1605 15.1515H71.6969V13.1313H65.1605V11.1111H71.4927V0.60606H56.7856V11.1111H63.1178V13.1313H56.5813V15.1515H63.1178V17.3737H56.0707V19.3939H72.3097V17.3737H65.2626V15.1515H65.1605ZM65.3647 2.62626H69.6543V4.84848H65.3647V2.62626ZM65.3647 6.86869H69.6543V8.9899H65.3647V6.86869ZM63.3221 8.9899H59.0325V6.86869H63.3221V8.9899ZM63.3221 4.84848H59.0325V2.62626H63.3221V4.84848ZM10.7239 8.68687H21.4478V11.1111H10.7239V8.68687Z" fill="#FF6A00"/>
<path d="M26.7587 0H19.7116L21.4478 2.42424L26.5544 4.0404C27.4736 4.34343 28.0864 5.25253 28.0864 6.16162V13.8384C28.0864 14.7475 27.4736 15.6566 26.5544 15.9596L21.4478 17.5758L19.7116 20H26.7587C29.7205 20 32.0696 17.6768 32.0696 14.7475V5.35354C32.0696 2.42424 29.7205 0 26.7587 0ZM5.51515 15.9596C4.59596 15.6566 3.98316 14.7475 3.98316 13.8384V6.16162C3.98316 5.25253 4.59596 4.34343 5.51515 4.0404L10.6218 2.42424L12.358 0H5.31089C2.34905 0 0 2.42424 0 5.35354V14.6465C0 17.5758 2.34905 19.899 5.31089 19.899H12.358L10.6218 17.4747L5.51515 15.9596ZM49.7385 4.84848H44.5297V15.4545H49.7385V4.84848ZM47.5937 13.3333H46.4703V6.86869H47.5937V13.3333ZM36.8698 19.3939H38.9125V2.62626H41.0572L39.8316 8.18182V10.202H41.0572V14.6465C41.0572 14.9495 40.853 15.1515 40.5466 15.1515H40.0359V17.1717H41.0572C42.1807 17.1717 43.0999 16.2626 43.0999 15.1515V8.08081H41.8743L43.0999 2.52525V0.50505H36.8698V19.3939Z" fill="#FF6A00"/>
<path d="M44.019 2.62626H51.0662V15.6566C51.0662 16.5657 50.3513 17.3737 49.3299 17.3737H47.7979V19.3939H49.9427C51.679 19.3939 53.211 17.9798 53.211 16.1616V2.62626H53.9259V0.60606H44.019V2.62626ZM75.0673 0.70707H89.7744V2.72727H75.0673V0.70707ZM90.5914 10.303V8.28283H74.3524V10.303H78.4377L75.1694 17.3737V19.3939H88.9573C89.5701 19.3939 89.9786 18.8889 89.9786 18.3838C89.9786 18.1818 89.9786 18.0808 89.8765 17.9798L88.1403 14.1414H85.8933L87.4253 17.3737H77.5185L80.7867 10.303H90.5914Z" fill="#FF6A00"/>
</svg>
<svg viewBox="0 0 77 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M61.9037 7.97203H54.3043V9.01632H56.6884V13.1935H53.8572V14.387H56.6884V21.1002H57.8804V14.387H60.8606V20.9511H63.2447V19.7576H62.2017V7.97203H61.9037ZM57.8804 13.0443V9.01632H60.8606V13.1935H57.8804V13.0443ZM40.5955 17.9674H46.7048V19.1608H40.5955V17.9674ZM36.2743 15.8788C36.2743 17.9674 36.1252 19.4592 35.6782 20.8019C35.6782 20.9511 35.6782 20.9511 35.8272 20.9511H37.0193C37.4663 19.0117 37.4663 17.5198 37.4663 16.3263H39.1054V19.7576H38.0624C37.9133 19.7576 37.9133 19.7576 37.9133 19.9068L38.2114 20.9511H40.2975V7.97203H36.4233L36.2743 15.8788ZM37.6153 15.4312V12.7459H39.2544V15.5804H37.6153V15.4312ZM39.1054 9.01632V11.7016H37.4663V9.01632H39.1054Z" fill="#56606F"/>
<path d="M47.152 12.5967H48.6421V11.5524H44.3208C44.3208 11.2541 44.3208 10.8065 44.6188 10.5082H48.4931V9.46387H47.45C47.599 8.86713 47.897 8.12121 47.897 8.12121C47.897 7.97203 47.897 7.97203 47.748 7.97203H46.854L46.4069 9.46387H45.0659C45.2149 8.86713 45.2149 8.2704 45.3639 7.52447C45.3639 7.37529 45.3639 7.37529 45.2149 7.37529H44.3208C44.1718 8.12121 44.1718 8.86713 43.8738 9.46387H43.1288L42.6817 7.97203H41.7877C41.6387 7.97203 41.6387 7.97203 41.6387 8.12121C41.6387 8.27039 41.7877 8.86713 42.0857 9.46387H41.0426V10.5082H43.5758C43.4268 10.8065 43.4268 11.2541 43.1288 11.5524H40.7446V12.5967H42.5327C41.7877 13.641 41.0426 14.0886 40.7446 14.2378V15.4312C40.7446 15.5804 40.8936 15.5804 40.8936 15.4312C41.3407 15.5804 41.6387 15.1329 41.9367 14.8345H46.2579V16.4755H42.8307L42.9798 15.5804C42.9798 15.4312 42.9798 15.4312 42.8307 15.4312H41.7877L41.6387 17.3706C41.6387 17.5198 41.6387 17.5198 41.7877 17.5198H47.599V20.3543H45.0659C44.9169 20.3543 44.9169 20.3543 44.9169 20.5035L45.2149 21.5478H48.7911V16.6247H47.45V14.0886C48.046 14.5361 48.4931 14.9837 49.0891 15.1329C49.2381 15.282 49.2381 15.282 49.2381 15.1329V14.0886C48.6421 13.9394 47.897 13.4918 47.152 12.5967ZM45.8109 12.5967C45.9599 13.0443 46.4069 13.4918 46.705 13.7902H42.9798C43.2778 13.4918 43.5758 13.0443 43.8738 12.5967H45.8109ZM55.0494 17.2214L52.9633 18.8625V11.5524H50.1321V12.7459H51.7712V19.7576C51.3242 20.0559 51.0262 20.2051 51.0262 20.2051L51.7712 21.1002L55.1984 18.5641L55.0494 17.2214ZM51.9202 10.359H53.1123C53.2613 10.359 53.2613 10.2098 53.2613 10.2098L51.4732 7.52447H50.2812L50.1321 7.67366C50.2812 7.82284 51.9202 10.359 51.9202 10.359ZM65.629 8.12121H75.3145V9.31468H65.629V8.12121ZM69.2052 14.0886H76.8046V12.8951H64.4369V14.0886H67.8641C67.1191 16.028 65.331 20.6527 65.331 20.8019C65.331 20.951 65.331 20.951 65.48 20.951H76.2086C76.3576 20.951 76.3576 20.8019 76.3576 20.8019L74.7185 16.6247H73.5264C73.3774 16.6247 73.3774 16.7739 73.3774 16.7739C73.3774 16.9231 74.5695 19.7576 74.5695 19.7576H67.4171C67.2681 19.6084 69.2052 14.0886 69.2052 14.0886Z" fill="#56606F"/>
<path d="M28.2277 19.4592C27.7807 19.9068 26.7376 20.8019 24.8005 20.8019H13.7739C17.2011 17.5198 19.8832 14.6853 20.1813 14.5361C20.4793 14.2378 21.0753 13.641 21.6713 13.1935C22.8634 11.8508 23.9065 11.7016 24.9495 11.7016C26.2906 11.7016 27.3336 12.1492 28.2277 13.0443C30.0158 14.6853 30.0158 17.8182 28.2277 19.4592ZM30.4628 10.9557C29.1217 9.61305 27.1846 8.56876 25.2475 8.56876C23.3104 8.56876 21.8203 9.31468 20.4793 10.2098C20.0322 10.6573 19.1382 11.2541 18.5422 12.1492C17.7971 12.5967 6.77051 23.7855 6.77051 23.7855H23.0124C24.0555 23.7855 24.9495 23.7855 25.5455 23.6364C27.1846 23.4872 28.9727 22.7413 30.3138 21.5478C33.145 18.5641 33.145 13.7902 30.4628 10.9557Z" fill="#00A3FF"/>
<path d="M12.135 10.2098C10.6449 9.1655 9.3038 8.71795 7.5157 8.71795C5.42958 8.71795 3.49248 9.61305 2.30041 11.1049C-0.530744 14.0886 -0.530744 18.8625 2.44942 21.8462C3.64149 23.0396 4.98256 23.6364 6.47264 23.6364L9.45281 20.8019H7.81372C6.17463 20.6527 5.13157 20.0559 4.38653 19.4592C2.59843 17.669 2.59843 14.8345 4.38653 12.8951C5.28058 12 6.17462 11.5524 7.66471 11.5524C8.55876 11.5524 9.45281 11.7016 10.6449 12.8951C11.0919 13.4918 12.433 14.3869 12.88 14.9837H13.029L14.8171 12.8951C14.2211 12.1492 12.731 10.9557 12.135 10.2098Z" fill="#00C8DC"/>
<path d="M25.6948 6.77855C24.3537 2.89977 20.4795 0.214451 16.3073 0.214451C11.241 0.214451 7.21778 3.94405 6.32373 8.71795C6.62175 8.71795 7.06877 8.56876 7.5158 8.56876C7.96282 8.56876 8.70786 8.71795 9.15489 8.71795C9.89993 5.4359 12.7311 3.19813 16.0093 3.19813C18.8404 3.19813 21.3736 4.83916 22.5656 7.37529C22.5656 7.37529 22.7146 7.52447 22.7146 7.37529C23.7577 7.22611 24.8007 6.77855 25.6948 6.77855C25.6948 7.07692 25.6948 7.07692 25.6948 6.77855Z" fill="#006EFF"/>
</svg>
<svg viewBox="0 0 99 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M86.3129 8.06573C86.3534 8.06573 86.3881 8.09155 86.3997 8.12692L86.4049 8.15498V8.40814H88.2764V8.15498C88.2764 8.11569 88.3024 8.08201 88.3395 8.07079L88.3684 8.06573H90.1524C90.193 8.06573 90.2277 8.09155 90.2393 8.12692L90.2451 8.15498L90.2439 8.40814H91.3391C91.3796 8.40814 91.4143 8.43452 91.4265 8.46988L91.4317 8.49795V9.78785C91.4316 9.80667 91.4254 9.82497 91.414 9.84018C91.4025 9.85539 91.3865 9.86673 91.368 9.87261L91.3391 9.8771H90.2439L90.2445 9.95961C90.2443 9.97826 90.2381 9.99639 90.2268 10.0115C90.2155 10.0266 90.1996 10.0379 90.1814 10.0438L90.1524 10.0489H88.3684C88.3491 10.0487 88.3303 10.0427 88.3147 10.0316C88.2991 10.0205 88.2875 10.0049 88.2816 9.98712L88.2764 9.95905V9.87654H86.4043V9.95961C86.4042 9.9782 86.3981 9.99627 86.3869 10.0114C86.3757 10.0264 86.3599 10.0378 86.3418 10.0438L86.3129 10.0489H84.5289C84.5094 10.0488 84.4905 10.0428 84.4748 10.0318C84.4591 10.0207 84.4474 10.005 84.4414 9.98712L84.4368 9.95905V9.87654L83.3416 9.8771C83.3224 9.87691 83.3037 9.87091 83.2881 9.85995C83.2726 9.84898 83.2609 9.8336 83.2548 9.81592L83.249 9.78785V8.49795C83.249 8.45866 83.2762 8.42498 83.3127 8.41319L83.3416 8.4087H84.4362V8.15498C84.4362 8.11569 84.4629 8.08201 84.4999 8.07079L84.5289 8.06573H86.3129ZM94.7022 8.04497C94.7146 8.04497 94.7269 8.04736 94.7383 8.05198C94.7497 8.05661 94.7601 8.06338 94.7688 8.07191C94.7772 8.08045 94.7837 8.09057 94.7879 8.10166C94.7925 8.1127 94.7947 8.12456 94.7943 8.13646C94.7928 8.35423 94.7684 8.57127 94.7214 8.78422H98.5916C98.6165 8.78422 98.6391 8.79376 98.657 8.81004C98.6744 8.82632 98.6836 8.84933 98.6836 8.87347V10.2745C98.6837 10.2982 98.6742 10.321 98.657 10.3379C98.6394 10.3544 98.616 10.3636 98.5916 10.3638H97.9907L97.9902 10.5759L97.9844 10.7819L97.9792 10.892L97.9618 11.1232C97.8871 11.9607 97.6301 13.0631 96.8105 14.0269C97.4761 14.2447 98.1974 14.3401 98.8966 14.3401C98.9173 14.3401 98.9375 14.3466 98.9539 14.3588C98.9703 14.371 98.9822 14.3881 98.9875 14.4075L98.991 14.4311V15.9477C98.99 15.9724 98.9792 15.9957 98.9609 16.0128C98.9431 16.0304 98.9191 16.0409 98.8938 16.042C97.5028 16.0443 96.2646 15.758 95.247 15.2309C94.2971 15.7047 93.1168 15.9573 91.72 15.9842C91.7002 15.9843 91.6808 15.9788 91.6643 15.9682C91.6478 15.9576 91.635 15.9424 91.6274 15.9247L91.6222 15.9068L91.621 15.8882L91.6007 14.3351C91.6007 14.3103 91.6107 14.2866 91.6285 14.2688C91.637 14.2596 91.6475 14.2523 91.6592 14.2475C91.6709 14.2426 91.6835 14.2403 91.6963 14.2408C92.4268 14.2256 93.071 14.1397 93.625 13.9842C92.9998 13.2809 92.5738 12.4182 92.392 11.428C92.3896 11.4146 92.3902 11.4008 92.3938 11.3876C92.3979 11.3746 92.4045 11.3626 92.4135 11.3522C92.4224 11.3418 92.4335 11.3332 92.4459 11.327L92.4661 11.3202L92.4864 11.3186H94.084C94.1049 11.3186 94.1257 11.3259 94.1425 11.3382C94.1599 11.3517 94.1714 11.3691 94.1761 11.3893C94.3602 12.1218 94.7295 12.6955 95.2151 13.135L95.2753 13.0766C95.9856 12.362 96.1529 11.4303 96.1853 10.7528L96.1911 10.5484V10.3638L93.8392 10.3643C93.27 10.9231 92.5086 11.2592 91.7003 11.3085C91.6875 11.3095 91.6746 11.3076 91.6627 11.3028C91.6509 11.2989 91.6402 11.2924 91.6314 11.2838C91.6218 11.2752 91.6143 11.2646 91.6094 11.2529L91.6036 11.236L91.6013 11.2175V9.66268C91.6013 9.64023 91.6106 9.61946 91.6251 9.60318C91.6395 9.58674 91.6597 9.57613 91.6818 9.57343C92.0423 9.52252 92.3728 9.35006 92.6152 9.08635C92.8576 8.82263 92.9965 8.48453 93.0074 8.13141C93.0088 8.10839 93.0191 8.08673 93.0363 8.07079C93.0476 8.06003 93.0615 8.05229 93.0768 8.04833L93.1 8.04497H94.7022ZM85.5413 10.2319C85.5543 10.232 85.5671 10.2347 85.579 10.2397C85.5909 10.2447 85.6017 10.252 85.6107 10.261C85.6195 10.2703 85.6262 10.2812 85.6305 10.2931C85.6347 10.305 85.6365 10.3175 85.6356 10.3301C85.625 10.4446 85.6084 10.5585 85.5858 10.6714L90.9362 10.6708C90.9605 10.6708 90.9843 10.6753 91.0074 10.6843C91.0298 10.6934 91.05 10.7067 91.067 10.7234C91.084 10.7402 91.0974 10.7601 91.1064 10.7819L91.1168 10.8156L91.1203 10.851V15.3084C91.1202 15.4884 91.0518 15.662 90.9282 15.7963C90.8046 15.9306 90.6344 16.016 90.45 16.0364L90.3643 16.0409H88.4321C88.4181 16.041 88.4043 16.0381 88.3916 16.0325C88.3794 16.026 88.3687 16.0175 88.3597 16.0072C88.351 15.9966 88.3451 15.9841 88.3424 15.9707L88.34 15.9505L88.3418 15.9309L88.6509 14.6365C88.656 14.617 88.6676 14.5996 88.6839 14.5871C88.6948 14.579 88.7075 14.5732 88.7209 14.5703L88.7412 14.5675H89.2911C89.3113 14.5677 89.331 14.5614 89.3471 14.5495C89.3631 14.5375 89.3746 14.5208 89.3797 14.5018L89.3831 14.4782V12.0286C89.3831 12.017 89.3812 12.0054 89.3773 11.9944C89.3722 11.9837 89.3653 11.9739 89.3571 11.9652C89.3486 11.9566 89.3381 11.95 89.3264 11.9461L89.309 11.9411L89.2911 11.9405H84.9821C84.8417 12.1047 84.6805 12.2511 84.5022 12.3761L88.0332 12.375C88.0576 12.3746 88.0817 12.3792 88.1041 12.3884C88.1265 12.3976 88.1468 12.4111 88.1635 12.4283C88.1814 12.4446 88.1942 12.4642 88.204 12.4867L88.2138 12.5203L88.2173 12.5557V15.085C88.217 15.1324 88.1977 15.1779 88.1635 15.2118C88.1374 15.2363 88.1047 15.253 88.0691 15.2601L88.0332 15.2635H83.8771C83.8527 15.2637 83.8286 15.2595 83.8059 15.2511C83.7604 15.2326 83.7241 15.1979 83.7046 15.154L83.6947 15.1204L83.6912 15.085V12.7628C83.5002 12.819 83.2999 12.8566 83.091 12.8751C83.0779 12.8768 83.0647 12.8757 83.0522 12.8717C83.0398 12.8676 83.0284 12.8611 83.0186 12.8526C83.0095 12.8438 83.0022 12.8333 82.9972 12.8218L82.9914 12.8032L82.9902 12.7842V11.2854C82.9902 11.263 83.0001 11.2417 83.0157 11.2243C83.0315 11.207 83.0534 11.1964 83.0771 11.1945L83.157 11.1889C83.4215 11.1586 83.796 11.0115 83.8962 10.3116C83.8984 10.2898 83.9092 10.2697 83.9263 10.2554C83.9373 10.2453 83.9509 10.2381 83.9656 10.2347L83.9888 10.2319H85.5413ZM86.6573 13.3808H85.2408C85.2209 13.3816 85.2016 13.3883 85.1859 13.4002C85.1701 13.4121 85.1585 13.4285 85.1529 13.4471L85.1488 13.4706V14.1588C85.1498 14.182 85.1597 14.2041 85.1766 14.2206C85.1882 14.2318 85.2026 14.2396 85.2177 14.2441L85.2408 14.2481H86.6573C86.6695 14.2482 86.6817 14.2459 86.6929 14.2413C86.7042 14.2366 86.7143 14.2298 86.7227 14.2211C86.7314 14.2125 86.7385 14.2024 86.7436 14.1914L86.7482 14.174L86.7493 14.1571V13.4706C86.7492 13.447 86.7397 13.4243 86.7227 13.4072C86.7109 13.3965 86.6966 13.3888 86.681 13.3848L86.6573 13.3814V13.3808ZM98.2825 16.4209C98.323 16.4209 98.3577 16.4462 98.3693 16.4821L98.3751 16.5102V17.6356C98.3749 17.6543 98.3686 17.6725 98.3572 17.6876C98.3458 17.7027 98.3298 17.714 98.3114 17.7198L98.2825 17.7249H92.1205V18.3625H98.0561C98.0967 18.3625 98.1314 18.3883 98.143 18.4243L98.1482 18.4523V19.5772C98.1479 19.5959 98.1417 19.6141 98.1303 19.6292C98.1188 19.6443 98.1028 19.6555 98.0845 19.6614L98.0556 19.6664H92.1205V20.2906H98.8179C98.8584 20.2906 98.8932 20.3164 98.9053 20.3518L98.9105 20.3799V21.5053C98.9103 21.524 98.904 21.5422 98.8926 21.5573C98.8812 21.5724 98.8652 21.5837 98.8469 21.5895L98.8179 21.5946H92.1205V23.0719C92.1205 23.2347 92.0632 23.3919 91.9602 23.5182L91.9046 23.5788C91.7866 23.6941 91.6317 23.7669 91.4653 23.7854L91.3819 23.7899H88.05C88.0355 23.79 88.0211 23.7871 88.0078 23.7815C87.9943 23.775 87.9825 23.7658 87.973 23.7545C87.9644 23.743 87.9585 23.7298 87.9557 23.7158L87.9545 23.6944L87.958 23.6737L88.2787 22.5297C88.2875 22.4975 88.3052 22.4682 88.3301 22.4451C88.3549 22.4221 88.3858 22.4061 88.4193 22.3989L88.4599 22.395H89.7467C89.771 22.3948 89.7952 22.3902 89.8179 22.3815C89.8404 22.3725 89.861 22.3596 89.8786 22.3434C89.8956 22.3266 89.9093 22.307 89.9192 22.2855L89.9296 22.2519L89.9331 22.2165V21.594H83.238C83.2187 21.594 83.2 21.5881 83.1843 21.5772C83.1686 21.5664 83.1568 21.551 83.1506 21.5334L83.1454 21.5053V20.3804C83.1454 20.3406 83.172 20.3075 83.2091 20.2957L83.238 20.2912H89.9325V19.6653L83.9998 19.6659C83.9805 19.6657 83.9618 19.6597 83.9463 19.6487C83.9307 19.6378 83.9191 19.6224 83.9129 19.6047L83.9072 19.5766V18.4518C83.9072 18.4119 83.9344 18.3788 83.9708 18.367L83.9998 18.3625H89.9331V17.7237H83.7705C83.7514 17.7236 83.7327 17.7177 83.7172 17.7068C83.7016 17.696 83.6899 17.6807 83.6837 17.6631L83.6785 17.635V16.5096C83.6785 16.4703 83.7051 16.4366 83.7422 16.4254L83.7711 16.4204H98.2825V16.4209ZM79.6693 8.06573H77.9044C77.855 8.06573 77.8076 8.08468 77.7726 8.11844C77.7376 8.1522 77.7177 8.19803 77.7174 8.24592V23.6058C77.7174 23.7051 77.8014 23.7859 77.9044 23.7859H79.6699C79.7191 23.7858 79.7662 23.7667 79.8009 23.733C79.8357 23.6992 79.8552 23.6535 79.8552 23.6058V8.24592C79.8552 8.19823 79.8357 8.15248 79.8009 8.11871C79.7662 8.08493 79.7191 8.06588 79.6699 8.06573H79.6693ZM66.6329 8.60348V10.2296C66.6331 10.2773 66.6527 10.3231 66.6875 10.3569C66.7223 10.3906 66.7695 10.4096 66.8187 10.4098H72.587C72.6108 10.4101 72.6334 10.4194 72.6501 10.4358C72.6668 10.4522 72.6762 10.4743 72.6762 10.4974V12.3272C72.677 12.3388 72.675 12.3503 72.6704 12.3609C72.6662 12.3717 72.6599 12.3816 72.6519 12.3901C72.6429 12.3979 72.6327 12.4043 72.6218 12.4092C72.6108 12.4138 72.599 12.4161 72.587 12.4159H66.8187C66.7694 12.4162 66.7223 12.4354 66.6875 12.4692C66.6527 12.5031 66.6331 12.5489 66.6329 12.5967V18.0448C66.6329 18.0689 66.6375 18.092 66.6468 18.1138C66.6565 18.1356 66.6702 18.1554 66.6873 18.1722C66.7044 18.1893 66.725 18.2028 66.7477 18.2119C66.7704 18.221 66.7947 18.2254 66.8193 18.225H72.587C72.6108 18.2261 72.6333 18.2362 72.6507 18.2519C72.6675 18.2693 72.6762 18.2912 72.6762 18.3142V21.3526C72.6769 21.3762 72.6727 21.3997 72.6637 21.4216C72.6547 21.4435 72.6413 21.4634 72.6241 21.4801C72.6065 21.4967 72.5859 21.51 72.5633 21.5193C72.541 21.528 72.5173 21.5326 72.4933 21.5328H68.245C68.2032 21.5327 68.1626 21.5461 68.1296 21.5711C68.0966 21.596 68.0733 21.631 68.0633 21.6703L67.65 23.4082C67.6454 23.428 67.6479 23.4487 67.6569 23.467C67.6659 23.4853 67.681 23.5002 67.6997 23.5092C67.7125 23.5148 67.7264 23.5176 67.7403 23.5176H74.1656C74.313 23.5175 74.4544 23.4607 74.5588 23.3597C74.6631 23.2587 74.722 23.1217 74.7224 22.9788V16.4136C74.7224 16.3658 74.7029 16.3199 74.668 16.286C74.6332 16.2521 74.586 16.233 74.5366 16.2329H68.7729C68.7609 16.2328 68.749 16.231 68.7376 16.2273C68.7267 16.2224 68.7165 16.216 68.7075 16.2082C68.6988 16.1997 68.6921 16.1896 68.6879 16.1784C68.683 16.1674 68.6807 16.1556 68.6809 16.1436V14.4833C68.6809 14.4594 68.6907 14.4366 68.708 14.4198C68.7254 14.4029 68.749 14.3934 68.7735 14.3934H74.5412C74.5904 14.3933 74.6376 14.3744 74.6725 14.3407C74.7074 14.3071 74.7272 14.2615 74.7276 14.2138V8.60291C74.7267 8.5553 74.7068 8.50989 74.672 8.47627C74.6372 8.44265 74.5904 8.42345 74.5412 8.42273H66.8193C66.77 8.42288 66.7228 8.44189 66.6879 8.47563C66.653 8.50938 66.6332 8.55512 66.6329 8.60291V8.60348ZM57.2612 8.00455C57.3486 8.00455 57.4216 8.06293 57.4407 8.14207L57.4453 8.18305V21.6928H61.3555V10.0438C61.3555 9.95681 61.4191 9.88384 61.5048 9.867L61.5424 9.86363H63.2182C63.308 9.86363 63.3832 9.92537 63.4006 10.0073L63.404 10.0438V23.4946C63.404 23.5816 63.3404 23.6552 63.2553 23.672L63.2182 23.6759L62.963 23.6776H49.8814L49.8582 23.6759H49.6336C49.5907 23.6758 49.5491 23.6614 49.5159 23.635C49.4826 23.6086 49.4598 23.5719 49.4513 23.5311L49.4472 23.4946V10.0438C49.4472 9.95681 49.5109 9.88384 49.596 9.867L49.6336 9.86363H51.3094C51.3997 9.86363 51.4744 9.92537 51.4917 10.0073L51.4958 10.0438V21.6928H55.3973V8.18305C55.3973 8.09829 55.4575 8.02756 55.5385 8.00904L55.5808 8.00455H57.2612ZM39.4058 7.75926C39.4483 7.75943 39.4894 7.77374 39.5223 7.7998C39.5551 7.82585 39.5778 7.86207 39.5864 7.90239L39.5899 7.93775V13.9483C39.5899 14.0903 39.587 14.2301 39.5824 14.3676C39.7404 18.6196 43.5267 21.461 45.8792 21.695L45.9903 21.704C46.0378 21.7051 46.0829 21.7254 46.1171 21.7579C46.1495 21.7916 46.1686 21.8359 46.1692 21.8814V23.5339C46.1693 23.5581 46.1642 23.582 46.1544 23.6042C46.1445 23.6265 46.13 23.6465 46.1119 23.663C46.0751 23.696 46.0265 23.7137 45.9764 23.7124C44.1675 23.6069 42.1125 22.6605 40.5624 21.1966C39.7342 20.4212 39.0507 19.5127 38.5421 18.5113C38.0338 19.5128 37.3503 20.4214 36.5219 21.1966C34.9683 22.6605 32.9157 23.6069 31.1056 23.7124C31.0807 23.7133 31.056 23.7095 31.0326 23.7012C30.9865 23.6828 30.9496 23.6478 30.9296 23.6035L30.918 23.5698L30.9146 23.5339V21.882C30.9146 21.8359 30.9325 21.7916 30.9666 21.7585C30.9906 21.7335 31.0216 21.716 31.0558 21.708L31.0905 21.704C33.443 21.5508 37.3387 18.687 37.5019 14.3671C37.4968 14.2275 37.4943 14.0879 37.4944 13.9483V7.93775C37.4944 7.89004 37.5141 7.84458 37.5477 7.8109C37.5825 7.7776 37.6296 7.75904 37.6785 7.75926H39.4058ZM33.7249 10.0489C33.7747 10.0489 33.821 10.0668 33.8569 10.1005C33.8916 10.1335 33.9121 10.178 33.9142 10.2251L33.9472 10.8622L33.9894 11.4853L34.0242 11.9276L34.0838 12.5956L34.1394 13.1563L34.2054 13.7681L34.2551 14.2037L34.3402 14.9003L34.4381 15.6485C34.4417 15.6744 34.44 15.7008 34.4328 15.726C34.4249 15.7512 34.4116 15.7744 34.3939 15.7943C34.3761 15.8142 34.3543 15.8303 34.3298 15.8416C34.3054 15.8529 34.2787 15.8586 34.2517 15.8585H32.3026C32.2564 15.8578 32.2119 15.8413 32.177 15.8119C32.1423 15.7819 32.1197 15.7409 32.1134 15.6963L31.9877 14.7027L31.9079 14.019L31.8396 13.3864L31.7643 12.6231L31.7064 11.9517L31.6619 11.3713L31.6242 10.7724L31.5993 10.2391C31.5976 10.2145 31.6016 10.1897 31.6109 10.1667C31.6201 10.1432 31.6345 10.1219 31.6532 10.1044C31.6893 10.069 31.7384 10.0488 31.7898 10.0483H33.7249V10.0489ZM45.3009 10.0489C45.3261 10.0482 45.3513 10.0527 45.3746 10.0621C45.3979 10.0714 45.419 10.0855 45.4363 10.1033C45.4542 10.1207 45.4681 10.1415 45.4773 10.1644C45.4865 10.1873 45.4907 10.2118 45.4896 10.2363L45.4549 10.874L45.4242 11.3612L45.368 12.096L45.324 12.604L45.2708 13.1636L45.2071 13.7738L45.1319 14.4355L45.045 15.1484L44.9802 15.653C44.9743 15.6971 44.9521 15.7376 44.9177 15.767C44.8953 15.7869 44.8683 15.8013 44.839 15.8091L44.7938 15.8147H42.8587C42.8318 15.8139 42.8053 15.8082 42.7805 15.7979C42.7566 15.7864 42.7353 15.7704 42.7178 15.7508C42.7003 15.7313 42.687 15.7085 42.6787 15.6839C42.6709 15.659 42.6685 15.6328 42.6717 15.607L42.802 14.5994L42.8842 13.9062L42.9328 13.4746L42.9959 12.8717L43.0636 12.1516L43.1151 11.5268L43.1527 10.9969L43.1828 10.4693L43.1933 10.2229C43.1951 10.1818 43.2112 10.1425 43.2391 10.1115C43.267 10.0805 43.3049 10.0596 43.3467 10.0522L43.3825 10.0494H45.3009V10.0489Z" fill="#4B4B4B"/>
<path d="M20.5576 11.0895L17.2842 23.5053C17.2759 23.5391 17.2761 23.5743 17.2848 23.608C17.2975 23.6594 17.328 23.7051 17.3711 23.7373C17.4143 23.7696 17.4676 23.7866 17.5221 23.7854H24.0673C24.1034 23.7862 24.1392 23.779 24.172 23.7645C24.2049 23.7499 24.2339 23.7283 24.2568 23.7012C24.2798 23.6742 24.2961 23.6425 24.3046 23.6084C24.3131 23.5744 24.3135 23.5389 24.3057 23.5047L21.0167 11.0895C21.0054 11.0539 20.9855 11.0214 20.9586 10.9948C20.9317 10.9682 20.8986 10.9481 20.8621 10.9364C20.8256 10.9247 20.7868 10.9217 20.7488 10.9275C20.7108 10.9333 20.6749 10.9479 20.6439 10.97C20.6021 10.9994 20.5715 11.0414 20.5571 11.0895M2.54131 13.9029L0.00591647 23.5053C-0.00175102 23.5394 -0.00129007 23.5749 0.0072643 23.6088C0.0158187 23.6428 0.0322416 23.6744 0.0552861 23.7013C0.0783306 23.7282 0.107391 23.7496 0.14026 23.764C0.17313 23.7784 0.208944 23.7853 0.244984 23.7843H5.29667C5.33299 23.7851 5.36903 23.7779 5.40211 23.7633C5.43518 23.7488 5.46444 23.7271 5.48769 23.7001C5.51025 23.6733 5.5261 23.6417 5.534 23.608C5.54285 23.5743 5.54325 23.5391 5.53516 23.5053L2.99745 13.9029C2.98243 13.8558 2.95193 13.8148 2.91062 13.7861C2.86969 13.7573 2.82052 13.7416 2.76996 13.7412C2.7188 13.7413 2.66897 13.757 2.62756 13.7861C2.58646 13.8149 2.55618 13.8559 2.54131 13.9029Z" fill="#00E5E5"/>
<path d="M7.01294 5.95294L2.39308 23.5047C2.38525 23.5378 2.38525 23.5721 2.39307 23.6052C2.40089 23.6382 2.41633 23.6691 2.43823 23.6956C2.4598 23.7227 2.48739 23.7447 2.51896 23.7601C2.55053 23.7754 2.58526 23.7837 2.62057 23.7843H11.8609C11.8969 23.7852 11.9328 23.7782 11.9657 23.7638C11.9985 23.7493 12.0276 23.7278 12.0507 23.7009C12.0738 23.674 12.0902 23.6424 12.0989 23.6084C12.1075 23.5744 12.1081 23.5389 12.1005 23.5047L7.46907 5.95238C7.45409 5.9056 7.42382 5.86481 7.38282 5.83619C7.3418 5.80709 7.29241 5.79119 7.24158 5.79072C7.19043 5.79079 7.1406 5.8065 7.09918 5.83563C7.05809 5.8644 7.02781 5.90539 7.01294 5.95238" fill="#006EFF"/>
<path d="M14.9826 0.371784C14.9676 0.325002 14.9373 0.284217 14.8963 0.255592C14.855 0.226269 14.8052 0.210356 14.7539 0.210125C14.7032 0.210428 14.6538 0.226131 14.6127 0.25503C14.5716 0.283801 14.5413 0.324796 14.5265 0.371784L8.21113 23.5047C8.20334 23.539 8.20374 23.5745 8.21228 23.6086C8.22081 23.6427 8.23727 23.6744 8.26039 23.7014C8.28352 23.7283 8.31269 23.7498 8.34568 23.7642C8.37868 23.7786 8.41462 23.7854 8.45077 23.7843H21.0704C21.1068 23.7851 21.1428 23.7779 21.1759 23.7633C21.2089 23.7488 21.2382 23.7271 21.2615 23.7001C21.2841 23.6729 21.3003 23.6413 21.3089 23.6074C21.3169 23.5736 21.3169 23.5385 21.3089 23.5047L14.9826 0.371784Z" fill="#006EFF"/>
<path d="M10.3444 8.50581L6.33814 23.503C6.32915 23.5372 6.32871 23.573 6.33686 23.6073C6.34502 23.6417 6.36154 23.6737 6.38503 23.7006C6.40809 23.728 6.43729 23.7498 6.4704 23.7645C6.50351 23.7792 6.53965 23.7863 6.57605 23.7854H14.5701C14.6066 23.7861 14.6428 23.7789 14.6761 23.7642C14.7094 23.7496 14.7388 23.7278 14.7622 23.7006C14.7849 23.6732 14.8008 23.6412 14.809 23.607C14.8171 23.5728 14.8171 23.5373 14.8091 23.503L10.8 8.50637C10.7849 8.4593 10.7545 8.4183 10.7131 8.38962C10.6719 8.36061 10.6223 8.3449 10.5713 8.34471C10.5206 8.34501 10.4712 8.36072 10.4301 8.38962C10.389 8.41839 10.3587 8.45938 10.3438 8.50637" fill="#00E5E5"/>
</svg>
...@@ -9,7 +9,26 @@ ...@@ -9,7 +9,26 @@
"confirm_logout": "Confirm to log out?", "confirm_logout": "Confirm to log out?",
"create_channel": "Add new channel", "create_channel": "Add new channel",
"create_model": "Add new model", "create_model": "Add new model",
"custom_domain": "Custom Domain",
"custom_model": "custom model", "custom_model": "custom model",
"custom_domain.domain": "Domain",
"custom_domain.provider": "Domain Provider",
"custom_domain.registration_hint": "Please prepare your own domain and complete the ICP filing through <bold>{{provider}}</bold>, then fill in the domain in the input box below",
"custom_domain.provider.aliyun": "Alibaba Cloud",
"custom_domain.provider.tencent": "Tencent Cloud",
"custom_domain.provider.volcengine": "Volcano Engine",
"custom_domain.DNS_record": "DNS Record",
"custom_domain.DNS_record.type": "Type",
"custom_domain.DNS_resolve_hint": "Please go to your domain service provider and add a CNAME resolution for this domain to <bold>{{domain}}</bold>. After the resolution takes effect, you can bind the custom domain.",
"custom_domain.dns_resolved": "Verified",
"custom_domain.dns_resolving": "Verifying",
"custom_domain.delete_confirm": "Confirm to delete this custom domain?",
"custom_domain.status.active": "Active",
"custom_domain.status.inactive": "Inactive",
"custom_domain.domain_verify": "Domain Verification",
"custom_domain.domain_verify.path": "File Path",
"custom_domain.domain_verify.content": "File Content",
"custom_domain.domain_verify.desc": "After saving, visiting {{domain}}/{{path}} will return {{content}}",
"default_model": "Default model", "default_model": "Default model",
"default_model_config": "Default model configuration", "default_model_config": "Default model configuration",
"language": "Language and time zone", "language": "Language and time zone",
...@@ -113,5 +132,7 @@ ...@@ -113,5 +132,7 @@
"app_registration_count": "App registration count", "app_registration_count": "App registration count",
"audit_log_store_duration": "Audit log storage duration", "audit_log_store_duration": "Audit log storage duration",
"ticket_response_time": "Ticket response time", "ticket_response_time": "Ticket response time",
"custom_config_details": "Custom configuration details" "custom_config_details": "Custom configuration details",
"upgrade_to_use_custom_domain": "Current plan does not support custom domains, please upgrade first",
"upgrade_plan": "Upgrade plan"
} }
...@@ -776,11 +776,11 @@ ...@@ -776,11 +776,11 @@
"create_success": "Created Successfully", "create_success": "Created Successfully",
"create_time": "Creation Time", "create_time": "Creation Time",
"cron_job_run_app": "Scheduled Task", "cron_job_run_app": "Scheduled Task",
"custom_plan_price": "Custom Billing",
"custom_plan_feature_1": "Priority Deep Technical Support", "custom_plan_feature_1": "Priority Deep Technical Support",
"custom_plan_feature_2": "Dedicated Account Manager", "custom_plan_feature_2": "Dedicated Account Manager",
"custom_plan_feature_3": "Flexible Resource Configuration", "custom_plan_feature_3": "Flexible Resource Configuration",
"custom_plan_feature_4": "Secure and Controllable", "custom_plan_feature_4": "Secure and Controllable",
"custom_plan_price": "Custom Billing",
"custom_title": "Custom Title", "custom_title": "Custom Title",
"data_index_custom": "Custom index", "data_index_custom": "Custom index",
"data_index_default": "Default index", "data_index_default": "Default index",
...@@ -1216,6 +1216,7 @@ ...@@ -1216,6 +1216,7 @@
"support.wallet.subscription.Upgrade plan": "Upgrade Package", "support.wallet.subscription.Upgrade plan": "Upgrade Package",
"support.wallet.subscription.ai_model": "AI Language Model", "support.wallet.subscription.ai_model": "AI Language Model",
"support.wallet.subscription.function.Audit log store duration": "{{amount}} days team operation log records", "support.wallet.subscription.function.Audit log store duration": "{{amount}} days team operation log records",
"support.wallet.subscription.function.Custom domain": "{{amount}} Custom domains",
"support.wallet.subscription.function.History store": "{{amount}} Days of Chat History Retention", "support.wallet.subscription.function.History store": "{{amount}} Days of Chat History Retention",
"support.wallet.subscription.function.Max app": "{{amount}} Agent limit", "support.wallet.subscription.function.Max app": "{{amount}} Agent limit",
"support.wallet.subscription.function.Max dataset": "{{amount}} Dataset limit", "support.wallet.subscription.function.Max dataset": "{{amount}} Dataset limit",
...@@ -1224,6 +1225,7 @@ ...@@ -1224,6 +1225,7 @@
"support.wallet.subscription.function.Points": "{{amount}} points", "support.wallet.subscription.function.Points": "{{amount}} points",
"support.wallet.subscription.function.Requests per minute": "{{amount}} QPM", "support.wallet.subscription.function.Requests per minute": "{{amount}} QPM",
"support.wallet.subscription.function.Website sync per dataset": "Single knowledge base {{amount}} web pages synchronized", "support.wallet.subscription.function.Website sync per dataset": "Single knowledge base {{amount}} web pages synchronized",
"support.wallet.subscription.function.custom domain tip": "The number of custom domain names that the team can configure, which can currently be used to access Wecom intelligent robots",
"support.wallet.subscription.mode.Month": "Month", "support.wallet.subscription.mode.Month": "Month",
"support.wallet.subscription.mode.Period": "Subscription Period", "support.wallet.subscription.mode.Period": "Subscription Period",
"support.wallet.subscription.mode.Year": "Year", "support.wallet.subscription.mode.Year": "Year",
......
...@@ -35,11 +35,16 @@ ...@@ -35,11 +35,16 @@
"wecom.bot_desc": "Connect to WeCom Bot directly via API", "wecom.bot_desc": "Connect to WeCom Bot directly via API",
"wecom.create_modal_title": "Create WeCom Bot", "wecom.create_modal_title": "Create WeCom Bot",
"wecom.edit_modal_title": "Edit WeCom Bot", "wecom.edit_modal_title": "Edit WeCom Bot",
"wecom.create_modal.step.1": "Configure Parameters",
"wecom.create_modal.step.2": "Fill in Callback URL",
"wecom.title": "Publish to WeCom Bot", "wecom.title": "Publish to WeCom Bot",
"dingtalk.bot": "DingTalk Bot", "dingtalk.bot": "DingTalk Bot",
"dingtalk.bot_desc": "Connect to DingTalk Bot directly via API", "dingtalk.bot_desc": "Connect to DingTalk Bot directly via API",
"dingtalk.create_modal_title": "Create DingTalk Bot", "dingtalk.create_modal_title": "Create DingTalk Bot",
"dingtalk.edit_modal_title": "Edit DingTalk Bot", "dingtalk.edit_modal_title": "Edit DingTalk Bot",
"dingtalk.title": "Publish to DingTalk Bot", "dingtalk.title": "Publish to DingTalk Bot",
"dingtalk.api": "DingTalk API" "dingtalk.api": "DingTalk API",
"use_default_domain": "Use Default Domain",
"ip_whitelist": "IP Whitelist",
"custom_domain_management": "Custom Domain Management"
} }
...@@ -14,7 +14,26 @@ ...@@ -14,7 +14,26 @@
"create_channel": "新增渠道", "create_channel": "新增渠道",
"create_model": "新增模型", "create_model": "新增模型",
"custom_config_details": "定制配置详情", "custom_config_details": "定制配置详情",
"custom_domain": "自定义域名",
"custom_model": "自定义模型", "custom_model": "自定义模型",
"custom_domain.domain": "域名",
"custom_domain.provider": "域名备案商",
"custom_domain.registration_hint": "请自备域名并通过 <bold>{{provider}}</bold> 完成备案后,将域名填入下方输入框中",
"custom_domain.provider.aliyun": "阿里云",
"custom_domain.provider.tencent": "腾讯云",
"custom_domain.provider.volcengine": "火山引擎",
"custom_domain.DNS_record": "DNS 记录",
"custom_domain.DNS_record.type": "类型",
"custom_domain.DNS_resolve_hint": "请到您的域名服务商处,添加该域名的 CNAME 解析到 <bold>{{domain}}</bold>,解析生效后即可绑定自定义域名。",
"custom_domain.dns_resolved": "已验证",
"custom_domain.dns_resolving": "验证中",
"custom_domain.delete_confirm": "确认删除该自定义域名?",
"custom_domain.status.active": "已生效",
"custom_domain.status.inactive": "已失效",
"custom_domain.domain_verify": "域名校验",
"custom_domain.domain_verify.path": "文件路径",
"custom_domain.domain_verify.content": "文件内容",
"custom_domain.domain_verify.desc": "保存后,访问 {{domain}}/{{path}} 将返回 {{content}}",
"day": "天", "day": "天",
"default_model": "预设模型", "default_model": "预设模型",
"default_model_config": "默认模型配置", "default_model_config": "默认模型配置",
...@@ -103,9 +122,6 @@ ...@@ -103,9 +122,6 @@
"requests_per_minute": "QPM", "requests_per_minute": "QPM",
"reset_default": "恢复默认配置", "reset_default": "恢复默认配置",
"status": "状态", "status": "状态",
"subscription_mode_month": "时长",
"subscription_package": "订阅套餐",
"subscription_period": "订阅周期",
"support_wallet_amount": "金额", "support_wallet_amount": "金额",
"team": "团队管理", "team": "团队管理",
"third_party": "第三方账号", "third_party": "第三方账号",
...@@ -113,5 +129,10 @@ ...@@ -113,5 +129,10 @@
"usage_records": "使用记录", "usage_records": "使用记录",
"website_sync_per_dataset": "站点同步最大页数", "website_sync_per_dataset": "站点同步最大页数",
"yes": "是", "yes": "是",
"yuan": "{{amount}}元" "yuan": "{{amount}}元",
"subscription_period": "订阅周期",
"subscription_package": "订阅套餐",
"subscription_mode_month": "时长",
"upgrade_to_use_custom_domain": "当前套餐不支持自定义域名,请先升级",
"upgrade_plan": "升级套餐"
} }
...@@ -301,6 +301,7 @@ ...@@ -301,6 +301,7 @@
"pro_modal_title": "商业版专享!", "pro_modal_title": "商业版专享!",
"pro_modal_unlock_button": "去解锁", "pro_modal_unlock_button": "去解锁",
"publish_channel": "发布渠道", "publish_channel": "发布渠道",
"publish_channel.wecom.empty": "发布到企业微信机器人,请先 <a>绑定自定义域名</a>,并且通过域名校验。",
"publish_success": "发布成功", "publish_success": "发布成功",
"question_guide_tip": "对话结束后,会为你生成 3 个引导性问题。", "question_guide_tip": "对话结束后,会为你生成 3 个引导性问题。",
"reasoning_response": "输出思考", "reasoning_response": "输出思考",
......
...@@ -781,11 +781,11 @@ ...@@ -781,11 +781,11 @@
"create_success": "创建成功", "create_success": "创建成功",
"create_time": "创建时间", "create_time": "创建时间",
"cron_job_run_app": "定时任务", "cron_job_run_app": "定时任务",
"custom_plan_price": "定制化计费",
"custom_plan_feature_1": "优先深度技术支持", "custom_plan_feature_1": "优先深度技术支持",
"custom_plan_feature_2": "专属客户经理", "custom_plan_feature_2": "专属客户经理",
"custom_plan_feature_3": "弹性资源配置", "custom_plan_feature_3": "弹性资源配置",
"custom_plan_feature_4": "安全可控", "custom_plan_feature_4": "安全可控",
"custom_plan_price": "定制化计费",
"custom_title": "自定义标题", "custom_title": "自定义标题",
"data_index_custom": "自定义索引", "data_index_custom": "自定义索引",
"data_index_default": "默认索引", "data_index_default": "默认索引",
...@@ -1052,6 +1052,7 @@ ...@@ -1052,6 +1052,7 @@
"read_quote": "查看引用", "read_quote": "查看引用",
"redo_tip": "恢复 ctrl shift z", "redo_tip": "恢复 ctrl shift z",
"redo_tip_mac": "恢复 ⌘ shift z", "redo_tip_mac": "恢复 ⌘ shift z",
"refresh": "刷新",
"request_end": "已加载全部", "request_end": "已加载全部",
"request_error": "请求异常", "request_error": "请求异常",
"request_more": "点击加载更多", "request_more": "点击加载更多",
...@@ -1224,6 +1225,7 @@ ...@@ -1224,6 +1225,7 @@
"support.wallet.subscription.eval_items_count": "单次评测数据条数: {{count}} 条", "support.wallet.subscription.eval_items_count": "单次评测数据条数: {{count}} 条",
"support.wallet.subscription.function.App registration count": "{{amount}} 个应用备案", "support.wallet.subscription.function.App registration count": "{{amount}} 个应用备案",
"support.wallet.subscription.function.Audit log store duration": "{{amount}} 天团队操作日志记录", "support.wallet.subscription.function.Audit log store duration": "{{amount}} 天团队操作日志记录",
"support.wallet.subscription.function.Custom domain": "{{amount}} 个自定义域名",
"support.wallet.subscription.function.History store": "{{amount}} 天对话记录保留", "support.wallet.subscription.function.History store": "{{amount}} 天对话记录保留",
"support.wallet.subscription.function.Max app": "{{amount}} 个 Agent", "support.wallet.subscription.function.Max app": "{{amount}} 个 Agent",
"support.wallet.subscription.function.Max dataset": "{{amount}} 个知识库", "support.wallet.subscription.function.Max dataset": "{{amount}} 个知识库",
...@@ -1233,6 +1235,7 @@ ...@@ -1233,6 +1235,7 @@
"support.wallet.subscription.function.Requests per minute": "{{amount}} QPM", "support.wallet.subscription.function.Requests per minute": "{{amount}} QPM",
"support.wallet.subscription.function.Ticket response time": "{{amount}} 小时工单支持响应", "support.wallet.subscription.function.Ticket response time": "{{amount}} 小时工单支持响应",
"support.wallet.subscription.function.Website sync per dataset": "站点同步最大 {{amount}} 页", "support.wallet.subscription.function.Website sync per dataset": "站点同步最大 {{amount}} 页",
"support.wallet.subscription.function.custom domain tip": "团队可以配置的自定义域名数量,目前可用于接入企微智能机器人",
"support.wallet.subscription.function.qpm tip": "主要指团队每分钟请求 Agent 的最大次数,与单个 Agent 复杂度无关。其他 OpenAPI 接口也受此影响,每个接口单独计算", "support.wallet.subscription.function.qpm tip": "主要指团队每分钟请求 Agent 的最大次数,与单个 Agent 复杂度无关。其他 OpenAPI 接口也受此影响,每个接口单独计算",
"support.wallet.subscription.mode.Month": "按月", "support.wallet.subscription.mode.Month": "按月",
"support.wallet.subscription.mode.Period": "订阅周期", "support.wallet.subscription.mode.Period": "订阅周期",
......
...@@ -35,11 +35,16 @@ ...@@ -35,11 +35,16 @@
"wecom.bot_desc": "通过 API 直接接入企业微信机器人", "wecom.bot_desc": "通过 API 直接接入企业微信机器人",
"wecom.create_modal_title": "创建企微机器人", "wecom.create_modal_title": "创建企微机器人",
"wecom.edit_modal_title": "编辑企微机器人", "wecom.edit_modal_title": "编辑企微机器人",
"wecom.create_modal.step.1": "配置参数",
"wecom.create_modal.step.2": "填写回调地址",
"wecom.title": "发布到企业微信机器人", "wecom.title": "发布到企业微信机器人",
"dingtalk.bot": "钉钉机器人", "dingtalk.bot": "钉钉机器人",
"dingtalk.bot_desc": "通过 API 直接接入钉钉机器人", "dingtalk.bot_desc": "通过 API 直接接入钉钉机器人",
"dingtalk.create_modal_title": "创建钉钉机器人", "dingtalk.create_modal_title": "创建钉钉机器人",
"dingtalk.edit_modal_title": "编辑钉钉机器人", "dingtalk.edit_modal_title": "编辑钉钉机器人",
"dingtalk.title": "发布到钉钉机器人", "dingtalk.title": "发布到钉钉机器人",
"dingtalk.api": "钉钉 API" "dingtalk.api": "钉钉 API",
"use_default_domain": "使用默认域名",
"ip_whitelist": "IP 白名单",
"custom_domain_management": "自定义域名管理"
} }
...@@ -15,6 +15,25 @@ ...@@ -15,6 +15,25 @@
"create_model": "新增模型", "create_model": "新增模型",
"custom_config_details": "定制配置詳情", "custom_config_details": "定制配置詳情",
"custom_model": "自訂模型", "custom_model": "自訂模型",
"custom_domain": "自訂域名",
"custom_domain.domain": "域名",
"custom_domain.provider": "域名備案商",
"custom_domain.registration_hint": "請自備域名並透過 <bold>{{provider}}</bold> 完成備案後,將域名填入下方輸入框中",
"custom_domain.provider.aliyun": "阿里雲",
"custom_domain.provider.tencent": "騰訊雲",
"custom_domain.provider.volcengine": "火山引擎",
"custom_domain.DNS_record": "DNS 記錄",
"custom_domain.DNS_record.type": "類型",
"custom_domain.DNS_resolve_hint": "請到您的域名服務商處,新增該域名的 CNAME 解析到 <bold>{{domain}}</bold>,解析生效後即可繫結自訂域名。",
"custom_domain.dns_resolved": "已驗證",
"custom_domain.dns_resolving": "驗證中",
"custom_domain.delete_confirm": "確認刪除該自訂域名?",
"custom_domain.status.active": "已生效",
"custom_domain.status.inactive": "已失效",
"custom_domain.domain_verify": "域名校驗",
"custom_domain.domain_verify.path": "檔案路徑",
"custom_domain.domain_verify.content": "檔案內容",
"custom_domain.domain_verify.desc": "儲存後,訪問 {{domain}}/{{path}} 將傳回 {{content}}",
"day": "天", "day": "天",
"default_model": "預設模型", "default_model": "預設模型",
"default_model_config": "預設模型設定", "default_model_config": "預設模型設定",
...@@ -113,5 +132,10 @@ ...@@ -113,5 +132,10 @@
"usage_records": "使用記錄", "usage_records": "使用記錄",
"website_sync_per_dataset": "站點同步最大頁數", "website_sync_per_dataset": "站點同步最大頁數",
"yes": "是", "yes": "是",
"yuan": "{{amount}}元" "yuan": "{{amount}}元",
} "month": "月",
\ No newline at end of file "extra_dataset_size": "額外知識庫容量",
"extra_ai_points": "AI 積分運算標準",
"upgrade_to_use_custom_domain": "目前套餐不支援自訂域名,請先升級",
"upgrade_plan": "升級套餐"
}
...@@ -775,11 +775,11 @@ ...@@ -775,11 +775,11 @@
"create_success": "建立成功", "create_success": "建立成功",
"create_time": "建立時間", "create_time": "建立時間",
"cron_job_run_app": "排程任務", "cron_job_run_app": "排程任務",
"custom_plan_price": "定制化計費",
"custom_plan_feature_1": "優先深度技術支援", "custom_plan_feature_1": "優先深度技術支援",
"custom_plan_feature_2": "專屬客戶經理", "custom_plan_feature_2": "專屬客戶經理",
"custom_plan_feature_3": "彈性資源配置", "custom_plan_feature_3": "彈性資源配置",
"custom_plan_feature_4": "安全可控", "custom_plan_feature_4": "安全可控",
"custom_plan_price": "定制化計費",
"custom_title": "自訂標題", "custom_title": "自訂標題",
"data_index_custom": "自定義索引", "data_index_custom": "自定義索引",
"data_index_default": "預設索引", "data_index_default": "預設索引",
...@@ -1213,6 +1213,7 @@ ...@@ -1213,6 +1213,7 @@
"support.wallet.subscription.Upgrade plan": "升級方案", "support.wallet.subscription.Upgrade plan": "升級方案",
"support.wallet.subscription.ai_model": "AI 語言模型", "support.wallet.subscription.ai_model": "AI 語言模型",
"support.wallet.subscription.function.Audit log store duration": "{{amount}} 天團隊操作日誌記錄", "support.wallet.subscription.function.Audit log store duration": "{{amount}} 天團隊操作日誌記錄",
"support.wallet.subscription.function.Custom domain": "{{amount}} 個自定義域名",
"support.wallet.subscription.function.History store": "{{amount}} 天對話紀錄保留", "support.wallet.subscription.function.History store": "{{amount}} 天對話紀錄保留",
"support.wallet.subscription.function.Max app": "{{amount}} 個 Agent", "support.wallet.subscription.function.Max app": "{{amount}} 個 Agent",
"support.wallet.subscription.function.Max dataset": "{{amount}} 個知識庫", "support.wallet.subscription.function.Max dataset": "{{amount}} 個知識庫",
...@@ -1221,6 +1222,7 @@ ...@@ -1221,6 +1222,7 @@
"support.wallet.subscription.function.Points": "{{amount}} 積分", "support.wallet.subscription.function.Points": "{{amount}} 積分",
"support.wallet.subscription.function.Requests per minute": "{{amount}} QPM", "support.wallet.subscription.function.Requests per minute": "{{amount}} QPM",
"support.wallet.subscription.function.Website sync per dataset": "單知識庫 {{amount}} 個網頁同步", "support.wallet.subscription.function.Website sync per dataset": "單知識庫 {{amount}} 個網頁同步",
"support.wallet.subscription.function.custom domain tip": "團隊可以配置的自定義域名數量,目前可用於接入企微智能機器人",
"support.wallet.subscription.mode.Month": "按月", "support.wallet.subscription.mode.Month": "按月",
"support.wallet.subscription.mode.Period": "訂閱週期", "support.wallet.subscription.mode.Period": "訂閱週期",
"support.wallet.subscription.mode.Year": "按年", "support.wallet.subscription.mode.Year": "按年",
......
...@@ -35,11 +35,16 @@ ...@@ -35,11 +35,16 @@
"wecom.bot_desc": "透過 API 直接連結企業微信聊天機器人", "wecom.bot_desc": "透過 API 直接連結企業微信聊天機器人",
"wecom.create_modal_title": "建立企業微信聊天機器人", "wecom.create_modal_title": "建立企業微信聊天機器人",
"wecom.edit_modal_title": "編輯企業微信聊天機器人", "wecom.edit_modal_title": "編輯企業微信聊天機器人",
"wecom.create_modal.step.1": "配置參數",
"wecom.create_modal.step.2": "填寫回調地址",
"wecom.title": "發布至企業微信聊天機器人", "wecom.title": "發布至企業微信聊天機器人",
"dingtalk.bot": "釘釘聊天機器人", "dingtalk.bot": "釘釘聊天機器人",
"dingtalk.bot_desc": "透過 API 直接連結釘釘聊天機器人", "dingtalk.bot_desc": "透過 API 直接連結釘釘聊天機器人",
"dingtalk.create_modal_title": "建立釘釘聊天機器人", "dingtalk.create_modal_title": "建立釘釘聊天機器人",
"dingtalk.edit_modal_title": "編輯釘釘聊天機器人", "dingtalk.edit_modal_title": "編輯釘釘聊天機器人",
"dingtalk.title": "發布至釘釘聊天機器人", "dingtalk.title": "發布至釘釘聊天機器人",
"dingtalk.api": "釘釘 API" "dingtalk.api": "釘釘 API",
"use_default_domain": "使用預設域名",
"ip_whitelist": "IP 白名單",
"custom_domain_management": "自訂域名管理"
} }
...@@ -76,6 +76,8 @@ COPY --from=maindeps /app/node_modules/@zilliz/milvus2-sdk-node ./node_modules/@ ...@@ -76,6 +76,8 @@ COPY --from=maindeps /app/node_modules/@zilliz/milvus2-sdk-node ./node_modules/@
COPY --from=builder /app/projects/app/package.json ./package.json COPY --from=builder /app/projects/app/package.json ./package.json
# copy config # copy config
COPY ./projects/app/data/config.json /app/data/config.json COPY ./projects/app/data/config.json /app/data/config.json
# copy test.mp3
COPY ./projects/app/data/test.mp3 /app/data/test.mp3
# copy GeoLite2-City.mmdb # copy GeoLite2-City.mmdb
COPY ./projects/app/data/GeoLite2-City.mmdb /app/data/GeoLite2-City.mmdb COPY ./projects/app/data/GeoLite2-City.mmdb /app/data/GeoLite2-City.mmdb
......
This source diff could not be displayed because it is too large. You can view the blob instead.

78.3 KB | W: | H:

25.6 KB | W: | H:

projects/app/public/imgs/chat/fastgpt_chat_diagram.png
projects/app/public/imgs/chat/fastgpt_chat_diagram.png
projects/app/public/imgs/chat/fastgpt_chat_diagram.png
projects/app/public/imgs/chat/fastgpt_chat_diagram.png
  • 2-up
  • Swipe
  • Onion skin

82.4 KB | W: | H:

26.4 KB | W: | H:

projects/app/public/imgs/chat/fastgpt_chat_diagram_en.png
projects/app/public/imgs/chat/fastgpt_chat_diagram_en.png
projects/app/public/imgs/chat/fastgpt_chat_diagram_en.png
projects/app/public/imgs/chat/fastgpt_chat_diagram_en.png
  • 2-up
  • Swipe
  • Onion skin

28 KB | W: | H:

8.17 KB | W: | H:

projects/app/public/imgs/errImg.png
projects/app/public/imgs/errImg.png
projects/app/public/imgs/errImg.png
projects/app/public/imgs/errImg.png
  • 2-up
  • Swipe
  • Onion skin
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1693533362715" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4021" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M166.32 808.98l-99.28 99.28c-15.31 15.32-39.71 15.32-55.03 0-15.32-15.31-15.32-39.71 0-55.03l98.71-99.28c15.32-15.31 39.71-15.31 55.03 0 15.32 15.32 15.32 39.72 0.57 55.03z m48.22 48.79l-99.28 99.27c-15.31 15.32-15.31 39.71 0 55.03 15.32 15.32 39.71 15.32 55.03 0l99.28-99.28c15.32-15.32 15.32-39.71 0-55.03-15.32-15.31-39.72-15.31-55.03 0.01z m625.14-545.16c0 71.47-57.86 129.34-129.34 129.34S581 384.08 581 312.61c0-71.48 57.86-129.34 129.34-129.34 72.05 0 129.91 57.86 129.34 129.34z m-77.71 0.57c0-28.36-23.26-51.63-51.62-51.63-28.36 0-51.62 23.26-51.62 51.63 0 28.36 23.26 51.62 51.62 51.62 28.93 0 52.19-23.26 51.62-51.62z m236.56 68.07c-27.8 104.39-83.39 203.65-164.51 285.35h-3.97l-5.11 5.67c10.21 38.57 11.35 78.28 2.84 116.86-9.07 46.52-32.33 90.2-66.94 124.8l-2.27 2.27c-38.57 36.87-85.09 59.57-134.45 67.51-51.62 7.94-105.51 0-152.6-23.26-17.58-10.21-25.53-32.9-16.45-51.63 1.7-2.84 3.41-5.67 5.67-8.5 14.18-15.32 24.96-32.9 32.9-51.63l3.4-7.94c-10.21 1.7-20.42 4.53-30.63 5.1-29.5 4.54-60.14 6.24-90.2 5.67-10.21 0-19.86-3.97-26.1-11.35L181.63 671.14c-8.51-7.95-11.35-18.72-10.78-29.5 0-28.37 2.27-58.43 5.67-86.79 1.7-10.78 2.84-20.99 5.1-31.2l-8.51 3.4c-17.58 7.95-35.17 18.72-51.06 33.47-14.75 14.18-39.14 12.48-53.32-2.27-2.27-2.84-3.97-7.37-6.24-10.21-23.26-47.09-30.63-99.28-22.69-150.9 8.22-51.8 32.65-99.66 69.78-136.71 34.61-34.61 78.85-57.86 123.67-66.94 39.14-8.51 79.42-7.94 116.86 2.27l7.95-7.95C439.75 106.69 538.45 51.66 642.83 23.3c108.35-28.36 222.38-28.36 329.59 0 13.62 4.54 23.82 15.32 27.23 27.23l0.57 0.57v0.56c28.37 107.78 28.37 221.81-1.69 329.59zM936.12 92.51c-89.06-19.86-182.67-18.16-270.03 5.1-91.34 24.96-178.7 73.18-250.74 145.22l-10.78 10.78-10.21 11.35c-10.21 11.35-26.66 16.45-42.55 10.78-31.2-12.48-65.81-14.75-98.71-7.38-31.2 6.81-61.27 22.13-86.23 46.52-26.66 27.8-43.12 60.7-48.79 95.3-3.4 20.99-3.4 42.55 0.57 62.97 9.64-5.67 18.72-10.78 28.93-14.18 26.66-11.34 55.6-18.15 84.53-18.72 3.97-0.57 7.94 0 11.91 0.56 19.86 5.67 31.77 26.66 26.1 46.52-7.95 26.1-13.05 52.2-16.45 78.29-1.7 22.12-3.4 43.12-3.97 64.67l146.36 146.93c22.12-1.14 43.11-2.27 64.67-5.11 27.23-3.97 52.76-8.51 78.29-15.89 3.97-0.56 7.94-1.13 11.91-1.13 20.42 0.57 36.31 18.72 36.31 39.14-1.7 29.5-7.38 58.43-19.29 85.66-3.97 9.08-9.07 19.29-14.18 28.36 20.42 3.97 41.98 3.97 62.4 1.13 33.47-5.1 66.94-20.99 93.03-47.09l1.13-1.13c24.39-24.96 39.71-55.6 47.09-86.79 6.81-32.34 3.97-66.94-7.94-99.28-4.54-14.18-1.7-30.64 10.21-41.41l11.91-11.35 8.51-8.51 2.27-1.13c72.04-72.61 119.7-159.41 145.79-250.17 23.27-89.05 25.54-180.95 7.95-270.01z" p-id="4022" fill="#515151"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1691391466028" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4715" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M511.957333 21.333333C241.024 21.333333 21.333333 240.981333 21.333333 512c0 216.832 140.544 400.725333 335.573334 465.664 24.490667 4.394667 32.256-10.069333 32.256-23.082667 0-11.690667 0.256-44.245333 0-85.205333-136.448 29.610667-164.736-64.64-164.736-64.64-22.314667-56.704-54.4-71.765333-54.4-71.765333-44.586667-30.464 3.285333-29.824 3.285333-29.824 49.194667 3.413333 75.178667 50.517333 75.178667 50.517333 43.776 75.008 114.816 53.333333 142.762666 40.789333 4.522667-31.658667 17.152-53.376 31.189334-65.536-108.970667-12.458667-223.488-54.485333-223.488-242.602666 0-53.546667 19.114667-97.322667 50.517333-131.669334-5.034667-12.330667-21.930667-62.293333 4.778667-129.834666 0 0 41.258667-13.184 134.912 50.346666a469.802667 469.802667 0 0 1 122.88-16.554666c41.642667 0.213333 83.626667 5.632 122.88 16.554666 93.653333-63.488 134.784-50.346667 134.784-50.346666 26.752 67.541333 9.898667 117.504 4.864 129.834666 31.402667 34.346667 50.474667 78.122667 50.474666 131.669334 0 188.586667-114.730667 230.016-224.042666 242.090666 17.578667 15.232 33.578667 44.672 33.578666 90.453334v135.850666c0 13.141333 7.936 27.605333 32.853334 22.869334C862.250667 912.597333 1002.666667 728.746667 1002.666667 512 1002.666667 240.981333 783.018667 21.333333 511.957333 21.333333z" p-id="4716"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1688889092975" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2652" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M932.15857778 820.62601482V639.06702222c-26.82121482 23.42305185-57.04059259 42.71976297-89.4445037 57.28331853-104.97896297 46.11792592-218.45333333 69.41961482-333.14133333 68.32734814-148.06281482 8.61677037-294.66927408-32.768-416.39632593-117.35798519v182.89398519C101.4290963 910.07051852 277.52675555 997.45185185 512.36408889 997.45185185c234.83733333 0 409.6-88.10951111 419.79448889-168.57315555v-8.25268148z" p-id="2653" fill="#4e83fd"></path><path d="M932.15857778 505.93185185v-3.39816296 8.25268148c-10.19448889 80.58500741-184.95715555 168.57315555-419.79448889 168.57315555-234.83733333 0-410.93499259-87.38133333-419.18767407-167.23816295V329.10601482C214.90346667 413.81736297 361.50992592 455.08077037 509.57274075 446.464c114.688 1.09226667 228.16237037-22.08805925 333.14133333-68.32734815 32.40391111-14.68491852 62.62328889-33.98162963 89.4445037-57.28331852V505.93185185z" p-id="2654" fill="#4e83fd"></path><path d="M816.13558518 85.40918518c69.66234075 30.70482963 111.28983703 74.3954963 111.28983704 113.35300741 0 38.22933333-42.96248889 80.58500741-111.28983704 113.35300741-97.09037037 41.26340741-201.70524445 61.53102222-307.16965926 59.34648889-238.96367408 0-417.1245037-90.7794963-417.1245037-171.97131852s178.88900741-172.6994963 418.4594963-172.69949629c104.97896297-2.3058963 209.10838518 17.71899259 305.83466666 58.6183111z" p-id="2655" fill="#4e83fd"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1691459708057" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5077" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M934.423357 388.738675H636.152113v99.05297h-64.657602L413.227927 382.546649v-64.511269h32.679284c69.808926 0 126.410038-56.601112 126.410038-126.410038 0-69.807902-56.602136-126.409015-126.410038-126.409015H330.962296c-69.808926 0-126.410038 56.602136-126.410038 126.409015 0 69.809949 56.602136 126.410038 126.410038 126.410038h32.702819v64.536852L166.551782 513.623987l197.114356 131.054825v61.440327h-32.702819c-69.808926 0-126.410038 56.601112-126.410038 126.410038 0 69.807902 56.602136 126.410038 126.410038 126.410038h114.944915c60.205197 0 110.372783-42.185798 123.096573-98.54541h240.532215V636.430964h124.887358V388.738675zM759.973186 810.856575h-189.833533c-10.328229-59.431577-61.84965-104.737436-124.232442-104.737436h-32.679284v-61.414745l161.435763-107.349936h61.488423v99.075483h123.821073v174.426634z" p-id="5078" fill="#FC9663"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1691460113549" class="icon" viewBox="0 0 1443 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2328" xmlns:xlink="http://www.w3.org/1999/xlink" width="90.1875" height="64"><path d="M1200.165345 1023.218368H349.208974c-192.401638 0-349.208974-215.369584-349.208974-406.207959a348.006463 348.006463 0 0 1 349.208974-346.022321 322.33287 322.33287 0 0 1 38.780955 2.344895C470.963136 141.056856 617.248506 0.002405 774.777348 0.002405a454.368494 454.368494 0 0 1 454.669122 423.403856c120.251024 14.309872 213.625944 168.952689 213.625944 359.189808a242.005186 242.005186 0 0 1-242.907069 240.622299zM349.148848 330.57247A288.061328 288.061328 0 0 0 60.125512 617.010409c0 158.009846 129.690729 346.623577 289.083462 346.623577h850.956371A182.120176 182.120176 0 0 0 1382.886776 782.596069c0-156.867461-81.951073-301.349066-182.661305-301.349066a30.062756 30.062756 0 0 1-30.062756-30.062756A394.062606 394.062606 0 0 0 774.717222 59.28616c-142.67784 0-274.893841 136.965916-344.940062 260.70422a29.581752 29.581752 0 0 1-31.265266 14.73075 297.080155 297.080155 0 0 0-49.30292-4.14866z m881.079253 120.912405zM300.62756 783.497952H240.502048v-240.502048h60.125512v60.125512h60.125512v-60.125512h60.125512v240.502048H360.753072v-120.251024H300.62756v120.251024z m180.376536-180.376536v-60.125512h180.376536v60.125512h-60.125512v180.376536H541.129608v-180.376536H481.004096z m240.502048 0v-60.125512h180.376536v60.125512h-60.125512v180.376536h-60.125512v-180.376536h-60.125512z m300.62756 180.376536h-60.125512v-240.502048h60.125512c71.368983-0.962008 127.225583-24.050205 126.263575 30.062756-1.863891 50.264928-7.936568 147.548006-66.138063 150.31378h-60.125512v60.125512z m0-180.376536v60.125512a35.293676 35.293676 0 0 0 12.025103 6.012551q56.39773 5.471422 54.954717-36.075307 0-37.818947-54.954717-36.075307a35.293676 35.293676 0 0 0-12.025103 6.012551z" fill="#7839ee" p-id="2329"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1686561811905" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2855" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M992 528c0 273.9-222.1 496-496 496S0 801.9 0 528 222.1 32 496 32c86.2 0 167.3 22 238 60.7 2.3 1.3 2.8 4.4 0.9 6.3l-37 37.3-4.2 4.3c-1.2 1.2-3.1 1.5-4.6 0.8-8.2-4.1-16.5-7.9-24.9-11.5C610.9 107.4 554.3 96 496 96s-114.9 11.4-168.1 33.9c-51.4 21.8-97.7 52.9-137.3 92.6-39.7 39.7-70.9 85.9-92.6 137.3C75.4 413.1 64 469.6 64 528c0 58.3 11.4 114.9 33.9 168.1 21.8 51.4 52.9 97.6 92.6 137.3 39.7 39.7 85.9 70.9 137.3 92.6 53.3 22.6 109.9 34 168.2 34s114.9-11.4 168.1-33.9c51.4-21.8 97.7-52.9 137.3-92.6 39.7-39.7 70.9-85.9 92.6-137.3 22.6-53.3 34-109.9 34-168.2 0-58.4-11.4-114.9-33.9-168.1-3.6-8.5-7.4-16.8-11.5-25-0.8-1.5-0.5-3.4 0.8-4.6l4.3-4.2 37.3-37c1.9-1.9 5-1.4 6.3 0.9C970 360.6 992 441.7 992 528z" p-id="2856" fill="#6CD3CC"></path><path d="M781.4 397c-3.7-8-11.7-13.1-20.6-13.1H740c-6 0-11.8 2.4-16 6.6-7 7-8.6 17.6-4.1 26.4 2.6 5.1 5 10.3 7.3 15.7 13.2 31.2 19.9 64.3 19.9 98.5s-6.7 67.3-19.9 98.5c-12.7 30.1-31 57.2-54.2 80.4-23.3 23.3-50.3 41.5-80.4 54.2-31.3 13.1-64.4 19.8-98.6 19.8s-67.3-6.7-98.5-19.9c-30.1-12.7-57.2-31-80.4-54.2-23.3-23.3-41.5-50.3-54.2-80.4-13.2-31.2-19.9-64.3-19.9-98.5s6.7-67.3 19.9-98.5c12.7-30.1 31-57.2 54.2-80.4 23.3-23.3 50.3-41.5 80.4-54.2 31.2-13.2 64.3-19.9 98.5-19.9s67.3 6.7 98.5 19.9c4.9 2.1 9.8 4.3 14.6 6.7 8.8 4.4 19.4 2.6 26.3-4.4 4.3-4.3 6.7-10.1 6.7-16.2v-20.2c0-9-5.2-17.1-13.4-20.8-40.4-18.6-85.3-29-132.6-29-175.5 0-318 143.4-317 318.9C178 707.1 319.6 848 494 848c174.8 0 316.6-141.3 317-316.2 0.1-48.2-10.5-93.9-29.6-134.8z" p-id="2857" fill="#6CD3CC"></path><path d="M634.5 488.5c-0.8-2.9-4.5-3.9-6.7-1.7l-34.7 34.7-1.8 1.8c-9 9-15.7 20.1-20.1 32.1-11.5 31.6-42.4 54-78.3 52.7-41.6-1.6-75.3-35.3-76.9-76.9-1.4-35.9 21-66.8 52.7-78.3 12-4.4 23-11.1 32.1-20.1l1.8-1.8 34.7-34.7c2.2-2.2 1.2-5.8-1.7-6.7-12.9-3.7-26.5-5.6-40.6-5.5-79.4 0.5-143 64.5-143 143.9 0 79.5 64.5 144 144 144 79.4 0 143.4-63.6 144-142.9 0.1-14.1-1.8-27.8-5.5-40.6z" p-id="2858" fill="#6CD3CC"></path><path d="M1014.3 146H882c-2.2 0-4-1.8-4-4V9.8c0-2.4-2-4-4-4-1 0-2 0.4-2.8 1.2L766.8 112.4l-46.1 46.5-44 44.4c-3 3-4.6 7-4.6 11.3v85.5c0 4.3-1.7 8.3-4.7 11.3l-94.7 94.7-47.4 47.4-51.8 51.9c-12.5 12.5-12.5 32.8 0 45.3 6.3 6.3 14.4 9.4 22.6 9.4s16.4-3.1 22.6-9.4l51.8-51.9 123.2-123.2 19-19c3-3 7.1-4.7 11.3-4.7h85.5c4.2 0 8.3-1.7 11.3-4.6l44.3-43.9 46.5-46.1L1017 152.9c2.6-2.6 0.8-6.9-2.7-6.9zM864 214.3l-44 43.5-25.6 25.4c-3 3-7 4.6-11.3 4.6H744c-4.4 0-8-3.6-8-8v-39c0-4.2 1.7-8.3 4.6-11.3l25.5-25.7 43.5-43.9 1.6-1.6c1.6-1.7 4.5-0.7 4.8 1.6 4.8 25.8 23.5 41.6 48.6 47.7 2.1 0.5 2.9 3.2 1.3 4.7l-1.9 2z" p-id="2859" fill="#6CD3CC"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1691460052375" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7219" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M950.676002 419.057175a255.346807 255.346807 0 0 0-22.014863-209.48251 257.949339 257.949339 0 0 0-277.74565-123.72694A258.759964 258.759964 0 0 0 212.538139 178.386943a255.346807 255.346807 0 0 0-170.572521 123.726941 257.949339 257.949339 0 0 0 31.699696 302.789689 255.133485 255.133485 0 0 0 21.758876 209.525175 258.162662 258.162662 0 0 0 277.958972 123.726941A255.346807 255.346807 0 0 0 565.757223 1023.996587a258.375984 258.375984 0 0 0 246.259276-179.446729 255.560129 255.560129 0 0 0 170.529856-123.726941 258.375984 258.375984 0 0 0-31.870353-301.765742zM565.757223 957.013381a190.966133 190.966133 0 0 1-122.702994-44.371041l6.015689-3.455821 203.893466-117.668587a33.918248 33.918248 0 0 0 16.724469-29.054499v-287.430483l86.182214 49.832092a3.029177 3.029177 0 0 1 1.621249 2.218552v238.195693a192.160738 192.160738 0 0 1-191.734093 191.734094zM153.618516 780.979809a190.710147 190.710147 0 0 1-22.825487-128.590689l6.058354 3.626479 204.064123 117.711252a32.8943 32.8943 0 0 0 33.278281 0l249.288453-143.736574v99.493526a3.413157 3.413157 0 0 1-1.407927 2.645197L415.578315 851.205514a191.990081 191.990081 0 0 1-261.959799-70.225705zM99.861294 336.928085a191.350114 191.350114 0 0 1 100.944118-84.176984V494.957254a32.680978 32.680978 0 0 0 16.553811 28.841176l248.093849 143.139272-86.182214 49.832092a3.242499 3.242499 0 0 1-3.029177 0l-206.069353-118.863193A192.160738 192.160738 0 0 1 99.861294 335.904138z m708.102081 164.471503l-248.861809-144.504534L645.070458 307.23362a3.242499 3.242499 0 0 1 3.029177 0l206.069353 119.076514a191.734094 191.734094 0 0 1-28.841177 345.795467v-242.248817a33.704925 33.704925 0 0 0-17.364436-28.457196z m85.75557-128.97467l-6.01569-3.626479-203.680143-118.692534a33.107623 33.107623 0 0 0-33.491603 0L401.456378 393.842478V294.306288a2.815855 2.815855 0 0 1 1.194605-2.602533l206.069353-118.905856a191.990081 191.990081 0 0 1 284.998609 198.816394z m-539.278804 176.417552l-86.182214-49.661434a3.413157 3.413157 0 0 1-1.62125-2.431875V259.236099a191.990081 191.990081 0 0 1 314.65041-147.320388l-6.058354 3.413157-203.850801 117.668587a33.918248 33.918248 0 0 0-16.767134 29.054499z m46.802915-100.901454l111.012931-63.996693 111.226253 63.996693v127.950723l-110.799609 63.996693-111.226253-63.996693z" p-id="7220"></path></svg>
\ No newline at end of file

46.3 KB | W: | H:

19.1 KB | W: | H:

projects/app/public/imgs/proModalBg.png
projects/app/public/imgs/proModalBg.png
projects/app/public/imgs/proModalBg.png
projects/app/public/imgs/proModalBg.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -85,6 +85,7 @@ const Navbar = ({ unread }: { unread: number }) => { ...@@ -85,6 +85,7 @@ const Navbar = ({ unread }: { unread: number }) => {
activeLink: [ activeLink: [
'/account/bill', '/account/bill',
'/account/info', '/account/info',
'/account/customDomain',
'/account/team', '/account/team',
'/account/usage', '/account/usage',
'/account/thirdParty', '/account/thirdParty',
......
...@@ -527,7 +527,7 @@ const ChatBox = ({ ...@@ -527,7 +527,7 @@ const ChatBox = ({
file: { file: {
type: file.type, type: file.type,
name: file.name, name: file.name,
url: file.key ? undefined : file.url || '', url: file.url,
icon: file.icon || '', icon: file.icon || '',
key: file.key || '' key: file.key || ''
} }
......
...@@ -46,7 +46,8 @@ const StandardPlanContentList = ({ ...@@ -46,7 +46,8 @@ const StandardPlanContentList = ({
standplan?.chatHistoryStoreDuration ?? plan.chatHistoryStoreDuration, standplan?.chatHistoryStoreDuration ?? plan.chatHistoryStoreDuration,
auditLogStoreDuration: standplan?.auditLogStoreDuration ?? plan.auditLogStoreDuration, auditLogStoreDuration: standplan?.auditLogStoreDuration ?? plan.auditLogStoreDuration,
appRegistrationCount: standplan?.appRegistrationCount ?? plan.appRegistrationCount, appRegistrationCount: standplan?.appRegistrationCount ?? plan.appRegistrationCount,
ticketResponseTime: standplan?.ticketResponseTime ?? plan.ticketResponseTime ticketResponseTime: standplan?.ticketResponseTime ?? plan.ticketResponseTime,
customDomain: standplan?.customDomain ?? plan.customDomain
}; };
}, [ }, [
subPlans?.standard, subPlans?.standard,
...@@ -62,7 +63,8 @@ const StandardPlanContentList = ({ ...@@ -62,7 +63,8 @@ const StandardPlanContentList = ({
standplan?.chatHistoryStoreDuration, standplan?.chatHistoryStoreDuration,
standplan?.auditLogStoreDuration, standplan?.auditLogStoreDuration,
standplan?.appRegistrationCount, standplan?.appRegistrationCount,
standplan?.ticketResponseTime standplan?.ticketResponseTime,
standplan?.customDomain
]); ]);
return planContent ? ( return planContent ? (
...@@ -175,6 +177,20 @@ const StandardPlanContentList = ({ ...@@ -175,6 +177,20 @@ const StandardPlanContentList = ({
</Box> </Box>
</Flex> </Flex>
)} )}
{planContent.customDomain !== undefined && (
<Flex alignItems={'center'}>
<MyIcon name={'price/right'} w={'16px'} mr={3} />
<Box color={'myGray.600'}>
{t('common:support.wallet.subscription.function.Custom domain', {
amount: planContent.customDomain
})}
</Box>
<QuestionTip
ml={1}
label={t('common:support.wallet.subscription.function.custom domain tip')}
/>
</Flex>
)}
</Grid> </Grid>
) : null; ) : null;
}; };
......
...@@ -22,7 +22,8 @@ export enum TabEnum { ...@@ -22,7 +22,8 @@ export enum TabEnum {
'apikey' = 'apikey', 'apikey' = 'apikey',
'loginout' = 'loginout', 'loginout' = 'loginout',
'team' = 'team', 'team' = 'team',
'model' = 'model' 'model' = 'model',
'customDomain' = 'customDomain'
} }
const AccountContainer = ({ const AccountContainer = ({
...@@ -77,6 +78,15 @@ const AccountContainer = ({ ...@@ -77,6 +78,15 @@ const AccountContainer = ({
label: t('account:third_party'), label: t('account:third_party'),
value: TabEnum.thirdParty value: TabEnum.thirdParty
}, },
...(feConfigs.isPlus && feConfigs.customDomain?.enable
? [
{
icon: 'common/globalLine',
label: t('account:custom_domain'),
value: TabEnum.customDomain
}
]
: []),
{ {
icon: 'common/model', icon: 'common/model',
label: t('account:model_provider'), label: t('account:model_provider'),
......
...@@ -109,6 +109,13 @@ const BillDetailModal = ({ billId, onClose }: BillDetailModalProps) => { ...@@ -109,6 +109,13 @@ const BillDetailModal = ({ billId, onClose }: BillDetailModalProps) => {
unit: 'h' unit: 'h'
}); });
} }
if (config.customDomain !== undefined) {
items.push({
key: i18nT('account:custom_domain'),
value: config.customDomain,
unit: ''
});
}
return items; return items;
}, [bill?.couponDetail?.subscriptions]); }, [bill?.couponDetail?.subscriptions]);
......
import {
ModalBody,
Box,
Radio,
Flex,
Text,
Input,
InputGroup,
InputRightElement,
Tag,
Table,
Tbody,
Td,
Th,
Thead,
Tr,
IconButton,
Button,
ModalFooter,
Link
} from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation, Trans } from 'next-i18next';
import Icon from '@fastgpt/web/components/common/Icon';
import type { IconNameType } from '@fastgpt/web/components/common/Icon/type';
import { useEffect, useMemo, useState } from 'react';
import { providerMap } from '@/web/support/customDomain/const';
import type { ProviderEnum } from '@fastgpt/global/support/customDomain/type';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { generateCNAMEDomain } from '@fastgpt/global/support/customDomain/utils';
import { useCopyData } from '@fastgpt/web/hooks/useCopyData';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import {
activeCustomDomain,
checkCustomDomainDNSResolve,
createCustomDomain
} from '@/web/support/customDomain/api';
import { getDocPath } from '@/web/common/system/doc';
const ProviderItem = ({
icon,
selected,
onClick,
isDisabled
}: {
icon: IconNameType;
selected: boolean;
onClick: () => void;
isDisabled: boolean;
}) => {
return (
<Flex
flex="1"
paddingInline="32px"
paddingBlock="12px"
alignItems="center"
justifyContent="start"
gap="8px"
border="1px solid"
borderColor={'blue.600'}
borderRadius="sm"
{...(selected
? {
backgroundColor: 'blue.50'
}
: {})}
onClick={isDisabled ? undefined : onClick}
cursor={isDisabled ? 'not-allowed' : 'pointer'}
>
<Radio isChecked={selected} />
<Icon name={icon} padding={'11px'} h="20px" w="100px" />
</Flex>
);
};
function CreateCustomDomainModal<T extends 'create' | 'refresh'>({
onClose,
type,
data
}: {
onClose: () => void;
type: T;
data?: T extends 'refresh'
? {
domain: string;
provider: ProviderEnum;
cnameDomain: string;
}
: undefined;
}) {
const { t } = useTranslation();
const { feConfigs } = useSystemStore();
const { copyData } = useCopyData();
const [provider, setProvider] = useState<ProviderEnum>('tencent');
const [domain, setDomain] = useState<string>('');
const [editDomain, setEditDomain] = useState<boolean>(true);
useEffect(() => {
if (type === 'refresh') {
setProvider(data?.provider || 'tencent');
setDomain(data?.domain || '');
}
}, [data, type]);
const cnameDomain = useMemo(() => {
if (type === 'refresh') {
return data?.cnameDomain!;
}
const domain = feConfigs?.customDomain?.domain?.[provider];
if (domain) {
return generateCNAMEDomain(domain);
}
return '';
}, [data?.cnameDomain, feConfigs?.customDomain?.domain, provider, type]);
const [DnsResolved, setDnsResolved] = useState<boolean>(false);
const [startDnsResolve, setStartDnsResolve] = useState<boolean>(type === 'create');
const { runAsync: checkDNSResolve } = useRequest2(
() => checkCustomDomainDNSResolve({ cnameDomain, domain }),
{
manual: true,
throttleWait: 4000,
onSuccess: (data) => {
setDnsResolved(data.success === true);
}
}
);
const { runAsync: activeDomain } = useRequest2(activeCustomDomain, {
manual: true,
onSuccess: () => onClose(),
successToast: t('common:Success')
});
const { runAsync: createDomain, loading: loadingCreatingDomain } = useRequest2(
createCustomDomain,
{
manual: true,
onSuccess: () => onClose(),
successToast: t('common:Success')
}
);
// auto trigger checkDNSResolve per 5s
useEffect(() => {
const intervalId = setInterval(() => {
if (domain && !editDomain && !DnsResolved && startDnsResolve) checkDNSResolve();
}, 5000);
return () => clearInterval(intervalId);
}, [DnsResolved, checkDNSResolve, cnameDomain, domain, editDomain, startDnsResolve]);
useEffect(() => {
if (domain && provider) {
setDnsResolved(false);
}
}, [domain, provider]);
const loading = loadingCreatingDomain;
return (
<MyModal
isOpen
onClose={onClose}
iconSrc="common/globalLine"
title={t('account:custom_domain')}
minW="800px"
>
<ModalBody>
<Box fontWeight="500" color="gray.900">
{t('account:custom_domain.provider')}
</Box>
<Flex flexDirection="row" gap="16px" w="100%" marginTop={'10px'}>
<ProviderItem
icon="support/account/customDomain/provider/tencent"
selected={provider === 'tencent'}
onClick={() => setProvider('tencent')}
isDisabled={!editDomain || type === 'refresh'}
/>
<ProviderItem
icon="support/account/customDomain/provider/aliyun"
selected={provider === 'aliyun'}
onClick={() => setProvider('aliyun')}
isDisabled={!editDomain || type === 'refresh'}
/>
<ProviderItem
icon="support/account/customDomain/provider/volcengine"
selected={provider === 'volcengine'}
onClick={() => setProvider('volcengine')}
isDisabled={!editDomain || type === 'refresh'}
/>
</Flex>
<Box marginTop={'16px'} fontSize={'sm'} color={'gray.600'}>
<Trans
i18nKey="account:custom_domain.registration_hint"
values={{ provider: t(providerMap[provider]) }}
components={{ bold: <Text as="span" fontWeight="bold" color="gray.900" /> }}
/>
</Box>
<Flex marginTop={'16px'} alignItems="center">
<InputGroup>
<Input
h="40px"
placeholder="www.example.com"
value={domain}
onChange={(e) => setDomain(e.target.value)}
isDisabled={!editDomain || type === 'refresh'}
/>
<InputRightElement width="auto" paddingRight={'8px'}>
{!editDomain && domain && startDnsResolve ? (
DnsResolved ? (
<Tag colorScheme="green" size="sm">
{t('account:custom_domain.dns_resolved')}
</Tag>
) : (
<Tag colorScheme="red" size="sm">
{t('account:custom_domain.dns_resolving')}
</Tag>
)
) : (
<></>
)}
</InputRightElement>
</InputGroup>
<Button
variant="whiteBase"
marginLeft={'8px'}
h={'40px'}
onClick={() => {
if (type === 'create') {
setEditDomain(!editDomain);
} else {
checkDNSResolve();
setStartDnsResolve(true);
}
}}
>
{type === 'create'
? editDomain
? t('common:Save')
: t('common:Edit')
: t('common:refresh')}
</Button>
</Flex>
<Flex
padding="16px"
borderRadius={'12px'}
border="1px dashed"
borderColor="#A1A1AA"
w="full"
marginTop="24px"
flexDir="column"
>
<Box fontWeight="500" color="gray.900">
{t('account:custom_domain.DNS_record')}
</Box>
<Box marginTop={'16px'} fontSize={'sm'} color={'gray.600'}>
<Trans
i18nKey="account:custom_domain.DNS_resolve_hint"
values={{ domain: cnameDomain }}
components={{ bold: <Text as="span" fontWeight="bold" color="gray.900" /> }}
/>
</Box>
<Table size="sm" marginTop={'16px'} w="full">
<Thead>
<Tr>
<Th>{t('account:custom_domain.DNS_record.type')}</Th>
<Th>TTL</Th>
<Th>{t('common:value')}</Th>
</Tr>
</Thead>
<Tbody>
<Tr>
<Td>CNAME</Td>
<Td>Auto</Td>
<Td>
<Flex alignItems="center" gap={2} justifyContent="space-between">
<Text>{cnameDomain}</Text>
<IconButton
icon={<Icon name="copy" w="14px" />}
aria-label="copy"
size="xs"
variant="ghost"
onClick={() => copyData(cnameDomain || '')}
/>
</Flex>
</Td>
</Tr>
</Tbody>
</Table>
<Link
href={
feConfigs.openAPIDocUrl ||
getDocPath('/docs/introduction/guide/team_permissions/customDomain')
}
target={'_blank'}
mt="2"
ml="2"
color={'primary.500'}
fontSize={'sm'}
>
<Flex alignItems={'center'}>
<Icon w={'17px'} h={'17px'} name="book" mr="1" />
{t('common:read_doc')}
</Flex>
</Link>
</Flex>
</ModalBody>
<ModalFooter>
<Button onClick={onClose} colorScheme="gray" variant="outline" mr={3}>
{t('common:Cancel')}
</Button>
<Button
onClick={() => {
if (type === 'create') {
return createDomain({
cnameDomain,
domain,
provider
});
}
return activeDomain(domain);
}}
colorScheme="blue"
isLoading={loading}
isDisabled={!DnsResolved}
>
{t('common:Confirm')}
</Button>
</ModalFooter>
</MyModal>
);
}
export default CreateCustomDomainModal;
import { ModalBody, Box, Input, Button, ModalFooter, Grid } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { updateCustomDomainVerifyFile } from '@/web/support/customDomain/api';
import { useForm } from 'react-hook-form';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
function domainVerifyModal({ onClose, domain }: { onClose: () => void; domain: string }) {
const { t } = useTranslation();
const { watch, handleSubmit, register } = useForm({
defaultValues: {
path: '',
content: ''
}
});
const path = watch('path');
const content = watch('content');
const { runAsync: updateVerifyFile, loading: isUpdating } = useRequest2(
updateCustomDomainVerifyFile,
{
manual: true,
onSuccess: () => {
onClose();
},
successToast: t('common:Success')
}
);
return (
<MyModal isOpen onClose={onClose} title={t('account:custom_domain.domain_verify')} minW="800px">
<ModalBody>
<Grid gridTemplateColumns="1fr 1fr" gap="16px">
<Box>
<FormLabel required>{t('account:custom_domain.domain_verify.path')}</FormLabel>
<Input {...register('path')} />
</Box>
<Box>
<FormLabel required>{t('account:custom_domain.domain_verify.content')}</FormLabel>
<Input {...register('content')} />
</Box>
</Grid>
<Box mt="2">{t('account:custom_domain.domain_verify.desc', { domain, path, content })}</Box>
</ModalBody>
<ModalFooter>
<Button
onClick={handleSubmit(() => updateVerifyFile({ domain, path, content }))}
isLoading={isUpdating}
>
{t('common:Confirm')}
</Button>
</ModalFooter>
</MyModal>
);
}
export default domainVerifyModal;
...@@ -240,7 +240,7 @@ const OffiAccount = ({ appId }: { appId: string }) => { ...@@ -240,7 +240,7 @@ const OffiAccount = ({ appId }: { appId: string }) => {
<ShowShareLinkModal <ShowShareLinkModal
shareLink={showShareLink ?? ''} shareLink={showShareLink ?? ''}
onClose={closeShowShareLinkModal} onClose={closeShowShareLinkModal}
img="/imgs/outlink/offiaccount-copylink-instruction.png" img="/imgs/outlink/offiaccount-copylink-instruction.jpg"
/> />
)} )}
</Box> </Box>
......
import React from 'react'; import React, { useMemo } from 'react';
import { Flex, Box, Button, ModalBody, Input, Link } from '@chakra-ui/react'; import { Flex, Box, Button, ModalBody, Input, Link, ModalFooter, Grid } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal'; import MyModal from '@fastgpt/web/components/common/MyModal';
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant'; import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
import type { WecomAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type'; import type { WecomAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type';
...@@ -7,12 +7,15 @@ import { useTranslation } from 'next-i18next'; ...@@ -7,12 +7,15 @@ import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { createShareChat, updateShareChat } from '@/web/support/outLink/api'; import { createShareChat, updateShareChat } from '@/web/support/outLink/api';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import BasicInfo from '../components/BasicInfo';
import { getDocPath } from '@/web/common/system/doc'; import { getDocPath } from '@/web/common/system/doc';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import { useMyStep } from '@fastgpt/web/hooks/useStep';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { format } from 'date-fns';
import { ShareLinkContainer } from '../components/showShareLinkModal';
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
const WecomEditModal = ({ const WecomEditModal = ({
appId, appId,
...@@ -25,7 +28,7 @@ const WecomEditModal = ({ ...@@ -25,7 +28,7 @@ const WecomEditModal = ({
appId: string; appId: string;
defaultData: OutLinkEditType<WecomAppType>; defaultData: OutLinkEditType<WecomAppType>;
onClose: () => void; onClose: () => void;
onCreate: (id: string) => void; onCreate: (shareId: string) => Promise<string | undefined>;
onEdit: () => void; onEdit: () => void;
isEdit?: boolean; isEdit?: boolean;
}) => { }) => {
...@@ -38,7 +41,11 @@ const WecomEditModal = ({ ...@@ -38,7 +41,11 @@ const WecomEditModal = ({
defaultValues: defaultData defaultValues: defaultData
}); });
const { runAsync: onclickCreate, loading: creating } = useRequest2( const {
runAsync: onclickCreate,
loading: creating,
data: createShareId
} = useRequest2(
(e) => (e) =>
createShareChat({ createShareChat({
...e, ...e,
...@@ -48,117 +55,214 @@ const WecomEditModal = ({ ...@@ -48,117 +55,214 @@ const WecomEditModal = ({
{ {
errorToast: t('common:create_failed'), errorToast: t('common:create_failed'),
successToast: t('common:create_success'), successToast: t('common:create_success'),
onSuccess: onCreate onSuccess: async (shareId) => {
const _id = await onCreate(shareId);
if (_id) {
setValue('_id', _id);
}
}
} }
); );
const { runAsync: onclickUpdate, loading: updating } = useRequest2((e) => updateShareChat(e), { const {
runAsync: onclickUpdate,
loading: updating,
data: updatedShareId
} = useRequest2((e) => updateShareChat(e), {
errorToast: t('common:update_failed'), errorToast: t('common:update_failed'),
successToast: t('common:update_success'), successToast: t('common:update_success'),
onSuccess: onEdit onSuccess: onEdit
}); });
const shareId = useMemo(() => createShareId || updatedShareId, [createShareId, updatedShareId]);
// 判断是否已经创建成功(有 createShareId 说明已经创建)
const isCreated = useMemo(() => !!createShareId, [createShareId]);
const isEditMode = useMemo(() => isEdit || isCreated, [isEdit, isCreated]);
const { feConfigs } = useSystemStore(); const { feConfigs } = useSystemStore();
const { MyStep, activeStep, goToNext, goToPrevious } = useMyStep({
steps: [
{
title: t('publish:wecom.create_modal.step.1')
},
{
title: t('publish:wecom.create_modal.step.2')
}
]
});
const baseUrl = useMemo(
() => feConfigs?.customApiDomain || `${location.origin}/api`,
[feConfigs?.customApiDomain]
);
return ( return (
<MyModal <MyModal
iconSrc="core/app/publish/wecom" iconSrc="core/app/publish/wecom"
title={isEdit ? t('publish:wecom.edit_modal_title') : t('publish:wecom.create_modal_title')} title={
isEditMode ? t('publish:wecom.edit_modal_title') : t('publish:wecom.create_modal_title')
}
minW={['auto', '60rem']} minW={['auto', '60rem']}
onClose={onClose}
> >
<ModalBody display={'grid'} gridTemplateColumns={['1fr', '1fr 1fr']} fontSize={'14px'} p={0}> <ModalBody fontSize={'14px'} p={8}>
<Box p={8} minH={['auto', '400px']} borderRight={'base'}> <MyStep />
<BasicInfo register={register} setValue={setValue} defaultData={defaultData} /> {activeStep === 0 && (
</Box> <>
<Flex p={8} minH={['auto', '400px']} flexDirection="column" gap={6}> <Grid
<Flex alignItems="center"> gridTemplateColumns={'200px 1fr'}
<Box color="myGray.600">{t('publish:wecom.api')}</Box> rowGap="4"
{feConfigs?.docUrl && ( mt="4"
<Link pb="24px"
href={feConfigs.openAPIDocUrl || getDocPath('/docs/use-cases/wecom-bot')} borderBottom="1px solid"
target={'_blank'} borderColor="myGray.200"
ml={2} >
color={'primary.500'} <Box color="myGray.900" fontWeight={'500'}>
fontSize={'sm'} {t('publish:basic_info')}
> </Box>
<Flex alignItems={'center'}> <Grid gridTemplateColumns={'1fr 1fr'} gap="4">
<MyIcon name="book" w={'17px'} h={'17px'} mr="1" /> <Flex flexDir={'column'} gap="2">
{t('common:read_doc')} <FormLabel required>{t('common:Name')}</FormLabel>
<Input
placeholder={t('publish:publish_name')}
maxLength={100}
{...register('name', {
required: t('common:name_is_empty')
})}
/>
</Flex> </Flex>
</Link> <Flex flexDir={'column'} gap="2">
)} <FormLabel>
</Flex> QPM
<Flex alignItems={'center'}> <QuestionTip ml={1} label={t('publish:qpm_tips')}></QuestionTip>
<FormLabel flex={'0 0 6.25rem'} required> </FormLabel>
Corp ID <Input
</FormLabel> max={1000}
<Input {...register('limit.QPM', {
placeholder="Corp ID" min: 0,
{...register('app.CorpId', { max: 1000,
required: true valueAsNumber: true,
})} required: t('publish:qpm_is_empty')
/> })}
</Flex> />
<Flex alignItems={'center'}> </Flex>
<FormLabel flex={'0 0 6.25rem'} required> <Flex flexDir={'column'} gap="2">
Agent ID <FormLabel>
</FormLabel> {t('common:support.outlink.Max usage points')}
<Input <QuestionTip
placeholder="Agent ID" ml={1}
{...register('app.AgentId', { label={t('common:support.outlink.Max usage points tip')}
required: true ></QuestionTip>
})} </FormLabel>
/> <Input
</Flex> {...register('limit.maxUsagePoints', {
<Flex alignItems={'center'}> min: -1,
<FormLabel flex={'0 0 6.25rem'} required> max: 10000000,
Secret valueAsNumber: true,
</FormLabel> required: true
<Input })}
placeholder="Secret" />
{...register('app.SuiteSecret', { </Flex>
required: true <Flex flexDir={'column'} gap="2">
})} <FormLabel>{t('common:expired_time')}</FormLabel>
/> <Input
</Flex> type="datetime-local"
<Flex alignItems={'center'}> defaultValue={
<FormLabel flex={'0 0 6.25rem'} required> defaultData.limit?.expiredTime
Token ? formatTime2YMDHM(defaultData.limit?.expiredTime)
</FormLabel> : ''
<Input }
placeholder="Token" onChange={(e) => {
{...register('app.CallbackToken', { setValue('limit.expiredTime', new Date(e.target.value));
required: true }}
})} />
/> </Flex>
</Flex> </Grid>
<Flex alignItems={'center'}> </Grid>
<FormLabel flex={'0 0 6.25rem'} required>
AES Key
</FormLabel>
<Input
placeholder="AES Key"
{...register('app.CallbackEncodingAesKey', { required: true })}
/>
</Flex>
<Box flex={1}></Box> <Grid gridTemplateColumns={'200px 1fr'} rowGap="4" mt="24px">
<Flex h="min">
<Box color="myGray.900" fontWeight="500">
{t('publish:wecom.api')}
</Box>
<Flex>
{feConfigs?.docUrl && (
<Link
href={getDocPath('/docs/use-cases/external-integration/wecom')}
target={'_blank'}
ml={2}
color={'primary.500'}
fontSize={'sm'}
>
<Flex alignItems={'center'}>
<MyIcon name="book" w={'17px'} h={'17px'} mr="1" />
{t('common:read_doc')}
</Flex>
</Link>
)}
</Flex>
</Flex>
<Flex justifyContent={'end'}> <Grid gridTemplateColumns={'1fr 1fr'} columnGap="4">
<Button variant={'whiteBase'} mr={3} onClick={onClose}> <Flex flexDir={'column'} gap="2">
{t('common:Close')} <FormLabel required>Token</FormLabel>
</Button> <Input
<Button placeholder="Token"
isLoading={creating || updating} {...register('app.CallbackToken', {
onClick={submitShareChat((data) => required: true
isEdit ? onclickUpdate(data) : onclickCreate(data) })}
)} />
> </Flex>
{t('common:Confirm')} <Flex flexDir={'column'} gap="2">
</Button> <FormLabel required>AES Key</FormLabel>
</Flex> <Input
</Flex> placeholder="AES Key"
{...register('app.CallbackEncodingAesKey', { required: true })}
/>
</Flex>
</Grid>
</Grid>
</>
)}
{activeStep === 1 && (
<Box mt="4">
<ShareLinkContainer
shareLink={`${baseUrl}/support/outLink/wecom/${shareId}`}
img="/imgs/outlink/wecom-copylink-instruction.png"
defaultDomain={false}
showCustomDomainSelector={true}
></ShareLinkContainer>
</Box>
)}
</ModalBody> </ModalBody>
<ModalFooter>
{activeStep === 1 && (
<Button
variant={'whiteBase'}
mr={3}
onClick={() => {
goToPrevious();
}}
>
{t('common:last_step')}
</Button>
)}
<Button
isLoading={creating || updating}
onClick={() => {
if (activeStep === 0) {
submitShareChat((data) =>
(isEditMode ? onclickUpdate(data) : onclickCreate(data)).then(() => goToNext())
)();
} else {
onClose();
}
}}
>
{t('common:Confirm')}
</Button>
</ModalFooter>
</MyModal> </MyModal>
); );
}; };
......
...@@ -10,7 +10,8 @@ import { ...@@ -10,7 +10,8 @@ import {
Th, Th,
Td, Td,
Tbody, Tbody,
useDisclosure useDisclosure,
Link
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { useLoading } from '@fastgpt/web/hooks/useLoading'; import { useLoading } from '@fastgpt/web/hooks/useLoading';
...@@ -19,13 +20,15 @@ import { formatTimeToChatTime } from '@fastgpt/global/common/string/time'; ...@@ -19,13 +20,15 @@ import { formatTimeToChatTime } from '@fastgpt/global/common/string/time';
import { defaultOutLinkForm } from '@/web/core/app/constants'; import { defaultOutLinkForm } from '@/web/core/app/constants';
import type { WecomAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d'; import type { WecomAppType, OutLinkEditType } from '@fastgpt/global/support/outLink/type.d';
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant'; import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
import { useTranslation } from 'next-i18next'; import { Trans, useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useSystemStore } from '@/web/common/system/useSystemStore';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import MyMenu from '@fastgpt/web/components/common/MyMenu'; import MyMenu from '@fastgpt/web/components/common/MyMenu';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { getDocPath } from '@/web/common/system/doc';
import { listCustomDomain } from '@/web/support/customDomain/api';
const WecomEditModal = dynamic(() => import('./WecomEditModal')); const WecomEditModal = dynamic(() => import('./WecomEditModal'));
const ShowShareLinkModal = dynamic(() => import('../components/showShareLinkModal')); const ShowShareLinkModal = dynamic(() => import('../components/showShareLinkModal'));
...@@ -58,31 +61,64 @@ const Wecom = ({ appId }: { appId: string }) => { ...@@ -58,31 +61,64 @@ const Wecom = ({ appId }: { appId: string }) => {
const [showShareLink, setShowShareLink] = useState<string | null>(null); const [showShareLink, setShowShareLink] = useState<string | null>(null);
const { data: customDomains = [] } = useRequest2(listCustomDomain, {
manual: false,
refreshOnWindowFocus: true
});
return ( return (
<Box position={'relative'} pt={3} px={5} minH={'50vh'}> <Box position={'relative'} pt={3} px={5} minH={'50vh'}>
<Flex justifyContent={'space-between'} flexDirection="row"> <Flex justifyContent={'space-between'} flexDirection="row">
<Box fontWeight={'bold'} fontSize={['md', 'lg']}> <Flex alignItems={'center'}>
{t('publish:wecom.title')} <Box fontWeight={'bold'} fontSize={['md', 'lg']}>
</Box> {t('publish:wecom.title')}
<Button </Box>
variant={'primary'} {feConfigs?.docUrl && (
colorScheme={'blue'} <Link
size={['sm', 'md']} href={getDocPath('/docs/use-cases/external-integration/wecom')}
leftIcon={<MyIcon name={'common/addLight'} w="1.25rem" color="white" />} target={'_blank'}
ml={3} ml={2}
{...(shareChatList.length >= 10 color={'primary.500'}
? { fontSize={'sm'}
isDisabled: true, >
title: t('common:core.app.share.Amount limit tip') <Flex alignItems={'center'}>
} <MyIcon name="book" w={'17px'} h={'17px'} mr="1" />
: {})} {t('common:read_doc')}
onClick={() => { </Flex>
setEditWecomData(defaultOutLinkForm as any); // HACK </Link>
setIsEdit(false); )}
}} </Flex>
> <Flex gap={3}>
{t('common:add_new')} {feConfigs.customDomain?.enable && (
</Button> <Button
variant={'whitePrimary'}
size={['sm', 'md']}
onClick={() => {
window.open('/account/customDomain', '_blank');
}}
>
{t('publish:custom_domain_management')}
</Button>
)}
<Button
variant={'primary'}
colorScheme={'blue'}
size={['sm', 'md']}
leftIcon={<MyIcon name={'common/addLight'} w="1.25rem" color="white" />}
{...(shareChatList.length >= 10
? {
isDisabled: true,
title: t('common:core.app.share.Amount limit tip')
}
: {})}
onClick={() => {
setEditWecomData(defaultOutLinkForm as any); // HACK
setIsEdit(false);
}}
>
{t('common:add_new')}
</Button>
</Flex>
</Flex> </Flex>
<TableContainer mt={3}> <TableContainer mt={3}>
<Table variant={'simple'} w={'100%'} overflowX={'auto'} fontSize={'sm'}> <Table variant={'simple'} w={'100%'} overflowX={'auto'} fontSize={'sm'}>
...@@ -199,14 +235,39 @@ const Wecom = ({ appId }: { appId: string }) => { ...@@ -199,14 +235,39 @@ const Wecom = ({ appId }: { appId: string }) => {
<WecomEditModal <WecomEditModal
appId={appId} appId={appId}
defaultData={editWecomData} defaultData={editWecomData}
onCreate={() => Promise.all([refetchShareChatList(), setEditWecomData(undefined)])} onCreate={async (shareId: string) => {
onEdit={() => Promise.all([refetchShareChatList(), setEditWecomData(undefined)])} const newList = await refetchShareChatList();
const newItem = newList.find((item) => item.shareId === shareId);
return newItem?._id;
}}
onEdit={() => refetchShareChatList()}
onClose={() => setEditWecomData(undefined)} onClose={() => setEditWecomData(undefined)}
isEdit={isEdit} isEdit={isEdit}
/> />
)} )}
{shareChatList.length === 0 && !isFetching && ( {shareChatList.length === 0 && !isFetching && (
<EmptyTip text={t('common:core.app.share.Not share link')}> </EmptyTip> <EmptyTip
{...(feConfigs.customDomain?.enable && customDomains.length > 0
? { text: '' }
: {
text: (
<Trans
i18nKey="app:publish_channel.wecom.empty"
components={{
a: (
<Link
color="primary.600"
key="link"
href="/account/customDomain"
target="_blank"
rel="noopener noreferrer"
/>
)
}}
/>
)
})}
/>
)} )}
<Loading loading={isFetching} fixed={false} /> <Loading loading={isFetching} fixed={false} />
{showShareLinkModalOpen && ( {showShareLinkModalOpen && (
...@@ -214,6 +275,8 @@ const Wecom = ({ appId }: { appId: string }) => { ...@@ -214,6 +275,8 @@ const Wecom = ({ appId }: { appId: string }) => {
shareLink={showShareLink ?? ''} shareLink={showShareLink ?? ''}
onClose={closeShowShareLinkModal} onClose={closeShowShareLinkModal}
img="/imgs/outlink/wecom-copylink-instruction.png" img="/imgs/outlink/wecom-copylink-instruction.png"
defaultDomain={false}
showCustomDomainSelector={true}
/> />
)} )}
</Box> </Box>
......
...@@ -4,45 +4,171 @@ import MyModal from '@fastgpt/web/components/common/MyModal'; ...@@ -4,45 +4,171 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import MyImage from '@fastgpt/web/components/common/Image/MyImage'; import MyImage from '@fastgpt/web/components/common/Image/MyImage';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { listCustomDomain } from '@/web/support/customDomain/api';
import { useState, useMemo, useEffect } from 'react';
import MySelect from '@fastgpt/web/components/common/MySelect';
export type ShowShareLinkModalProps = { export type ShowShareLinkModalProps = {
shareLink: string; shareLink: string;
onClose: () => void; onClose: () => void;
img: string; img: string;
defaultDomain?: boolean;
showCustomDomainSelector?: boolean;
}; };
function ShowShareLinkModal({ shareLink, onClose, img }: ShowShareLinkModalProps) { export const ShareLinkContainer = ({
shareLink,
img,
defaultDomain = true,
showCustomDomainSelector = false
}: {
shareLink: string;
img: string;
defaultDomain?: boolean;
showCustomDomainSelector?: boolean;
}) => {
const { copyData } = useCopyData(); const { copyData } = useCopyData();
const { t } = useTranslation(); const { t } = useTranslation();
const [customDomain, setCustomDomain] = useState<string | undefined>(undefined);
const { data: customDomainList = [] } = useRequest2(listCustomDomain, {
manual: !showCustomDomainSelector
});
// 从 shareLink 中提取原始域名
const originalDomain = useMemo(() => {
try {
const url = new URL(shareLink);
return url.origin;
} catch {
return '';
}
}, [shareLink]);
// 计算显示的分享链接(使用自定义域名替换原始域名)
const displayShareLink = useMemo(() => {
if (!customDomain || !originalDomain) {
return shareLink;
}
return shareLink.replace(originalDomain, `https://${customDomain}`);
}, [shareLink, customDomain, originalDomain]);
// 处理域名选择选项
const domainOptions = useMemo(() => {
const defaultOption = [
{
label: t('publish:use_default_domain'),
value: ''
}
];
// 只显示已激活的自定义域名
const activeDomains = customDomainList
.filter((item) => item.status === 'active')
.map((item) => ({
label: item.domain,
value: item.domain
}));
return activeDomains.length === 0
? [...defaultOption]
: [...(defaultDomain ? defaultOption : []), ...activeDomains];
}, [customDomainList, defaultDomain, t]);
// 当 defaultDomain=false 时,自动选择第一个自定义域名
useEffect(() => {
if (!defaultDomain && domainOptions.length > 0 && customDomain === undefined) {
setCustomDomain(domainOptions[0].value || undefined);
}
}, [defaultDomain, domainOptions, customDomain]);
return ( return (
<MyModal onClose={onClose} title={t('publish:show_share_link_modal_title')}> <>
<ModalBody> {/* 自定义域名选择器 */}
<Box borderRadius={'md'} bg={'myGray.100'} overflow={'hidden'} fontSize={'sm'}> {showCustomDomainSelector && domainOptions.length > 1 && (
<Flex <Box mb={4}>
p={3} <MySelect
bg={'myWhite.500'} value={customDomain || ''}
border="base" list={domainOptions}
borderTopLeftRadius={'md'} onChange={(value) => setCustomDomain(value || undefined)}
borderTopRightRadius={'md'} />
>
<Box flex={1}>{t('publish:copy_link_hint')}</Box>
<MyIcon
name={'copy'}
w={'16px'}
color={'myGray.600'}
cursor={'pointer'}
_hover={{ color: 'primary.500' }}
onClick={() => copyData(shareLink)}
/>
</Flex>
<Box whiteSpace={'pre'} p={3} overflowX={'auto'}>
{shareLink}
</Box>
</Box> </Box>
<Box mt="4" borderRadius="0.5rem" border="1px" borderStyle="solid" borderColor="myGray.200"> )}
<MyImage src={img} borderRadius="0.5rem" alt="" />
<Box borderRadius={'md'} bg={'myGray.100'} overflow={'hidden'} fontSize={'sm'}>
<Flex
p={3}
bg={'myWhite.500'}
border="base"
borderTopLeftRadius={'md'}
borderTopRightRadius={'md'}
>
<Box flex={1}>{t('publish:copy_link_hint')}</Box>
<MyIcon
name={'copy'}
w={'16px'}
color={'myGray.600'}
cursor={'pointer'}
_hover={{ color: 'primary.500' }}
onClick={() => copyData(displayShareLink)}
/>
</Flex>
<Box whiteSpace={'pre'} p={3} overflowX={'auto'}>
{displayShareLink}
</Box> </Box>
</Box>
<Box mt="4" borderRadius="0.5rem" border="1px" borderStyle="solid" borderColor="myGray.200">
<MyImage src={img} borderRadius="0.5rem" alt="" />
</Box>
{/*<Box borderRadius={'md'} bg={'myGray.100'} overflow={'hidden'} fontSize={'sm'} mt="4">
<Flex
p={3}
bg={'myWhite.500'}
border="base"
borderTopLeftRadius={'md'}
borderTopRightRadius={'md'}
>
<Box flex="1">{t('publish:ip_whitelist')}</Box>
<MyIcon
name={'copy'}
w={'16px'}
color={'myGray.600'}
cursor={'pointer'}
_hover={{ color: 'primary.500' }}
onClick={() => copyData(feConfigs?.ip_whitelist || '')}
/>
</Flex>
<Box p={3} wordBreak={'break-all'}>
{feConfigs.ip_whitelist}
</Box>
</Box>*/}
</>
);
};
function ShowShareLinkModal({
shareLink,
onClose,
img,
defaultDomain,
showCustomDomainSelector
}: ShowShareLinkModalProps) {
const { t } = useTranslation();
return (
<MyModal onClose={onClose} title={t('publish:show_share_link_modal_title')}>
<ModalBody>
<ShareLinkContainer
shareLink={shareLink}
img={img}
defaultDomain={defaultDomain}
showCustomDomainSelector={showCustomDomainSelector}
/>
</ModalBody> </ModalBody>
</MyModal> </MyModal>
); );
......
...@@ -528,7 +528,7 @@ const CreateButton = ({ appType }: { appType: AppTypeEnum | 'all' }) => { ...@@ -528,7 +528,7 @@ const CreateButton = ({ appType }: { appType: AppTypeEnum | 'all' }) => {
> >
<Box <Box
as="img" as="img"
src={getWebReqUrl('/imgs/app/createButton.png')} src={getWebReqUrl('/imgs/app/createButton.jpg')}
alt="operational advertisement" alt="operational advertisement"
width="100%" width="100%"
maxW="100%" maxW="100%"
......
import AccountContainer from '@/pageComponents/account/AccountContainer';
import { serviceSideProps } from '@/web/common/i18n/utils';
import { deleteCustomDomain, listCustomDomain } from '@/web/support/customDomain/api';
import {
Box,
Button,
Flex,
Table,
TableContainer,
Tbody,
Td,
Thead,
Tr,
useDisclosure
} from '@chakra-ui/react';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useTranslation } from 'next-i18next';
import dynamic from 'next/dynamic';
import { providerMap, customDomainStatusMap } from '@/web/support/customDomain/const';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import MyLoading from '@fastgpt/web/components/common/MyLoading';
import type { CustomDomainType } from '@fastgpt/global/support/customDomain/type';
import { useState, useMemo } from 'react';
import { useUserStore } from '@/web/support/user/useUserStore';
import { StandardSubLevelEnum } from '@fastgpt/global/support/wallet/sub/constants';
import { useRouter } from 'next/router';
import Tag from '@fastgpt/web/components/common/Tag';
const CreateCustomDomainModal = dynamic(
() => import('@/pageComponents/account/customDomain/createModal')
);
/** unimplemented */
// const DomainVerifyModal = dynamic(
// () => import('@/pageComponents/account/customDomain/domainVerifyModal')
// );
const CustomDomain = () => {
const { t } = useTranslation();
const router = useRouter();
const { teamPlanStatus } = useUserStore();
const {
data: customDomainList,
refreshAsync: refreshCustomDomainList,
loading: loadingCustomDomainList
} = useRequest2(listCustomDomain, {
manual: false
});
const {
isOpen: isOpenCreateModal,
onOpen: onOpenCreateModal,
onClose: onCloseCreateModal
} = useDisclosure();
// const {
// isOpen: isOpenDomainVerify,
// onOpen: onOpenDomainVerify,
// onClose: onCloseDomainVerify
// } = useDisclosure();
const { runAsync: onDelete, loading: loadingDelete } = useRequest2(deleteCustomDomain, {
manual: true,
successToast: t('common:Success'),
onSuccess: () => refreshCustomDomainList()
});
const { ConfirmModal, openConfirm } = useConfirm({
content: t('account:custom_domain.delete_confirm')
});
const [editDomain, setEditDomain] = useState<CustomDomainType | undefined>(undefined);
// 检查用户是否有 advanced 套餐
const isAdvancedPlan = useMemo(() => {
const currentLevel = teamPlanStatus?.standard?.currentSubLevel;
if (!currentLevel) return false;
return currentLevel === StandardSubLevelEnum.advanced;
}, [teamPlanStatus?.standard?.currentSubLevel]);
return (
<>
<AccountContainer>
<Flex flexDirection="column" h="100%" padding="24px">
<TableContainer flex="1" display="flex" flexDirection="column">
{loadingCustomDomainList ? <MyLoading /> : null}
<Flex justifyContent="space-between" alignItems="center" w="100%">
<Box fontSize="20px" fontWeight="500">
{t('account:custom_domain')}
{customDomainList?.length ? (
`: (${customDomainList.length}/${teamPlanStatus?.standardConstants?.customDomain})`
) : (
<></>
)}
</Box>
<Button
variant="whitePrimaryOutline"
onClick={onOpenCreateModal}
isDisabled={!isAdvancedPlan}
>
{t('common:Add')}
</Button>
</Flex>
<Table marginTop="12px">
<Thead>
<Tr>
<Td>{t('account:custom_domain.domain')}</Td>
<Td>CNAME</Td>
<Td>{t('account:custom_domain.provider')}</Td>
<Td>{t('common:Status')}</Td>
<Td>{t('common:Action')}</Td>
</Tr>
</Thead>
<Tbody>
{customDomainList?.length ? (
customDomainList.map((customDomain) => (
<Tr key={customDomain.domain}>
<Td>{customDomain.domain}</Td>
<Td>{customDomain.cnameDomain}</Td>
<Td>{t(providerMap[customDomain.provider])}</Td>
<Td>
{customDomain.status === 'active' ? (
<Tag colorSchema="green">
{t(customDomainStatusMap[customDomain.status])}
</Tag>
) : (
<Tag colorSchema="red">
{t(customDomainStatusMap[customDomain.status])}
</Tag>
)}
</Td>
<Td>
<Flex gap="2">
<Button
variant="whiteDanger"
onClick={() => {
return openConfirm(() => onDelete(customDomain.domain))();
}}
>
{t('common:Delete')}
</Button>
{customDomain.status === 'inactive' ? (
<Button
variant="whitePrimary"
onClick={() => {
setEditDomain(customDomain);
onOpenCreateModal();
}}
>
{t('common:Edit')}
</Button>
) : (
<></>
// <Button
// variant="whitePrimary"
// onClick={() => {
// setEditDomain(customDomain);
// onOpenDomainVerify();
// }}
// >
// {t('account:custom_domain.domain_verify')}
// </Button>
)}
</Flex>
</Td>
</Tr>
))
) : (
<Tr h="100%">
<Td colSpan={5} textAlign="center" h="100%">
<Flex
h="100%"
alignItems="center"
justifyContent="center"
minH="400px"
flexDirection="column"
gap={4}
>
<EmptyTip
text={
!isAdvancedPlan && (
<Flex flexDir="column" alignItems="center">
<Box>{t('account:upgrade_to_use_custom_domain')}</Box>
<Button
mt="4"
variant="primary"
onClick={() => router.push('/price')}
size="md"
>
{t('account:upgrade_plan')}
</Button>
</Flex>
)
}
/>
</Flex>
</Td>
</Tr>
)}
</Tbody>
</Table>
</TableContainer>
</Flex>
</AccountContainer>
<ConfirmModal />
{isOpenCreateModal && (
<CreateCustomDomainModal
onClose={() => {
onCloseCreateModal();
refreshCustomDomainList();
setEditDomain(undefined);
}}
type={editDomain ? 'refresh' : 'create'}
data={editDomain!}
/>
)}
{/*{isOpenDomainVerify && editDomain?.domain && (
<DomainVerifyModal
domain={editDomain?.domain}
onClose={() => {
onCloseDomainVerify();
setEditDomain(undefined);
}}
/>
)}*/}
</>
);
};
export default CustomDomain;
export async function getServerSideProps(content: any) {
return {
props: {
...(await serviceSideProps(content, ['account']))
}
};
}
...@@ -9,7 +9,7 @@ import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; ...@@ -9,7 +9,7 @@ import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { type DatasetCollectionItemType } from '@fastgpt/global/core/dataset/type'; import { type DatasetCollectionItemType } from '@fastgpt/global/core/dataset/type';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { collectionTagsToTagLabel } from '@fastgpt/service/core/dataset/collection/utils'; import { collectionTagsToTagLabel } from '@fastgpt/service/core/dataset/collection/utils';
import { getVectorCountByCollectionId } from '@fastgpt/service/common/vectorDB/controller'; import { getVectorCount } from '@fastgpt/service/common/vectorDB/controller';
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema'; import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
import { readFromSecondary } from '@fastgpt/service/common/mongo/utils'; import { readFromSecondary } from '@fastgpt/service/common/mongo/utils';
import { getS3DatasetSource } from '@fastgpt/service/common/s3/sources/dataset'; import { getS3DatasetSource } from '@fastgpt/service/common/s3/sources/dataset';
...@@ -38,7 +38,11 @@ async function handler(req: NextApiRequest): Promise<DatasetCollectionItemType> ...@@ -38,7 +38,11 @@ async function handler(req: NextApiRequest): Promise<DatasetCollectionItemType>
const [file, indexAmount, errorCount] = await Promise.all([ const [file, indexAmount, errorCount] = await Promise.all([
fileId ? getS3DatasetSource().getFileMetadata(fileId) : undefined, fileId ? getS3DatasetSource().getFileMetadata(fileId) : undefined,
getVectorCountByCollectionId(collection.teamId, collection.datasetId, collection._id), getVectorCount({
teamId: collection.teamId,
datasetId: collection.datasetId,
collectionId: collection._id
}),
MongoDatasetTraining.countDocuments( MongoDatasetTraining.countDocuments(
{ {
teamId: collection.teamId, teamId: collection.teamId,
......
...@@ -21,7 +21,7 @@ export type OutLinkUpdateQuery = {}; ...@@ -21,7 +21,7 @@ export type OutLinkUpdateQuery = {};
// } // }
export type OutLinkUpdateBody = OutLinkEditType; export type OutLinkUpdateBody = OutLinkEditType;
export type OutLinkUpdateResponse = {}; export type OutLinkUpdateResponse = string;
async function handler( async function handler(
req: ApiRequestProps<OutLinkUpdateBody, OutLinkUpdateQuery> req: ApiRequestProps<OutLinkUpdateBody, OutLinkUpdateQuery>
...@@ -44,7 +44,7 @@ async function handler( ...@@ -44,7 +44,7 @@ async function handler(
per: ManagePermissionVal per: ManagePermissionVal
}); });
await MongoOutLink.findByIdAndUpdate(_id, { const doc = await MongoOutLink.findByIdAndUpdate(_id, {
name, name,
responseDetail, responseDetail,
showRawSource, showRawSource,
...@@ -66,6 +66,6 @@ async function handler( ...@@ -66,6 +66,6 @@ async function handler(
} }
}); });
})(); })();
return {}; return doc?.shareId!;
} }
export default NextAPI(handler); export default NextAPI(handler);
...@@ -125,7 +125,7 @@ export const useSystemStore = create<State>()( ...@@ -125,7 +125,7 @@ export const useSystemStore = create<State>()(
return null; return null;
}, },
gitStar: 25000, gitStar: 26500,
async loadGitStar() { async loadGitStar() {
if (!get().feConfigs?.show_git) return; if (!get().feConfigs?.show_git) return;
try { try {
...@@ -255,6 +255,8 @@ export const useSystemStore = create<State>()( ...@@ -255,6 +255,8 @@ export const useSystemStore = create<State>()(
{ {
name: 'globalStore', name: 'globalStore',
partialize: (state) => ({ partialize: (state) => ({
gitStar: state.gitStar,
loginStore: state.loginStore, loginStore: state.loginStore,
initDataBufferId: state.initDataBufferId, initDataBufferId: state.initDataBufferId,
feConfigs: state.feConfigs, feConfigs: state.feConfigs,
......
import type {
CreateCustomDomainBody,
CustomDomainType
} from '@fastgpt/global/support/customDomain/type';
import { DELETE, GET, POST } from '@/web/common/api/request';
export const listCustomDomain = () => GET<CustomDomainType[]>('/proApi/support/customDomain/list');
export const checkCustomDomainDNSResolve = (props: { domain: string; cnameDomain: string }) =>
POST<{ success: boolean; message: string }>(
'/proApi/support/customDomain/checkDNSResolve',
props
);
export const deleteCustomDomain = (domain: string) =>
DELETE<{ success: boolean; message: string }>('/proApi/support/customDomain/delete', {
domain
});
export const createCustomDomain = (props: CreateCustomDomainBody) =>
POST<{ success: boolean; message: string }>('/proApi/support/customDomain/create', props);
export const activeCustomDomain = (domain: string) =>
POST<{ success: boolean; message: string }>('/proApi/support/customDomain/active', {
domain
});
// TODO: verify files
export const updateCustomDomainVerifyFile = (props: {
domain: string;
path: string;
content: string;
}) =>
POST<{ success: boolean; message: string }>(
'/proApi/support/customDomain/updateVerifyFile',
props
);
import type {
ProviderEnum,
CustomDomainStatusEnum
} from '@fastgpt/global/support/customDomain/type';
import type { t } from 'i18next';
export const providerMap = {
aliyun: 'account:custom_domain.provider.aliyun',
tencent: 'account:custom_domain.provider.tencent',
volcengine: 'account:custom_domain.provider.volcengine'
} satisfies Record<ProviderEnum, Parameters<typeof t>[0]>;
export const customDomainStatusMap = {
active: 'account:custom_domain.status.active',
inactive: 'account:custom_domain.status.inactive'
} satisfies Record<CustomDomainStatusEnum, Parameters<typeof t>[0]>;
...@@ -6,8 +6,7 @@ import { ...@@ -6,8 +6,7 @@ import {
mockVectorInit, mockVectorInit,
mockGetVectorDataByTime, mockGetVectorDataByTime,
mockGetVectorCountByTeamId, mockGetVectorCountByTeamId,
mockGetVectorCountByDatasetId, mockGetVectorCount,
mockGetVectorCountByCollectionId,
resetVectorMocks resetVectorMocks
} from '@test/mocks/common/vector'; } from '@test/mocks/common/vector';
import { mockGetVectorsByText } from '@test/mocks/core/ai/embedding'; import { mockGetVectorsByText } from '@test/mocks/core/ai/embedding';
...@@ -18,8 +17,7 @@ import { ...@@ -18,8 +17,7 @@ import {
recallFromVectorStore, recallFromVectorStore,
getVectorDataByTime, getVectorDataByTime,
getVectorCountByTeamId, getVectorCountByTeamId,
getVectorCountByDatasetId, getVectorCount,
getVectorCountByCollectionId,
insertDatasetDataVector, insertDatasetDataVector,
deleteDatasetDataVector deleteDatasetDataVector
} from '@fastgpt/service/common/vectorDB/controller'; } from '@fastgpt/service/common/vectorDB/controller';
...@@ -123,48 +121,42 @@ describe('VectorDB Controller', () => { ...@@ -123,48 +121,42 @@ describe('VectorDB Controller', () => {
const result = await getVectorCountByTeamId('team_123'); const result = await getVectorCountByTeamId('team_123');
expect(result).toBe(150); expect(result).toBe(150);
expect(mockGetVectorCountByTeamId).not.toHaveBeenCalled(); expect(mockGetVectorCount).not.toHaveBeenCalled();
}); });
it('should fetch from Vector and cache if no cache exists', async () => { it('should fetch from Vector and cache if no cache exists', async () => {
mockGetRedisCache.mockResolvedValue(null); mockGetRedisCache.mockResolvedValue(null);
mockGetVectorCountByTeamId.mockResolvedValue(200); mockGetVectorCount.mockResolvedValue(200);
const result = await getVectorCountByTeamId('team_456'); const result = await getVectorCountByTeamId('team_456');
expect(result).toBe(200); expect(result).toBe(200);
expect(mockGetVectorCountByTeamId).toHaveBeenCalledWith('team_456'); expect(mockGetVectorCount).toHaveBeenCalledWith({ teamId: 'team_456' });
}); });
it('should handle undefined cache value', async () => { it('should handle undefined cache value', async () => {
mockGetRedisCache.mockResolvedValue(undefined); mockGetRedisCache.mockResolvedValue(undefined);
mockGetVectorCountByTeamId.mockResolvedValue(50); mockGetVectorCount.mockResolvedValue(50);
const result = await getVectorCountByTeamId('team_789'); const result = await getVectorCountByTeamId('team_789');
expect(result).toBe(50); expect(result).toBe(50);
expect(mockGetVectorCountByTeamId).toHaveBeenCalled(); expect(mockGetVectorCount).toHaveBeenCalledWith({ teamId: 'team_789' });
}); });
}); });
describe('getVectorCountByDatasetId', () => { describe('getVectorCount', () => {
it('should call Vector.getVectorCountByDatasetId', async () => { it('should call Vector.getVectorCount', async () => {
const result = await getVectorCountByDatasetId('team_1', 'dataset_1'); const result = await getVectorCount({ teamId: 'team_1', datasetId: 'dataset_1' });
expect(mockGetVectorCountByDatasetId).toHaveBeenCalledWith('team_1', 'dataset_1'); expect(mockGetVectorCount).toHaveBeenCalledWith({
teamId: 'team_1',
datasetId: 'dataset_1'
});
expect(result).toBe(50); expect(result).toBe(50);
}); });
}); });
describe('getVectorCountByCollectionId', () => {
it('should call Vector.getVectorCountByCollectionId', async () => {
const result = await getVectorCountByCollectionId('team_1', 'dataset_1', 'col_1');
expect(mockGetVectorCountByCollectionId).toHaveBeenCalledWith('team_1', 'dataset_1', 'col_1');
expect(result).toBe(25);
});
});
describe('insertDatasetDataVector', () => { describe('insertDatasetDataVector', () => {
const mockModel = { const mockModel = {
model: 'text-embedding-ada-002', model: 'text-embedding-ada-002',
......
...@@ -26,9 +26,7 @@ export const mockGetVectorDataByTime = vi.fn().mockResolvedValue([ ...@@ -26,9 +26,7 @@ export const mockGetVectorDataByTime = vi.fn().mockResolvedValue([
export const mockGetVectorCountByTeamId = vi.fn().mockResolvedValue(100); export const mockGetVectorCountByTeamId = vi.fn().mockResolvedValue(100);
export const mockGetVectorCountByDatasetId = vi.fn().mockResolvedValue(50); export const mockGetVectorCount = vi.fn().mockResolvedValue(50);
export const mockGetVectorCountByCollectionId = vi.fn().mockResolvedValue(25);
const MockVectorCtrl = vi.fn().mockImplementation(() => ({ const MockVectorCtrl = vi.fn().mockImplementation(() => ({
init: mockVectorInit, init: mockVectorInit,
...@@ -37,8 +35,7 @@ const MockVectorCtrl = vi.fn().mockImplementation(() => ({ ...@@ -37,8 +35,7 @@ const MockVectorCtrl = vi.fn().mockImplementation(() => ({
embRecall: mockVectorEmbRecall, embRecall: mockVectorEmbRecall,
getVectorDataByTime: mockGetVectorDataByTime, getVectorDataByTime: mockGetVectorDataByTime,
getVectorCountByTeamId: mockGetVectorCountByTeamId, getVectorCountByTeamId: mockGetVectorCountByTeamId,
getVectorCountByDatasetId: mockGetVectorCountByDatasetId, getVectorCount: mockGetVectorCount
getVectorCountByCollectionId: mockGetVectorCountByCollectionId
})); }));
// Mock PgVectorCtrl // Mock PgVectorCtrl
...@@ -74,6 +71,5 @@ export const resetVectorMocks = () => { ...@@ -74,6 +71,5 @@ export const resetVectorMocks = () => {
mockVectorInit.mockClear(); mockVectorInit.mockClear();
mockGetVectorDataByTime.mockClear(); mockGetVectorDataByTime.mockClear();
mockGetVectorCountByTeamId.mockClear(); mockGetVectorCountByTeamId.mockClear();
mockGetVectorCountByDatasetId.mockClear(); mockGetVectorCount.mockClear();
mockGetVectorCountByCollectionId.mockClear();
}; };
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment