diff --git a/docSite/assets/imgs/dataset3.png b/docSite/assets/imgs/dataset3.png new file mode 100644 index 000000000..c36b92e81 Binary files /dev/null and b/docSite/assets/imgs/dataset3.png differ diff --git a/docSite/assets/imgs/dataset4.png b/docSite/assets/imgs/dataset4.png new file mode 100644 index 000000000..3230c015a Binary files /dev/null and b/docSite/assets/imgs/dataset4.png differ diff --git a/docSite/assets/imgs/faq1.png b/docSite/assets/imgs/faq1.png new file mode 100644 index 000000000..6a5223c76 Binary files /dev/null and b/docSite/assets/imgs/faq1.png differ diff --git a/docSite/assets/imgs/faq2.png b/docSite/assets/imgs/faq2.png new file mode 100644 index 000000000..a05e9ebd2 Binary files /dev/null and b/docSite/assets/imgs/faq2.png differ diff --git a/docSite/assets/imgs/faq3.png b/docSite/assets/imgs/faq3.png new file mode 100644 index 000000000..c9f7252b5 Binary files /dev/null and b/docSite/assets/imgs/faq3.png differ diff --git a/docSite/assets/imgs/other1.png b/docSite/assets/imgs/other1.png new file mode 100644 index 000000000..7b4ff777a Binary files /dev/null and b/docSite/assets/imgs/other1.png differ diff --git a/docSite/assets/imgs/other2.png b/docSite/assets/imgs/other2.png new file mode 100644 index 000000000..c89b7612a Binary files /dev/null and b/docSite/assets/imgs/other2.png differ diff --git a/docSite/assets/imgs/other3.png b/docSite/assets/imgs/other3.png new file mode 100644 index 000000000..d06180a56 Binary files /dev/null and b/docSite/assets/imgs/other3.png differ diff --git a/docSite/assets/imgs/quizApp1.png b/docSite/assets/imgs/quizApp1.png new file mode 100644 index 000000000..b8dd79e78 Binary files /dev/null and b/docSite/assets/imgs/quizApp1.png differ diff --git a/docSite/assets/imgs/quizApp2.png b/docSite/assets/imgs/quizApp2.png new file mode 100644 index 000000000..4d24f7356 Binary files /dev/null and b/docSite/assets/imgs/quizApp2.png differ diff --git a/docSite/content/zh-cn/docs/development/faq.md b/docSite/content/zh-cn/docs/development/faq.md index 91e606503..dd42b9807 100644 --- a/docSite/content/zh-cn/docs/development/faq.md +++ b/docSite/content/zh-cn/docs/development/faq.md @@ -19,6 +19,53 @@ images: [] ## 二、通用问题 +### 通过sealos部署的话,是否没有本地部署的一些限制? +![](/imgs/faq1.png) +这是索引模型的长度限制,通过任何方式部署都一样的,但不同索引模型的配置不一样,可以在后台修改参数。 + +### sealos怎么挂载 小程序配置文件 + +新增配置文件:/app/projects/app/public/xxxx.txt +如图 +![](/imgs/faq2.png) + +### 数据库3306端口被占用了,启动服务失败 +![](/imgs/faq3.png) + +mysql 只有 oneAPI 用到,外面一般不需要调用,所以可以 +- 把 3306:3306 的映射去掉/或者直接改一个映射。 + +```yaml +# 在 docker-compose.yaml 文件内 +# ... + mysql: + image: mysql:8.0.36 + ports: + - 3306:3306 # 这个端口被占用了! + # - 3307:3306 # 直接改一个。。和外面的不冲突 + # *empty* 或者直接删了,反正外面用不到 + oneapi: + container_name: oneapi + image: ghcr.io/songquanpeng/one-api:latest + environment: + - SQL_DSN=root:oneapimmysql@tcp(mysql:3306)/oneapi # 这不用改,容器内外网络是隔离的 +``` +- 另一种做法是可以直接连现有的 mysql, 要改 oneAPI 的环境变量。 +```yaml +# 在 docker-compose.yaml 文件内 +# ... +# mysql: # 要连外面的,这个玩意用不到了 +# image: mysql:8.0.36 +# ports: +# - 3306:3306 # 这个端口被占用了! +oneapi: + container_name: oneapi + image: ghcr.io/songquanpeng/one-api:latest + environment: + - SQL_DSN=root:oneapimmysql@tcp(mysql:3306)/oneapi # 改成外面的链接字符串 +``` + + ### 本地部署的限制 具体内容参考https://fael3z0zfze.feishu.cn/wiki/OFpAw8XzAi36Guk8dfucrCKUnjg。 diff --git a/docSite/content/zh-cn/docs/development/openapi/auth.md b/docSite/content/zh-cn/docs/development/openapi/auth.md index 5bcd72a0e..aa7014e80 100644 --- a/docSite/content/zh-cn/docs/development/openapi/auth.md +++ b/docSite/content/zh-cn/docs/development/openapi/auth.md @@ -55,4 +55,28 @@ curl --location --request POST 'https://api.fastgpt.in/api/v1/chat/completions' } ] }' -``` \ No newline at end of file +``` + +## 自定义用户 ID + +`v4.8.13`后支持传入自定义的用户 ID, 并且存入历史记录中。 + +```sh +curl --location --request POST 'https://api.fastgpt.in/api/v1/chat/completions' \ +--header 'Authorization: Bearer fastgpt-xxxxxx' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "chatId": "111", + "stream": false, + "detail": false, + "messages": [ + { + "content": "导演是谁", + "role": "user" + } + ], + "customUid": "xxxxxx" +}' +``` + +在历史记录中,该条记录的使用者会显示为 `xxxxxx`。 diff --git a/docSite/content/zh-cn/docs/development/openapi/chat.md b/docSite/content/zh-cn/docs/development/openapi/chat.md index 25f2c835d..808eccdee 100644 --- a/docSite/content/zh-cn/docs/development/openapi/chat.md +++ b/docSite/content/zh-cn/docs/development/openapi/chat.md @@ -686,7 +686,7 @@ curl --location --request POST 'http://localhost:3000/api/core/chat/getHistories - appId - 应用 Id - offset - 偏移量,即从第几条数据开始取 - pageSize - 记录数量 -- source - 对话源 +- source - 对话源。source=api,表示获取通过 API 创建的对话(不会获取到页面上的对话记录) {{% /alert %}} {{< /markdownify >}} diff --git a/docSite/content/zh-cn/docs/development/openapi/dataset.md b/docSite/content/zh-cn/docs/development/openapi/dataset.md index 09d400463..efe1d6397 100644 --- a/docSite/content/zh-cn/docs/development/openapi/dataset.md +++ b/docSite/content/zh-cn/docs/development/openapi/dataset.md @@ -733,6 +733,21 @@ data 为集合的 ID。 {{< tab tabName="请求示例" >}} {{< markdownify >}} +**4.8.19+** +```bash +curl --location --request POST 'http://localhost:3000/api/core/dataset/collection/listv2' \ +--header 'Authorization: Bearer {{authorization}}' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "offset":0, + "pageSize": 10, + "datasetId":"6593e137231a2be9c5603ba7", + "parentId": null, + "searchText":"" +}' +``` + +**4.8.19-(不再维护)** ```bash curl --location --request POST 'http://localhost:3000/api/core/dataset/collection/list' \ --header 'Authorization: Bearer {{authorization}}' \ @@ -753,7 +768,7 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/collectio {{< markdownify >}} {{% alert icon=" " context="success" %}} -- pageNum: 页码(选填) +- offset: 偏移量 - pageSize: 每页数量,最大30(选填) - datasetId: 知识库的ID(必填) - parentId: 父级Id(选填) @@ -773,9 +788,7 @@ curl --location --request POST 'http://localhost:3000/api/core/dataset/collectio "statusText": "", "message": "", "data": { - "pageNum": 1, - "pageSize": 10, - "data": [ + "list": [ { "_id": "6593e137231a2be9c5603ba9", "parentId": null, diff --git a/docSite/content/zh-cn/docs/development/upgrading/4819.md b/docSite/content/zh-cn/docs/development/upgrading/4819.md new file mode 100644 index 000000000..7f3ad25f6 --- /dev/null +++ b/docSite/content/zh-cn/docs/development/upgrading/4819.md @@ -0,0 +1,22 @@ +--- +title: 'V4.8.19(进行中)' +description: 'FastGPT V4.8.19 更新说明' +icon: 'upgrade' +draft: false +toc: true +weight: 806 +--- + + +## 完整更新内容 + +1. 新增 - 工作流知识库检索支持按知识库权限进行过滤。 +2. 新增 - 飞书/语雀知识库查看原文。 +3. 新增 - 流程等待插件,可以等待 n 毫秒后继续执行流程。 +4. 优化 - 成员列表分页加载。 +5. 优化 - 统一分页加载代码。 +6. 优化 - 对话页面加载时,可配置是否为独立页面。 +7. 修复 - 语雀文件库导入时,嵌套文件内容无法展开的问题。 +8. 修复 - 工作流编排中,LLM 参数无法关闭问题。 +9. 修复 - 工作流编排中,代码运行节点还原模板问题。 +10. 修复 - HTTP 接口适配对象字符串解析。 \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/faq/app.md b/docSite/content/zh-cn/docs/faq/app.md index 5797a3f60..a38f5baba 100644 --- a/docSite/content/zh-cn/docs/faq/app.md +++ b/docSite/content/zh-cn/docs/faq/app.md @@ -21,6 +21,19 @@ weight: 908 定时执行会在应用发布后生效,会在后台生效。 +## V4.8.18-FIX2中提到“ 1. 修复 HTTP 节点, {{}} 格式引用变量兼容问题。建议尽快替换 / 模式取变量, {{}} 语法已弃用。”替换{{}}引用格式是仅仅只有在http节点,还是所有节点的都会有影响? + +只有 http 节点用到这个语法。 + +## 工作流类型的应用在运行预览可以正常提问返回,但是发布免登录窗口之后有问题。 + +一般是没正确发布,在工作流右上角点击【保存并发布】。 + +## 如何解决猜你想问使用中文回答显示 + +注意需要更新到V4.8.17及以上,把猜你想问的提示词改成中文。 +![](/imgs/quizApp2.png) + ## AI对话回答要求中的Markdown语法取消 修改知识库默认提示词, 默认用的是标准模板提示词,会要求按 Markdown 输出,可以去除该要求: @@ -38,3 +51,32 @@ A: 通常是由于上下文不一致导致,可以在对话日志中,找到 | | | | | --- | --- | --- | | ![](/imgs/image-85.png) | ![](/imgs/image-86.png) | ![](/imgs/image-87.png) | +在针对知识库的回答要求里有, 要给它配置提示词,不然他就是默认的,默认的里面就有该语法。 + +## 工作流操作:一个工作流,以一个问题分类节点开始,根据不同的分类导入到不同的分支,访问相应的知识库和AI对话,AI对话返回内容后,怎么样不进入问题分类节点,而是将问题到知识库搜索,然后把历史记录一起作为背景再次AI查询。 + +做个判断器,如果是初次开始对话也就是历史记录为0,就走问题分类;不为零直接走知识库和ai。 + +## 实时对话,设置 fastgpt 定时,比如每隔 3000MS 去拿一次 webhook发送过来的消息到AI页面 + +定时执行没有这么高频率的去拿信息的,想要实现在企微里面的实时对话的机器人, +目前通过低代码的工作流构建应该是不行的,只能自己写代码,然后去调用 FastGPT 的 APIKey 回复。企业微信似乎没有提供「自动监听」群聊消息的接口(或是通过 at 机器人这种触发消息推送)。应该只能发消息给应用,接收这个 https://developer.work.weixin.qq.com/document/path/90238 文档中的消息推送实现实时对话。或者是定时去拿群聊消息,通过这个文档所示的接口https://developer.work.weixin.qq.com/document/path/98914,然后用这个接口 https://developer.work.weixin.qq.com/document/path/90248 去推送消息。 + +## 工作流连接数据库 + +工作流提供该连接数据库功能,用这个数据库连接的 plugin 可以实现 text2SQL,但是相对危险,不建议做写入等操作。 + +![](/imgs/quizApp1.png) + +## 关于循环体,协助理解循环体的循环条件和终止条件、循环的方式,循环体内参数调用后、在循环体内属于是局部作用域的参数还是全局作用域的参数 + +可理解为 for 函数,传一个数组,每个数据都执行一次。 + +## 公式无法正常显示 + +添加相关提示词,引导模型按 Markdown 输出公式 + +```bash +Latex inline: \(x^2\) +Latex block: $$e=mc^2$$ +``` diff --git a/docSite/content/zh-cn/docs/faq/dataset.md b/docSite/content/zh-cn/docs/faq/dataset.md index 589b57160..a5b90340a 100644 --- a/docSite/content/zh-cn/docs/faq/dataset.md +++ b/docSite/content/zh-cn/docs/faq/dataset.md @@ -16,6 +16,25 @@ weight: 910 * **文件处理模型**:用于数据处理的【增强处理】和【问答拆分】。在【增强处理】中,生成相关问题和摘要,在【问答拆分】中执行问答对生成。 * **索引模型**:用于向量化,即通过对文本数据进行处理和组织,构建出一个能够快速查询的数据结构。 +## 知识库支持Excel类文件的导入 + +xlsx等都可以上传的,不止支持CSV。 + +## 知识库tokens的计算方式 + +统一按gpt3.5标准。 + +## 误删除重排模型后,重排模型怎么加入到fastgpt + +![](/imgs/dataset3.png) + +config.json文件里面配置后就可以勾选重排模型 + +## 线上平台上创建了应用和知识库,到期之后如果短期内不续费,数据是否会被清理。 + +免费版是三十天不登录后清空知识库,应用不会动。其他付费套餐到期后自动切免费版。 +![](/imgs/dataset4.png) + ## 基于知识库的查询,但是问题相关的答案过多。ai回答到一半就不继续回答。 FastGPT回复长度计算公式: @@ -37,7 +56,7 @@ FastGPT回复长度计算公式: ![](/imgs/dataset2.png) -1. 私有化部署的时候,后台配模型参数,可以在配置最大上文时候,预留一些空间,比如 128000 的模型,可以只配置 120000, 剩余的空间后续会被安排给输出 +另外私有化部署的时候,后台配模型参数,可以在配置最大上文时,预留一些空间,比如 128000 的模型,可以只配置 120000, 剩余的空间后续会被安排给输出 ## 受到模型上下文的限制,有时候达不到聊天记录的轮次,连续对话字数过多就会报上下文不够的错误。 @@ -61,4 +80,4 @@ FastGPT回复长度计算公式: ![](/imgs/dataset2.png) -1. 私有化部署的时候,后台配模型参数,可以在配置最大上文时候,预留一些空间,比如 128000 的模型,可以只配置 120000, 剩余的空间后续会被安排给输出 \ No newline at end of file +另外,私有化部署的时候,后台配模型参数,可以在配置最大上文时,预留一些空间,比如 128000 的模型,可以只配置 120000, 剩余的空间后续会被安排给输出。 \ No newline at end of file diff --git a/docSite/content/zh-cn/docs/faq/other.md b/docSite/content/zh-cn/docs/faq/other.md index b8adf64f2..09de91a1e 100644 --- a/docSite/content/zh-cn/docs/faq/other.md +++ b/docSite/content/zh-cn/docs/faq/other.md @@ -8,4 +8,14 @@ weight: 918 ## oneapi 官网是哪个 -只有开源的 README,没官网,GitHub: https://github.com/songquanpeng/one-api \ No newline at end of file +只有开源的 README,没官网,GitHub: https://github.com/songquanpeng/one-api + +## 想做多用户和多chat key + +![](/imgs/other1.png) +![](/imgs/other2.png) + +多用户问题:只能采取二开或者商业版 + +多chat key问题:1. 私有化部署情况下,oneapi解决。2. Saas或者商业版中,可以为每个团队设置单独的key。 +![](/imgs/other3.png) diff --git a/docSite/content/zh-cn/docs/guide/knowledge_base/lark_dataset.md b/docSite/content/zh-cn/docs/guide/knowledge_base/lark_dataset.md index 8c7a8ab55..8631b0f0d 100644 --- a/docSite/content/zh-cn/docs/guide/knowledge_base/lark_dataset.md +++ b/docSite/content/zh-cn/docs/guide/knowledge_base/lark_dataset.md @@ -22,10 +22,11 @@ FastGPT v4.8.16 版本开始,商业版用户支持飞书知识库导入,用 ## 2. 配置应用权限 -创建应用后,进入应用可以配置相关权限,这里需要增加两个权限: +创建应用后,进入应用可以配置相关权限,这里需要增加**3个权限**: 1. 获取云空间文件夹下的云文档清单 2. 查看新版文档 +3. 查看、评论、编辑和管理云空间中所有文件 ![alt text](/imgs/image-41.png) diff --git a/docSite/content/zh-cn/docs/guide/workbench/workflow/loop.md b/docSite/content/zh-cn/docs/guide/workbench/workflow/loop.md index 7b32a91cd..7468e2f18 100644 --- a/docSite/content/zh-cn/docs/guide/workbench/workflow/loop.md +++ b/docSite/content/zh-cn/docs/guide/workbench/workflow/loop.md @@ -1,6 +1,6 @@ --- -title: "循环执行" -description: "FastGPT 循环运行节点介绍和使用" +title: "批量运行" +description: "FastGPT 批量运行节点介绍和使用" icon: "input" draft: false toc: true @@ -9,15 +9,15 @@ weight: 260 ## 节点概述 -【**循环运行**】节点是 FastGPT V4.8.11 版本新增的一个重要功能模块。它允许工作流对数组类型的输入数据进行迭代处理,每次处理数组中的一个元素,并自动执行后续节点,直到完成整个数组的处理。 +【**批量运行**】节点是 FastGPT V4.8.11 版本新增的一个重要功能模块。它允许工作流对数组类型的输入数据进行迭代处理,每次处理数组中的一个元素,并自动执行后续节点,直到完成整个数组的处理。 这个节点的设计灵感来自编程语言中的循环结构,但以可视化的方式呈现。 -![循环运行节点](/imgs/fastgpt-loop-node.png) +![批量运行节点](/imgs/fastgpt-loop-node.png) > 在程序中,节点可以理解为一个个 Function 或者接口。可以理解为它就是一个**步骤**。将多个节点一个个拼接起来,即可一步步的去实现最终的 AI 输出。 -【**循环运行**】节点本质上也是一个 Function,它的主要职责是自动化地重复执行特定的工作流程。 +【**批量运行**】节点本质上也是一个 Function,它的主要职责是自动化地重复执行特定的工作流程。 ## 核心特性 @@ -41,9 +41,9 @@ weight: 260 ## 应用场景 -【**循环运行**】节点的主要作用是通过自动化的方式扩展工作流的处理能力,使 FastGPT 能够更好地处理批量任务和复杂的数据处理流程。特别是在处理大规模数据或需要多轮迭代的场景下,循环运行节点能显著提升工作流的效率和自动化程度。 +【**批量运行**】节点的主要作用是通过自动化的方式扩展工作流的处理能力,使 FastGPT 能够更好地处理批量任务和复杂的数据处理流程。特别是在处理大规模数据或需要多轮迭代的场景下,批量运行节点能显著提升工作流的效率和自动化程度。 -【**循环运行**】节点特别适合以下场景: +【**批量运行**】节点特别适合以下场景: 1. **批量数据处理** - 批量翻译文本 @@ -64,7 +64,7 @@ weight: 260 ### 输入参数设置 -【**循环运行**】节点需要配置两个核心输入参数: +【**批量运行**】节点需要配置两个核心输入参数: 1. **数组 (必填)**:接收一个数组类型的输入,可以是: - 字符串数组 (`Array`) @@ -95,7 +95,7 @@ weight: 260 ### 批量处理数组 -假设我们有一个包含多个文本的数组,需要对每个文本进行 AI 处理。这是循环运行节点最基础也最常见的应用场景。 +假设我们有一个包含多个文本的数组,需要对每个文本进行 AI 处理。这是批量运行节点最基础也最常见的应用场景。 #### 实现步骤 @@ -114,9 +114,9 @@ weight: 260 return { textArray: texts }; ``` -2. 配置循环运行节点 +2. 配置批量运行节点 - ![配置循环运行节点](/imgs/fastgpt-loop-node-example-2.png) + ![配置批量运行节点](/imgs/fastgpt-loop-node-example-2.png) - 数组输入:选择上一步代码运行节点的输出变量 `textArray`。 - 循环体内添加一个【AI 对话】节点,用于处理每个文本。这里我们输入的 prompt 为:`请将这段文本翻译成英文`。 @@ -128,7 +128,7 @@ weight: 260 ![运行流程](/imgs/fastgpt-loop-node-example-3.png) 1. 【代码运行】节点执行,生成测试数组 -2. 【循环运行】节点接收数组,开始遍历 +2. 【批量运行】节点接收数组,开始遍历 3. 对每个数组元素: - 【AI 对话】节点处理当前元素 - 【指定回复】节点输出翻译后的文本 @@ -144,7 +144,7 @@ weight: 260 - 需要维护上下文的连贯性 - 翻译质量需要多轮优化 -【**循环运行**】节点可以很好地解决这些问题。 +【**批量运行**】节点可以很好地解决这些问题。 #### 实现步骤 @@ -281,9 +281,9 @@ weight: 260 这里我们用到了 [Jina AI 开源的一个强大的正则表达式](https://x.com/JinaAI_/status/1823756993108304135),它能利用所有可能的边界线索和启发式方法来精确切分文本。 -2. 配置循环运行节点 +2. 配置批量运行节点 - ![配置循环运行节点](/imgs/fastgpt-loop-node-example-5.png) + ![配置批量运行节点](/imgs/fastgpt-loop-node-example-5.png) - 数组输入:选择上一步代码运行节点的输出变量 `chunks`。 - 循环体内添加一个【代码运行】节点,对源文本进行格式化。 diff --git a/docSite/content/zh-cn/docs/use-cases/app-cases/multi_turn_translation_bot.md b/docSite/content/zh-cn/docs/use-cases/app-cases/multi_turn_translation_bot.md index a3f51303b..b9b5363d9 100644 --- a/docSite/content/zh-cn/docs/use-cases/app-cases/multi_turn_translation_bot.md +++ b/docSite/content/zh-cn/docs/use-cases/app-cases/multi_turn_translation_bot.md @@ -212,7 +212,7 @@ export default async function (ctx: FunctionContext): Promise{ ![](/imgs/translate13.png) -## 循环执行 +## 批量运行 长文反思翻译比较关键的一个部分,就是对多个文本块进行循环反思翻译 diff --git a/docSite/content/zh-cn/docs/use-cases/app-cases/translate-subtitle-using-gpt.md b/docSite/content/zh-cn/docs/use-cases/app-cases/translate-subtitle-using-gpt.md index fa12d31ce..64703a7be 100644 --- a/docSite/content/zh-cn/docs/use-cases/app-cases/translate-subtitle-using-gpt.md +++ b/docSite/content/zh-cn/docs/use-cases/app-cases/translate-subtitle-using-gpt.md @@ -91,9 +91,9 @@ weight: 604 这个过程不仅提高了效率,还最大限度地减少了人为错误的可能性。 -## 循环执行 +## 批量运行 -为了处理整个长字幕文件,我们需要一个循环执行机制。这是通过一个简单但有效的判断模块实现的: +为了处理整个长字幕文件,我们需要一个批量运行机制。这是通过一个简单但有效的判断模块实现的: 1. 检查当前翻译的文本块是否为最后一个。 2. 如果不是,则将工作流重定向到格式化原文本块节点。 diff --git a/packages/global/common/system/types/index.d.ts b/packages/global/common/system/types/index.d.ts index 90bcb7dfd..64b7bd0a4 100644 --- a/packages/global/common/system/types/index.d.ts +++ b/packages/global/common/system/types/index.d.ts @@ -40,7 +40,7 @@ export type FastGPTConfigFileType = { export type FastGPTFeConfigsType = { show_emptyChat?: boolean; - register_method?: ['email' | 'phone']; + register_method?: ['email' | 'phone' | 'sync']; login_method?: ['email' | 'phone']; // Attention: login method is diffrent with oauth find_password_method?: ['email' | 'phone']; bind_notification_method?: ['email' | 'phone']; @@ -76,7 +76,6 @@ export type FastGPTFeConfigsType = { wecom?: { corpid?: string; agentid?: string; - secret?: string; }; microsoft?: { clientId?: string; diff --git a/packages/global/core/app/constants.ts b/packages/global/core/app/constants.ts index ba9485036..a57a7a797 100644 --- a/packages/global/core/app/constants.ts +++ b/packages/global/core/app/constants.ts @@ -33,7 +33,7 @@ export const defaultWhisperConfig: AppWhisperConfigType = { export const defaultQGConfig: AppQGConfigType = { open: false, model: 'gpt-4o-mini', - customPrompt: PROMPT_QUESTION_GUIDE + customPrompt: '' }; export const defaultChatInputGuideConfig = { diff --git a/packages/global/core/app/type.d.ts b/packages/global/core/app/type.d.ts index 86885c71e..899628936 100644 --- a/packages/global/core/app/type.d.ts +++ b/packages/global/core/app/type.d.ts @@ -12,8 +12,9 @@ import { TeamTagSchema as TeamTagsSchemaType } from '@fastgpt/global/support/use import { StoreEdgeItemType } from '../workflow/type/edge'; import { AppPermission } from '../../support/permission/app/controller'; import { ParentIdType } from '../../common/parentFolder/type'; -import { FlowNodeInputTypeEnum } from 'core/workflow/node/constant'; +import { FlowNodeInputTypeEnum } from '../../core/workflow/node/constant'; import { WorkflowTemplateBasicType } from '@fastgpt/global/core/workflow/type'; +import { SourceMemberType } from '../../support/user/type'; export type AppSchema = { _id: string; @@ -63,6 +64,7 @@ export type AppListItemType = { permission: AppPermission; inheritPermission?: boolean; private?: boolean; + sourceMember: SourceMemberType; }; export type AppDetailType = AppSchema & { diff --git a/packages/global/core/app/version.d.ts b/packages/global/core/app/version.d.ts index 178834ce4..dbf0ae53f 100644 --- a/packages/global/core/app/version.d.ts +++ b/packages/global/core/app/version.d.ts @@ -1,5 +1,7 @@ +import { TeamMemberStatusEnum } from 'support/user/team/constant'; import { StoreEdgeItemType } from '../workflow/type/edge'; import { AppChatConfigType, AppSchema } from './type'; +import { SourceMemberType } from 'support/user/type'; export type AppVersionSchemaType = { _id: string; @@ -20,4 +22,5 @@ export type VersionListItemType = { time: Date; isPublish: boolean | undefined; tmbId: string; + sourceMember: SourceMemberType; }; diff --git a/packages/global/core/dataset/apiDataset.d.ts b/packages/global/core/dataset/apiDataset.d.ts index 1d64a8a59..94c036886 100644 --- a/packages/global/core/dataset/apiDataset.d.ts +++ b/packages/global/core/dataset/apiDataset.d.ts @@ -5,6 +5,7 @@ export type APIFileItem = { type: 'file' | 'folder'; updateTime: Date; createTime: Date; + hasChild?: boolean; }; export type APIFileServer = { diff --git a/packages/global/core/dataset/type.d.ts b/packages/global/core/dataset/type.d.ts index 7a772f78d..70d3211d1 100644 --- a/packages/global/core/dataset/type.d.ts +++ b/packages/global/core/dataset/type.d.ts @@ -11,6 +11,7 @@ import { import { DatasetPermission } from '../../support/permission/dataset/controller'; import { Permission } from '../../support/permission/controller'; import { APIFileServer, FeishuServer, YuqueServer } from './apiDataset'; +import { SourceMemberType } from 'support/user/type'; export type DatasetSchemaType = { _id: string; @@ -165,6 +166,7 @@ export type DatasetListItemType = { vectorModel: VectorModelItemType; inheritPermission: boolean; private?: boolean; + sourceMember?: SourceMemberType; }; export type DatasetItemType = Omit & { diff --git a/packages/global/core/dataset/utils.ts b/packages/global/core/dataset/utils.ts index 5f25b230f..d8e6564ac 100644 --- a/packages/global/core/dataset/utils.ts +++ b/packages/global/core/dataset/utils.ts @@ -34,7 +34,7 @@ export function getSourceNameIcon({ } } catch (error) {} - return 'file/fill/manual'; + return 'file/fill/file'; } /* get dataset data default index */ diff --git a/packages/global/core/workflow/constants.ts b/packages/global/core/workflow/constants.ts index d93c7ada3..175f0c19d 100644 --- a/packages/global/core/workflow/constants.ts +++ b/packages/global/core/workflow/constants.ts @@ -152,6 +152,7 @@ export enum NodeInputKeyEnum { datasetSearchExtensionModel = 'datasetSearchExtensionModel', datasetSearchExtensionBg = 'datasetSearchExtensionBg', collectionFilterMatch = 'collectionFilterMatch', + authTmbId = 'authTmbId', // concat dataset datasetQuoteList = 'system_datasetQuoteList', diff --git a/packages/global/core/workflow/runtime/type.d.ts b/packages/global/core/workflow/runtime/type.d.ts index 7084740b1..995302b65 100644 --- a/packages/global/core/workflow/runtime/type.d.ts +++ b/packages/global/core/workflow/runtime/type.d.ts @@ -41,6 +41,10 @@ export type ChatDispatchProps = { teamId: string; tmbId: string; // App tmbId }; + runningUserInfo: { + teamId: string; + tmbId: string; + }; uid: string; // Who run this workflow chatId?: string; diff --git a/packages/global/core/workflow/template/system/datasetSearch.ts b/packages/global/core/workflow/template/system/datasetSearch.ts index 69e15a679..2d117ada0 100644 --- a/packages/global/core/workflow/template/system/datasetSearch.ts +++ b/packages/global/core/workflow/template/system/datasetSearch.ts @@ -89,6 +89,13 @@ export const DatasetSearchModule: FlowNodeTemplateType = { valueType: WorkflowIOValueTypeEnum.string, value: '' }, + { + key: NodeInputKeyEnum.authTmbId, + renderTypeList: [FlowNodeInputTypeEnum.hidden], + label: '', + valueType: WorkflowIOValueTypeEnum.boolean, + value: false + }, { ...Input_Template_UserChatInput, toolDescription: i18nT('workflow:content_to_search') diff --git a/packages/global/core/workflow/type/node.d.ts b/packages/global/core/workflow/type/node.d.ts index d0bc5b919..691fc96c9 100644 --- a/packages/global/core/workflow/type/node.d.ts +++ b/packages/global/core/workflow/type/node.d.ts @@ -89,6 +89,7 @@ export type NodeTemplateListItemType = { hasTokenFee?: boolean; // 是否配置积分 instructions?: string; // 使用说明 courseUrl?: string; // 教程链接 + sourceMember?: SourceMember; }; export type NodeTemplateListType = { diff --git a/packages/global/support/user/team/controller.d.ts b/packages/global/support/user/team/controller.d.ts index 98a64af30..fb331d0de 100644 --- a/packages/global/support/user/team/controller.d.ts +++ b/packages/global/support/user/team/controller.d.ts @@ -12,6 +12,7 @@ export type CreateTeamProps = { avatar?: string; defaultTeam?: boolean; memberName?: string; + memberAvatar?: string; }; export type UpdateTeamProps = Omit & { name?: string; diff --git a/packages/global/support/user/team/org/constant.ts b/packages/global/support/user/team/org/constant.ts index 370cdad82..c5aea21ab 100644 --- a/packages/global/support/user/team/org/constant.ts +++ b/packages/global/support/user/team/org/constant.ts @@ -5,8 +5,6 @@ export const OrgMemberCollectionName = 'team_org_members'; export const getOrgChildrenPath = (org: OrgSchemaType) => `${org.path}/${org.pathId}`; -// export enum OrgMemberRole { -// owner = 'owner', -// admin = 'admin', -// member = 'member' -// } +export enum SyncOrgSourceEnum { + wecom = 'wecom' +} diff --git a/packages/global/support/user/team/org/type.d.ts b/packages/global/support/user/team/org/type.d.ts index ca4a73292..b742ece48 100644 --- a/packages/global/support/user/team/org/type.d.ts +++ b/packages/global/support/user/team/org/type.d.ts @@ -13,6 +13,7 @@ type OrgSchemaType = { }; type OrgMemberSchemaType = { + _id: string; teamId: string; orgId: string; tmbId: string; @@ -20,6 +21,6 @@ type OrgMemberSchemaType = { type OrgType = Omit & { avatar: string; - members: OrgMemberSchemaType[]; permission: TeamPermission; + members: OrgMemberSchemaType[]; }; diff --git a/packages/global/support/user/team/type.d.ts b/packages/global/support/user/team/type.d.ts index 9c7dc12b4..6f063576f 100644 --- a/packages/global/support/user/team/type.d.ts +++ b/packages/global/support/user/team/type.d.ts @@ -44,6 +44,7 @@ export type TeamMemberSchema = { name: string; role: `${TeamMemberRoleEnum}`; status: `${TeamMemberStatusEnum}`; + avatar: string; defaultTeam: boolean; }; diff --git a/packages/global/support/user/type.d.ts b/packages/global/support/user/type.d.ts index 0f8729266..13f6dd522 100644 --- a/packages/global/support/user/type.d.ts +++ b/packages/global/support/user/type.d.ts @@ -1,12 +1,12 @@ import { TeamPermission } from '../permission/user/controller'; import { UserStatusEnum } from './constant'; +import { TeamMemberStatusEnum } from './team/constant'; import { TeamTmbItemType } from './team/type'; export type UserModelSchema = { _id: string; username: string; password: string; - avatar: string; promotionRate: number; inviterId?: string; openaiKey: string; @@ -22,7 +22,7 @@ export type UserModelSchema = { export type UserType = { _id: string; username: string; - avatar: string; + avatar: string; // it should be team member's avatar after 4.8.18 timezone: string; promotionRate: UserModelSchema['promotionRate']; team: TeamTmbItemType; @@ -30,3 +30,9 @@ export type UserType = { notificationAccount?: string; permission: TeamPermission; }; + +export type SourceMemberType = { + name: string; + avatar: string; + status: `${TeamMemberStatusEnum}`; +}; diff --git a/packages/plugins/register.ts b/packages/plugins/register.ts index 832d39628..3e00f18a5 100644 --- a/packages/plugins/register.ts +++ b/packages/plugins/register.ts @@ -12,7 +12,8 @@ const staticPluginList = [ 'DingTalkWebhook', 'WeWorkWebhook', 'google', - 'bing' + 'bing', + 'delay' ]; // Run in worker thread (Have npm packages) const packagePluginList = [ @@ -28,8 +29,7 @@ const packagePluginList = [ 'databaseConnection', 'Doc2X', 'Doc2X/PDF2text', - 'searchXNG', - 'sleep' + 'searchXNG' ]; export const list = [...staticPluginList, ...packagePluginList]; diff --git a/packages/plugins/src/delay/index.ts b/packages/plugins/src/delay/index.ts new file mode 100644 index 000000000..2e89a837d --- /dev/null +++ b/packages/plugins/src/delay/index.ts @@ -0,0 +1,18 @@ +import { delay } from '@fastgpt/global/common/system/utils'; + +type Props = { + ms: number; +}; +type Response = Promise; + +const main = async ({ ms }: Props): Response => { + if (typeof ms !== 'number' || ms <= 0 || ms > 300000) { + return ms; + } + + await delay(ms); + + return ms; +}; + +export default main; diff --git a/packages/plugins/src/sleep/template.json b/packages/plugins/src/delay/template.json similarity index 73% rename from packages/plugins/src/sleep/template.json rename to packages/plugins/src/delay/template.json index 45579f06c..2678d9480 100644 --- a/packages/plugins/src/sleep/template.json +++ b/packages/plugins/src/delay/template.json @@ -1,11 +1,11 @@ { "author": "collin", "version": "4817", - "name": "流程暂停", + "name": "流程等待", "avatar": "core/workflow/template/sleep", - "intro": "让工作流暂停指定时间后运行", + "intro": "让工作流等待指定时间后运行", "showStatus": true, - "weight": 10, + "weight": 1, "isTool": true, "templateType": "tools", @@ -20,22 +20,19 @@ "flowNodeType": "pluginInput", "showStatus": false, "position": { - "x": 616.4226348688949, - "y": -165.05298493910115 + "x": 627.6352390819724, + "y": -165.05298493910118 }, "version": "481", "inputs": [ { - "renderTypeList": [ - "numberInput", - "reference" - ], + "renderTypeList": ["numberInput", "reference"], "selectedTypeIndex": 0, "valueType": "number", "canEdit": true, - "key": "ms", - "label": "ms", - "description": "需要暂停的时间,毫秒", + "key": "延迟时长", + "label": "延迟时长", + "description": "需要暂停的时间,单位毫秒", "defaultValue": 1000, "list": [ { @@ -47,8 +44,8 @@ "canSelectFile": true, "canSelectImg": true, "required": true, - "toolDescription": "需要暂停的时间,毫秒", - "max": 999999999999, + "toolDescription": "需要暂停的时间,单位毫秒", + "max": 300000, "min": 1 } ], @@ -56,8 +53,8 @@ { "id": "ms", "valueType": "number", - "key": "ms", - "label": "ms", + "key": "延迟时长", + "label": "延迟时长", "type": "hidden" } ] @@ -70,15 +67,13 @@ "flowNodeType": "pluginOutput", "showStatus": false, "position": { - "x": 1925.5772573010433, - "y": -131.55298493910115 + "x": 1921.839722563351, + "y": -160.05298493910115 }, "version": "481", "inputs": [ { - "renderTypeList": [ - "reference" - ], + "renderTypeList": ["reference"], "valueType": "any", "canEdit": true, "key": "result", @@ -86,10 +81,7 @@ "isToolOutput": true, "description": "", "required": true, - "value": [ - "zCJC6zw7c14i", - "httpRawResponse" - ] + "value": ["zCJC6zw7c14i", "httpRawResponse"] } ], "outputs": [] @@ -116,16 +108,14 @@ "flowNodeType": "httpRequest468", "showStatus": true, "position": { - "x": 1152.535395637613, - "y": -433.21496011686054 + "x": 1154.4041630064592, + "y": -455.0529849391012 }, "version": "481", "inputs": [ { "key": "system_addInputParam", - "renderTypeList": [ - "addInputParam" - ], + "renderTypeList": ["addInputParam"], "valueType": "dynamic", "label": "", "required": false, @@ -157,9 +147,7 @@ }, { "key": "system_httpMethod", - "renderTypeList": [ - "custom" - ], + "renderTypeList": ["custom"], "valueType": "string", "label": "", "value": "POST", @@ -171,9 +159,7 @@ }, { "key": "system_httpTimeout", - "renderTypeList": [ - "custom" - ], + "renderTypeList": ["custom"], "valueType": "number", "label": "", "value": 30, @@ -187,24 +173,20 @@ }, { "key": "system_httpReqUrl", - "renderTypeList": [ - "hidden" - ], + "renderTypeList": ["hidden"], "valueType": "string", "label": "", "description": "common:core.module.input.description.Http Request Url", "placeholder": "https://api.ai.com/getInventory", "required": false, - "value": "sleep", + "value": "delay", "valueDesc": "", "debugLabel": "", "toolDescription": "" }, { "key": "system_httpHeader", - "renderTypeList": [ - "custom" - ], + "renderTypeList": ["custom"], "valueType": "any", "value": [], "label": "", @@ -217,9 +199,7 @@ }, { "key": "system_httpParams", - "renderTypeList": [ - "hidden" - ], + "renderTypeList": ["hidden"], "valueType": "any", "value": [], "label": "", @@ -231,9 +211,7 @@ }, { "key": "system_httpJsonBody", - "renderTypeList": [ - "hidden" - ], + "renderTypeList": ["hidden"], "valueType": "any", "value": "{\n\"ms\": {{$pluginInput.ms$}}\n}", "label": "", @@ -245,9 +223,7 @@ }, { "key": "system_httpFormBody", - "renderTypeList": [ - "hidden" - ], + "renderTypeList": ["hidden"], "valueType": "any", "value": [], "label": "", @@ -259,9 +235,7 @@ }, { "key": "system_httpContentType", - "renderTypeList": [ - "hidden" - ], + "renderTypeList": ["hidden"], "valueType": "string", "value": "json", "label": "", @@ -338,32 +312,7 @@ } ], "chatConfig": { - "welcomeText": "", - "variables": [], - "questionGuide": { - "open": false, - "model": "gpt-4o-mini", - "customPrompt": "You are an AI assistant tasked with predicting the user's next question based on the conversation history. Your goal is to generate 3 potential questions that will guide the user to continue the conversation. When generating these questions, adhere to the following rules:\n\n1. Use the same language as the user's last question in the conversation history.\n2. Keep each question under 20 characters in length.\n\nAnalyze the conversation history provided to you and use it as context to generate relevant and engaging follow-up questions. Your predictions should be logical extensions of the current topic or related areas that the user might be interested in exploring further.\n\nRemember to maintain consistency in tone and style with the existing conversation while providing diverse options for the user to choose from. Your goal is to keep the conversation flowing naturally and help the user delve deeper into the subject matter or explore related topics." - }, - "ttsConfig": { - "type": "web" - }, - "whisperConfig": { - "open": false, - "autoSend": false, - "autoTTSResponse": false - }, - "chatInputGuide": { - "open": false, - "textList": [], - "customUrl": "" - }, - "instruction": "", - "autoExecute": { - "open": false, - "defaultPrompt": "" - }, - "_id": "6784ba2c227f92f6c723ead0" + "welcomeText": "" } } -} \ No newline at end of file +} diff --git a/packages/plugins/src/sleep/index.ts b/packages/plugins/src/sleep/index.ts deleted file mode 100644 index 8f5e9de7e..000000000 --- a/packages/plugins/src/sleep/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -type Props = { - ms: number; -}; -type Response = Promise<{ - result: any; -}>; - -const main = async ({ ms }: Props): Response => { - await new Promise((resolve) => { - setTimeout(resolve, ms); - }); - return { - result: ms - }; -}; - -export default main; diff --git a/packages/service/common/api/pagination.ts b/packages/service/common/api/pagination.ts new file mode 100644 index 000000000..052cd41f5 --- /dev/null +++ b/packages/service/common/api/pagination.ts @@ -0,0 +1,21 @@ +import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; +import { ApiRequestProps } from '../../type/next'; + +export function parsePaginationRequest(req: ApiRequestProps) { + const { + pageSize = 10, + pageNum = 1, + offset = 0 + } = Object.keys(req.body).includes('pageSize') + ? req.body + : Object.keys(req.query).includes('pageSize') + ? req.query + : {}; + if (!pageSize || (pageNum === undefined && offset === undefined)) { + throw new Error(CommonErrEnum.missingParams); + } + return { + pageSize: Number(pageSize), + offset: offset ? Number(offset) : (Number(pageNum) - 1) * Number(pageSize) + }; +} diff --git a/packages/service/common/middle/cors.ts b/packages/service/common/middle/cors.ts index 73b294162..294dbe5a4 100644 --- a/packages/service/common/middle/cors.ts +++ b/packages/service/common/middle/cors.ts @@ -2,7 +2,7 @@ import type { NextApiResponse, NextApiRequest } from 'next'; import NextCors from 'nextjs-cors'; export async function withNextCors(req: NextApiRequest, res: NextApiResponse) { - const methods = ['GET', 'eHEAD', 'PUT', 'PATCH', 'POST', 'DELETE']; + const methods = ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE']; const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(','); const origin = req.headers.origin; diff --git a/packages/service/core/dataset/apiDataset/api.ts b/packages/service/core/dataset/apiDataset/api.ts index 19818b32f..162d685f5 100644 --- a/packages/service/core/dataset/apiDataset/api.ts +++ b/packages/service/core/dataset/apiDataset/api.ts @@ -99,7 +99,13 @@ export const useApiDatasetRequest = ({ apiServer }: { apiServer: APIFileServer } if (files.some((file) => !file.id || !file.name || typeof file.type === 'undefined')) { return Promise.reject('Invalid file data format'); } - return files; + + const formattedFiles = files.map((file) => ({ + ...file, + hasChild: file.type === 'folder' + })); + + return formattedFiles; }; const getFileContent = async ({ teamId, apiFileId }: { teamId: string; apiFileId: string }) => { diff --git a/packages/service/core/dataset/search/controller.ts b/packages/service/core/dataset/search/controller.ts index 0de4c2884..7a3ea9a3f 100644 --- a/packages/service/core/dataset/search/controller.ts +++ b/packages/service/core/dataset/search/controller.ts @@ -284,7 +284,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) { { _id: { $in: collectionIdList } }, - '_id name fileId rawLink externalFileId externalFileUrl', + '_id name fileId rawLink apiFileId externalFileId externalFileUrl', { ...readFromSecondary } ).lean() ]); @@ -525,7 +525,7 @@ export async function searchDatasetData(props: SearchDatasetDataProps) { { _id: { $in: searchResults.map((item) => item.collectionId) } }, - '_id name fileId rawLink externalFileId externalFileUrl', + '_id name fileId rawLink apiFileId externalFileId externalFileUrl', { ...readFromSecondary } ).lean() ]); diff --git a/packages/service/core/dataset/utils.ts b/packages/service/core/dataset/utils.ts new file mode 100644 index 000000000..4f805ccb1 --- /dev/null +++ b/packages/service/core/dataset/utils.ts @@ -0,0 +1,39 @@ +import { getTmbInfoByTmbId } from '../../support/user/team/controller'; +import { getResourcePermission } from '../../support/permission/controller'; +import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant'; +import { DatasetPermission } from '@fastgpt/global/support/permission/dataset/controller'; + +// TODO: 需要优化成批量获取权限 +export const filterDatasetsByTmbId = async ({ + datasetIds, + tmbId +}: { + datasetIds: string[]; + tmbId: string; +}) => { + const { teamId, permission: tmbPer } = await getTmbInfoByTmbId({ tmbId }); + + // First get all permissions + const permissions = await Promise.all( + datasetIds.map(async (datasetId) => { + const per = await getResourcePermission({ + teamId, + tmbId, + resourceId: datasetId, + resourceType: PerResourceTypeEnum.dataset + }); + + if (per === undefined) return false; + + const datasetPer = new DatasetPermission({ + per, + isOwner: tmbPer.isOwner + }); + + return datasetPer.hasReadPer; + }) + ); + + // Then filter datasetIds based on permissions + return datasetIds.filter((_, index) => permissions[index]); +}; diff --git a/packages/service/core/workflow/dispatch/dataset/search.ts b/packages/service/core/workflow/dispatch/dataset/search.ts index 8a40c8e28..92067dacb 100644 --- a/packages/service/core/workflow/dispatch/dataset/search.ts +++ b/packages/service/core/workflow/dispatch/dataset/search.ts @@ -17,6 +17,7 @@ import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type'; import { checkTeamReRankPermission } from '../../../../support/permission/teamLimit'; import { MongoDataset } from '../../../dataset/schema'; import { i18nT } from '../../../../../web/i18n/utils'; +import { filterDatasetsByTmbId } from '../../../dataset/utils'; type DatasetSearchProps = ModuleDispatchProps<{ [NodeInputKeyEnum.datasetSelectList]: SelectedDatasetType; @@ -29,6 +30,7 @@ type DatasetSearchProps = ModuleDispatchProps<{ [NodeInputKeyEnum.datasetSearchExtensionModel]: string; [NodeInputKeyEnum.datasetSearchExtensionBg]: string; [NodeInputKeyEnum.collectionFilterMatch]: string; + [NodeInputKeyEnum.authTmbId]: boolean; }>; export type DatasetSearchResponse = DispatchNodeResultType<{ [NodeOutputKeyEnum.datasetQuoteQA]: SearchDataResponseItemType[]; @@ -39,6 +41,7 @@ export async function dispatchDatasetSearch( ): Promise { const { runningAppInfo: { teamId }, + runningUserInfo: { tmbId }, histories, node, params: { @@ -52,7 +55,8 @@ export async function dispatchDatasetSearch( datasetSearchUsingExtensionQuery, datasetSearchExtensionModel, datasetSearchExtensionBg, - collectionFilterMatch + collectionFilterMatch, + authTmbId = false } } = props as DatasetSearchProps; @@ -64,18 +68,20 @@ export async function dispatchDatasetSearch( return Promise.reject(i18nT('common:core.chat.error.Select dataset empty')); } + const emptyResult = { + quoteQA: [], + [DispatchNodeResponseKeyEnum.nodeResponse]: { + totalPoints: 0, + query: '', + limit, + searchMode + }, + nodeDispatchUsages: [], + [DispatchNodeResponseKeyEnum.toolResponses]: [] + }; + if (!userChatInput) { - return { - quoteQA: [], - [DispatchNodeResponseKeyEnum.nodeResponse]: { - totalPoints: 0, - query: '', - limit, - searchMode - }, - nodeDispatchUsages: [], - [DispatchNodeResponseKeyEnum.toolResponses]: [] - }; + return emptyResult; } // query extension @@ -83,13 +89,24 @@ export async function dispatchDatasetSearch( ? getLLMModel(datasetSearchExtensionModel) : undefined; - const { concatQueries, rewriteQuery, aiExtensionResult } = await datasetSearchQueryExtension({ - query: userChatInput, - extensionModel, - extensionBg: datasetSearchExtensionBg, - histories: getHistories(6, histories) - }); + const [{ concatQueries, rewriteQuery, aiExtensionResult }, datasetIds] = await Promise.all([ + datasetSearchQueryExtension({ + query: userChatInput, + extensionModel, + extensionBg: datasetSearchExtensionBg, + histories: getHistories(6, histories) + }), + authTmbId + ? filterDatasetsByTmbId({ + datasetIds: datasets.map((item) => item.datasetId), + tmbId + }) + : Promise.resolve(datasets.map((item) => item.datasetId)) + ]); + if (datasetIds.length === 0) { + return emptyResult; + } // console.log(concatQueries, rewriteQuery, aiExtensionResult); // get vector @@ -110,7 +127,7 @@ export async function dispatchDatasetSearch( model: vectorModel.model, similarity, limit, - datasetIds: datasets.map((item) => item.datasetId), + datasetIds, searchMode, usingReRank: usingReRank && (await checkTeamReRankPermission(teamId)), collectionFilterMatch diff --git a/packages/service/core/workflow/dispatch/plugin/run.ts b/packages/service/core/workflow/dispatch/plugin/run.ts index f7eb09af5..a81a6a867 100644 --- a/packages/service/core/workflow/dispatch/plugin/run.ts +++ b/packages/service/core/workflow/dispatch/plugin/run.ts @@ -72,6 +72,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise({ + .populate<{ tmb: TeamMemberSchema }>({ path: 'tmb', - select: 'name userId role', - populate: { - path: 'user', - select: 'avatar' - } + select: 'name userId avatar' }) .lean(), MongoResourcePermission.find({ diff --git a/packages/service/support/permission/org/orgSchema.ts b/packages/service/support/permission/org/orgSchema.ts index 45030c4a6..94fa531e4 100644 --- a/packages/service/support/permission/org/orgSchema.ts +++ b/packages/service/support/permission/org/orgSchema.ts @@ -47,14 +47,11 @@ export const OrgSchema = new Schema( OrgSchema.virtual('members', { ref: OrgMemberCollectionName, localField: '_id', - foreignField: 'orgId' + foreignField: 'orgId', + match: function (this: OrgSchemaType) { + return { teamId: this.teamId }; + } }); -// OrgSchema.virtual('permission', { -// ref: ResourcePermissionCollectionName, -// localField: '_id', -// foreignField: 'orgId', -// justOne: true -// }); try { OrgSchema.index({ diff --git a/packages/service/support/user/controller.ts b/packages/service/support/user/controller.ts index cf432b214..ab28aac72 100644 --- a/packages/service/support/user/controller.ts +++ b/packages/service/support/user/controller.ts @@ -41,7 +41,7 @@ export async function getUserDetail({ return { _id: user._id, username: user.username, - avatar: user.avatar, + avatar: tmb.avatar, timezone: user.timezone, promotionRate: user.promotionRate, team: tmb, diff --git a/packages/service/support/user/schema.ts b/packages/service/support/user/schema.ts index 10094ee6f..78234fd34 100644 --- a/packages/service/support/user/schema.ts +++ b/packages/service/support/user/schema.ts @@ -3,7 +3,6 @@ const { Schema } = connectionMongo; import { hashStr } from '@fastgpt/global/common/string/tools'; import type { UserModelSchema } from '@fastgpt/global/support/user/type'; import { UserStatusEnum, userStatusMap } from '@fastgpt/global/support/user/constant'; -import { getRandomUserAvatar } from '@fastgpt/global/support/user/utils'; export const userCollectionName = 'users'; @@ -33,11 +32,6 @@ const UserSchema = new Schema({ type: Date, default: () => new Date() }, - avatar: { - type: String, - default: () => getRandomUserAvatar() - }, - promotionRate: { type: Number, default: 15 @@ -62,7 +56,10 @@ const UserSchema = new Schema({ ref: userCollectionName }, fastgpt_sem: Object, - sourceDomain: String + sourceDomain: String, + + /** @deprecated */ + avatar: String }); try { diff --git a/packages/service/support/user/team/controller.ts b/packages/service/support/user/team/controller.ts index 451e02f5b..d48d9bf0b 100644 --- a/packages/service/support/user/team/controller.ts +++ b/packages/service/support/user/team/controller.ts @@ -37,7 +37,7 @@ async function getTeamMember(match: Record): Promise getRandomUserAvatar() + }, name: { type: String, default: 'Member' @@ -39,7 +44,6 @@ const TeamMemberSchema = new Schema({ // Abandoned role: { type: String - // enum: Object.keys(TeamMemberRoleMap) // disable enum validation for old data } }); diff --git a/packages/service/support/user/utils.ts b/packages/service/support/user/utils.ts index fc1fef631..574ab7853 100644 --- a/packages/service/support/user/utils.ts +++ b/packages/service/support/user/utils.ts @@ -1,4 +1,7 @@ +import { SourceMemberType } from '@fastgpt/global/support/user/type'; import { MongoTeam } from './team/teamSchema'; +import { MongoTeamMember } from './team/teamMemberSchema'; +import { ClientSession } from '../../common/mongo'; /* export dataset limit */ export const updateExportDatasetLimit = async (teamId: string) => { @@ -67,3 +70,41 @@ export const checkWebSyncLimit = async ({ return Promise.reject(`每个团队,每 ${limitMinutes} 分钟仅使用一次同步功能。`); } }; + +/** + * This function will add a property named sourceMember to the list passed in. + * @param list The list to add the sourceMember property to. [TmbId] property is required. + * @error If member is not found, this item will be skipped. + * @returns The list with the sourceMember property added. + */ +export async function addSourceMember({ + list, + session +}: { + list: T[]; + session?: ClientSession; +}): Promise> { + if (!Array.isArray(list)) return []; + + const tmbList = await MongoTeamMember.find( + { + _id: { $in: list.map((item) => String(item.tmbId)) } + }, + 'tmbId name avatar status', + { + session + } + ).lean(); + + return list + .map((item) => { + const tmb = tmbList.find((tmb) => String(tmb._id) === String(item.tmbId)); + if (!tmb) return; + + return { + ...item, + sourceMember: { name: tmb.name, avatar: tmb.avatar, status: tmb.status } + }; + }) + .filter(Boolean) as Array; +} diff --git a/packages/web/common/fetch/type.d.ts b/packages/web/common/fetch/type.d.ts index 0153db956..7f2ea1fbf 100644 --- a/packages/web/common/fetch/type.d.ts +++ b/packages/web/common/fetch/type.d.ts @@ -1,8 +1,13 @@ -export type PaginationProps = T & { - offset: number; - pageSize: number; -}; -export type PaginationResponse = { +import { RequireOnlyOne } from '@fastgpt/global/common/type/utils'; + +type PaginationProps = T & { + pageSize: number | string; +} & RequireOnlyOne<{ + offset: number | string; + pageNum: number | string; + }>; + +type PaginationResponse = { total: number; list: T[]; }; diff --git a/packages/web/components/common/Avatar/AvatarGroup.tsx b/packages/web/components/common/Avatar/AvatarGroup.tsx index 2c26b9a9d..199373f05 100644 --- a/packages/web/components/common/Avatar/AvatarGroup.tsx +++ b/packages/web/components/common/Avatar/AvatarGroup.tsx @@ -23,7 +23,7 @@ function AvatarGroup({ {avatars.slice(0, max).map((avatar, index) => ( 0 ? 'absolute' : 'relative'} left={index > 0 ? `${index * 15}px` : 0} diff --git a/packages/web/components/common/MySelect/index.tsx b/packages/web/components/common/MySelect/index.tsx index 7d503d21b..c9eb6230d 100644 --- a/packages/web/components/common/MySelect/index.tsx +++ b/packages/web/components/common/MySelect/index.tsx @@ -21,7 +21,15 @@ import type { ButtonProps, MenuItemProps } from '@chakra-ui/react'; import MyIcon from '../Icon'; import { useRequest2 } from '../../../hooks/useRequest'; import MyDivider from '../MyDivider'; +import { useScrollPagination } from '../../../hooks/useScrollPagination'; +/** 选择组件 Props 类型 + * value: 选中的值 + * placeholder: 占位符 + * list: 列表数据 + * isLoading: 是否加载中 + * ScrollData: 分页滚动数据控制器 [useScrollPagination] 的返回值 + * */ export type SelectProps = ButtonProps & { value?: T; placeholder?: string; @@ -34,6 +42,7 @@ export type SelectProps = ButtonProps & { }[]; isLoading?: boolean; onchange?: (val: T) => any | Promise; + ScrollData?: ReturnType['ScrollData']; }; const MySelect = ( @@ -44,6 +53,7 @@ const MySelect = ( list = [], onchange, isLoading = false, + ScrollData, ...props }: SelectProps, ref: ForwardedRef<{ @@ -87,6 +97,46 @@ const MySelect = ( const isSelecting = loading || isLoading; + const ListRender = useMemo(() => { + return ( + <> + {list.map((item, i) => ( + + { + if (onChange && value !== item.value) { + onChange(item.value); + } + }} + whiteSpace={'pre-wrap'} + fontSize={'sm'} + display={'block'} + > + {item.label} + {item.description && ( + + {item.description} + + )} + + {item.showBorder && } + + ))} + + ); + }, [list, value]); + return ( ( maxH={'40vh'} overflowY={'auto'} > - {list.map((item, i) => ( - - { - if (onChange && value !== item.value) { - onChange(item.value); - } - }} - whiteSpace={'pre-wrap'} - fontSize={'sm'} - display={'block'} - > - {item.label} - {item.description && ( - - {item.description} - - )} - - {item.showBorder && } - - ))} + {ScrollData ? {ListRender} : ListRender} diff --git a/packages/web/components/common/UserBox/index.tsx b/packages/web/components/common/UserBox/index.tsx new file mode 100644 index 000000000..8e659fa1a --- /dev/null +++ b/packages/web/components/common/UserBox/index.tsx @@ -0,0 +1,23 @@ +import { Box, HStack, type StackProps } from '@chakra-ui/react'; +import { SourceMemberType } from '@fastgpt/global/support/user/type'; +import React from 'react'; +import Avatar from '../Avatar'; +import { useTranslation } from 'next-i18next'; +import Tag from '../Tag'; + +export type UserBoxProps = { + sourceMember: SourceMemberType; + avatarSize?: string; +} & StackProps; +function UserBox({ sourceMember, avatarSize = '1.25rem', ...props }: UserBoxProps) { + const { t } = useTranslation(); + return ( + + + {sourceMember.name} + {sourceMember.status === 'leave' && {t('common:user_leaved')}} + + ); +} + +export default React.memo(UserBox); diff --git a/packages/web/hooks/usePagination.tsx b/packages/web/hooks/usePagination.tsx index 477c32672..5e65d11f3 100644 --- a/packages/web/hooks/usePagination.tsx +++ b/packages/web/hooks/usePagination.tsx @@ -4,7 +4,6 @@ import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons'; import { useTranslation } from 'next-i18next'; import { useToast } from './useToast'; import { getErrText } from '@fastgpt/global/common/error/utils'; - import { useBoolean, useLockFn, @@ -14,37 +13,33 @@ import { useThrottleEffect } from 'ahooks'; +import { PaginationProps, PaginationResponse } from '../common/fetch/type'; + const thresholdVal = 200; -type PagingData = { - pageNum: number; - pageSize: number; - data: T[]; - total?: number; -}; - -export function usePagination({ - api, - pageSize = 10, - params = {}, - defaultRequest = true, - type = 'button', - onChange, - refreshDeps, - scrollLoadType = 'bottom', - EmptyTip -}: { - api: (data: any) => Promise>; - pageSize?: number; - params?: Record; - defaultRequest?: boolean; - type?: 'button' | 'scroll'; - onChange?: (pageNum: number) => void; - refreshDeps?: any[]; - throttleWait?: number; - scrollLoadType?: 'top' | 'bottom'; - EmptyTip?: React.JSX.Element; -}) { +export function usePagination( + api: (data: PaginationProps) => Promise>, + { + pageSize = 10, + params, + defaultRequest = true, + type = 'button', + onChange, + refreshDeps, + scrollLoadType = 'bottom', + EmptyTip + }: { + pageSize?: number; + params?: DataT; + defaultRequest?: boolean; + type?: 'button' | 'scroll'; + onChange?: (pageNum: number) => void; + refreshDeps?: any[]; + throttleWait?: number; + scrollLoadType?: 'top' | 'bottom'; + EmptyTip?: React.JSX.Element; + } +) { const { toast } = useToast(); const { t } = useTranslation(); @@ -64,7 +59,7 @@ export function usePagination({ setTrue(); try { - const res: PagingData = await api({ + const res = await api({ pageNum: num, pageSize, ...params @@ -93,13 +88,13 @@ export function usePagination({ ); } - setData((prevData) => (num === 1 ? res.data : [...res.data, ...prevData])); + setData((prevData) => (num === 1 ? res.list : [...res.list, ...prevData])); adjustScrollPosition(); } else { - setData((prevData) => (num === 1 ? res.data : [...prevData, ...res.data])); + setData((prevData) => (num === 1 ? res.list : [...prevData, ...res.list])); } } else { - setData(res.data); + setData(res.list); } onChange?.(num); diff --git a/packages/web/hooks/useScrollPagination.tsx b/packages/web/hooks/useScrollPagination.tsx index 83a5ad11b..4ecc77c44 100644 --- a/packages/web/hooks/useScrollPagination.tsx +++ b/packages/web/hooks/useScrollPagination.tsx @@ -16,7 +16,7 @@ import MyBox from '../components/common/MyBox'; import { useTranslation } from 'next-i18next'; type ItemHeight = (index: number, data: T) => number; -const thresholdVal = 200; +const thresholdVal = 100; export type ScrollListType = ({ children, @@ -269,8 +269,10 @@ export function useScrollPagination< ({ children, ScrollContainerRef, + isLoading, ...props }: { + isLoading?: boolean; children: ReactNode; ScrollContainerRef?: RefObject; } & BoxProps) => { @@ -302,7 +304,7 @@ export function useScrollPagination< ); return ( - + {scrollLoadType === 'top' && total > 0 && isLoading && ( {t('common:common.is_requesting')} @@ -325,7 +327,7 @@ export function useScrollPagination< )} {isEmpty && EmptyTip} - + ); } ); diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index 1feb36b2a..9871c5528 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -39,6 +39,7 @@ "classification": "Classification", "click_to_resume": "Click to Resume", "code_editor": "Code Editor", + "code_error.account_error": "Incorrect account name or password", "code_error.app_error.invalid_app_type": "Invalid Application Type", "code_error.app_error.invalid_owner": "Unauthorized Application Owner", "code_error.app_error.not_exist": "Application Does Not Exist", @@ -95,7 +96,6 @@ "code_error.team_error.website_sync_not_enough": "Unauthorized to Use Website Sync", "code_error.token_error_code.403": "Invalid Login Status, Please Re-login", "code_error.user_error.balance_not_enough": "Insufficient Account Balance", - "code_error.account_error": "Incorrect account name or password", "code_error.user_error.bin_visitor_guest": "You Are Currently a Guest, Unauthorized to Operate", "code_error.user_error.un_auth_user": "User Not Found", "common.Action": "Action", @@ -1273,6 +1273,7 @@ "user.team.role.Visitor": "visitor", "user.team.role.writer": "writable member", "user.type": "Type", + "user_leaved": "Leaved", "verification": "Verification", "workflow.template.communication": "Communication", "xx_search_result": "{{key}} Search Results", diff --git a/packages/web/i18n/en/workflow.json b/packages/web/i18n/en/workflow.json index 2f2912a24..223dda9b5 100644 --- a/packages/web/i18n/en/workflow.json +++ b/packages/web/i18n/en/workflow.json @@ -13,6 +13,8 @@ "append_application_reply_to_history_as_new_context": "Append the application's reply to the history as new context", "application_call": "Application Call", "assigned_reply": "Assigned Reply", + "auth_tmb_id": "Auth member", + "auth_tmb_id_tip": "After it is turned on, when the application is released to the outside world, the knowledge base will be filtered based on whether the user has permission to the knowledge base.\n\nIf it is not enabled, the configured knowledge base will be searched directly without permission filtering.", "can_not_loop": "This node can't loop.", "choose_another_application_to_call": "Select another application to call", "classification_result": "Classification Result", diff --git a/packages/web/i18n/zh-CN/account_team.json b/packages/web/i18n/zh-CN/account_team.json index e562ef7c9..91587efc2 100644 --- a/packages/web/i18n/zh-CN/account_team.json +++ b/packages/web/i18n/zh-CN/account_team.json @@ -35,5 +35,8 @@ "user_team_invite_member": "邀请成员", "user_team_leave_team": "离开团队", "user_team_leave_team_failed": "离开团队失败", - "waiting": "待接受" + "waiting": "待接受", + "sync_immediately": "立即同步", + "sync_member_failed": "同步成员失败", + "sync_member_success": "同步成员成功" } diff --git a/packages/web/i18n/zh-CN/common.json b/packages/web/i18n/zh-CN/common.json index bbbfcb4ed..fa46e013b 100644 --- a/packages/web/i18n/zh-CN/common.json +++ b/packages/web/i18n/zh-CN/common.json @@ -43,6 +43,7 @@ "classification": "分类", "click_to_resume": "点击恢复", "code_editor": "代码编辑", + "code_error.account_error": "账号名或密码错误", "code_error.app_error.invalid_app_type": "错误的应用类型", "code_error.app_error.invalid_owner": "非法的应用所有者", "code_error.app_error.not_exist": "应用不存在", @@ -99,7 +100,6 @@ "code_error.team_error.website_sync_not_enough": "无权使用Web站点同步~", "code_error.token_error_code.403": "登录状态无效,请重新登录", "code_error.user_error.balance_not_enough": "账号余额不足~", - "code_error.account_error": "账号名或密码错误", "code_error.user_error.bin_visitor_guest": "您当前身份为游客,无权操作", "code_error.user_error.un_auth_user": "找不到该用户", "common.Action": "操作", @@ -1268,6 +1268,7 @@ "user.team.role.Visitor": "访客", "user.team.role.writer": "可写成员", "user.type": "类型", + "user_leaved": "已离开", "verification": "验证", "workflow.template.communication": "通信", "xx_search_result": "{{key}} 的搜索结果", diff --git a/packages/web/i18n/zh-CN/workflow.json b/packages/web/i18n/zh-CN/workflow.json index 42a62528d..37c4dcabb 100644 --- a/packages/web/i18n/zh-CN/workflow.json +++ b/packages/web/i18n/zh-CN/workflow.json @@ -13,6 +13,8 @@ "append_application_reply_to_history_as_new_context": "将该应用回复内容拼接到历史记录中,作为新的上下文返回", "application_call": "应用调用", "assigned_reply": "指定回复", + "auth_tmb_id": "使用者鉴权", + "auth_tmb_id_tip": "开启后,对外发布该应用时,还会根据用户是否有该知识库权限进行知识库过滤。\n若未开启,则直接按配置的知识库进行检索,不进行权限过滤。", "can_not_loop": "该节点不支持循环嵌套", "choose_another_application_to_call": "选择一个其他应用进行调用", "classification_result": "分类结果", diff --git a/packages/web/i18n/zh-Hant/common.json b/packages/web/i18n/zh-Hant/common.json index 2d3cfc13a..efa9bbf46 100644 --- a/packages/web/i18n/zh-Hant/common.json +++ b/packages/web/i18n/zh-Hant/common.json @@ -39,6 +39,7 @@ "classification": "分類", "click_to_resume": "點選繼續", "code_editor": "程式碼編輯器", + "code_error.account_error": "帳號名稱或密碼錯誤", "code_error.app_error.invalid_app_type": "無效的應用程式類型", "code_error.app_error.invalid_owner": "非法的應用程式擁有者", "code_error.app_error.not_exist": "應用程式不存在", @@ -95,7 +96,6 @@ "code_error.team_error.website_sync_not_enough": "無權使用網站同步", "code_error.token_error_code.403": "登入狀態無效,請重新登入", "code_error.user_error.balance_not_enough": "帳戶餘額不足", - "code_error.account_error": "帳號名稱或密碼錯誤", "code_error.user_error.bin_visitor_guest": "您目前身份為訪客,無權操作", "code_error.user_error.un_auth_user": "找不到此使用者", "common.Action": "操作", @@ -1273,6 +1273,7 @@ "user.team.role.Visitor": "訪客", "user.team.role.writer": "可寫入成員", "user.type": "類型", + "user_leaved": "已離開", "verification": "驗證", "workflow.template.communication": "通訊", "xx_search_result": "{{key}} 的搜尋結果", diff --git a/packages/web/i18n/zh-Hant/workflow.json b/packages/web/i18n/zh-Hant/workflow.json index 98603c05a..b022ab4ca 100644 --- a/packages/web/i18n/zh-Hant/workflow.json +++ b/packages/web/i18n/zh-Hant/workflow.json @@ -13,6 +13,8 @@ "append_application_reply_to_history_as_new_context": "將應用程式的回覆附加到歷史紀錄中,作為新的脈絡", "application_call": "應用程式呼叫", "assigned_reply": "指定回覆", + "auth_tmb_id": "使用者鑑權", + "auth_tmb_id_tip": "開啟後,對外發布應用程式時,也會根據使用者是否有該知識庫權限進行知識庫過濾。\n\n若未開啟,則直接按配置的知識庫進行檢索,不進行權限過濾。", "can_not_loop": "這個節點不能迴圈。", "choose_another_application_to_call": "選擇另一個應用程式來呼叫", "classification_result": "分類結果", diff --git a/projects/app/.env.template b/projects/app/.env.template index b8f4ca0a8..71ed204df 100644 --- a/projects/app/.env.template +++ b/projects/app/.env.template @@ -57,3 +57,5 @@ WORKFLOW_MAX_LOOP_TIMES=50 # CHAT_LOG_INTERVAL=10000 # # 日志来源ID前缀 # CHAT_LOG_SOURCE_ID_PREFIX=fastgpt- +# 自定义跨域,不配置时,默认都允许跨域(逗号分割) +ALLOWED_ORIGINS= \ No newline at end of file diff --git a/projects/app/package.json b/projects/app/package.json index c735c9f6a..86370fcda 100644 --- a/projects/app/package.json +++ b/projects/app/package.json @@ -1,6 +1,6 @@ { "name": "app", - "version": "4.8.18", + "version": "4.8.19", "private": false, "scripts": { "dev": "next dev", diff --git a/projects/app/src/components/core/app/InputGuideConfig.tsx b/projects/app/src/components/core/app/InputGuideConfig.tsx index 13c3920ba..91a83d24a 100644 --- a/projects/app/src/components/core/app/InputGuideConfig.tsx +++ b/projects/app/src/components/core/app/InputGuideConfig.tsx @@ -54,7 +54,7 @@ const InputGuideConfig = ({ onChange: (e: ChatInputGuideConfigType) => void; }) => { const { t } = useTranslation(); - const { chatT, commonT } = useI18n(); + const { chatT } = useI18n(); const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen: isOpenLexiconConfig, @@ -220,7 +220,7 @@ const LexiconConfigModal = ({ appId, onClose }: { appId: string; onClose: () => }); const { run: createNewData, loading: isCreating } = useRequest2( - (textList: string[]) => { + async (textList: string[]) => { if (textList.filter(Boolean).length === 0) { return Promise.resolve(); } diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/Provider.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/Provider.tsx index d7b4a470f..3b1ff2f14 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/Provider.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/Provider.tsx @@ -29,8 +29,6 @@ export type ChatProviderProps = { outLinkAuthData?: OutLinkChatAuthProps; chatType: 'log' | 'chat' | 'share' | 'team'; - showRawSource: boolean; - showNodeStatus: boolean; }; type useChatStoreType = ChatProviderProps & { @@ -132,8 +130,6 @@ const Provider = ({ chatId, outLinkAuthData = {}, chatType = 'chat', - showRawSource, - showNodeStatus, children, ...props }: ChatProviderProps & { @@ -249,9 +245,7 @@ const Provider = ({ chatId, outLinkAuthData, getHistoryResponseData, - chatType, - showRawSource, - showNodeStatus + chatType }; return {children}; diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx index 0dd714cd5..a603216d0 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ChatItem.tsx @@ -25,6 +25,7 @@ import { isEqual } from 'lodash'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; import { formatTimeToChatItemTime } from '@fastgpt/global/common/string/time'; import dayjs from 'dayjs'; +import { ChatItemContext } from '@/web/core/chat/context/chatItemContext'; const colorMap = { [ChatStatusEnum.loading]: { @@ -139,7 +140,7 @@ const ChatItem = (props: Props) => { const isChatting = useContextSelector(ChatBoxContext, (v) => v.isChatting); const chatType = useContextSelector(ChatBoxContext, (v) => v.chatType); - const showNodeStatus = useContextSelector(ChatBoxContext, (v) => v.showNodeStatus); + const showNodeStatus = useContextSelector(ChatItemContext, (v) => v.showNodeStatus); const isChatLog = chatType === 'log'; const { copyData } = useCopyData(); diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/QuoteModal.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/QuoteModal.tsx index 091125ad7..a8457d0b0 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/QuoteModal.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/QuoteModal.tsx @@ -9,19 +9,16 @@ import RawSourceBox from '@/components/core/dataset/RawSourceBox'; import { getWebReqUrl } from '@fastgpt/web/common/system/utils'; import { useContextSelector } from 'use-context-selector'; import { ChatBoxContext } from '../Provider'; +import { ChatItemContext } from '@/web/core/chat/context/chatItemContext'; const QuoteModal = ({ rawSearch = [], onClose, - canEditDataset, - showRawSource, chatItemId, metadata }: { rawSearch: SearchDataResponseItemType[]; onClose: () => void; - canEditDataset: boolean; - showRawSource: boolean; chatItemId: string; metadata?: { collectionId: string; @@ -47,6 +44,11 @@ const QuoteModal = ({ chatItemId, ...(v.outLinkAuthData || {}) })); + const showRawSource = useContextSelector(ChatItemContext, (v) => v.isShowReadRawSource); + const showRouteToDatasetDetail = useContextSelector( + ChatItemContext, + (v) => v.showRouteToDatasetDetail + ); return ( <> @@ -71,12 +73,7 @@ const QuoteModal = ({ } > - + @@ -87,14 +84,10 @@ export default QuoteModal; export const QuoteList = React.memo(function QuoteList({ chatItemId, - rawSearch = [], - canEditDataset, - canViewSource + rawSearch = [] }: { chatItemId?: string; rawSearch: SearchDataResponseItemType[]; - canEditDataset: boolean; - canViewSource: boolean; }) { const theme = useTheme(); @@ -104,6 +97,11 @@ export const QuoteList = React.memo(function QuoteList({ chatId: v.chatId, ...(v.outLinkAuthData || {}) })); + const showRawSource = useContextSelector(ChatItemContext, (v) => v.isShowReadRawSource); + const showRouteToDatasetDetail = useContextSelector( + ChatItemContext, + (v) => v.showRouteToDatasetDetail + ); return ( <> @@ -120,8 +118,8 @@ export const QuoteList = React.memo(function QuoteList({ > diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx index eda19e7e2..7be7111e5 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/components/ResponseTags.tsx @@ -49,7 +49,6 @@ const ResponseTags = ({ const [quoteFolded, setQuoteFolded] = useState(true); const chatType = useContextSelector(ChatBoxContext, (v) => v.chatType); - const showRawSource = useContextSelector(ChatBoxContext, (v) => v.showRawSource); const notSharePage = useMemo(() => chatType !== 'share', [chatType]); const { @@ -251,8 +250,6 @@ const ResponseTags = ({ setQuoteModalData(undefined)} /> )} diff --git a/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx b/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx index 939d152aa..54534b622 100644 --- a/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx +++ b/projects/app/src/components/core/chat/ChatContainer/ChatBox/index.tsx @@ -18,7 +18,6 @@ import { Box, Checkbox } from '@chakra-ui/react'; import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus'; import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt'; import { useForm } from 'react-hook-form'; -import { useRouter } from 'next/router'; import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useTranslation } from 'next-i18next'; import { diff --git a/projects/app/src/components/core/chat/components/WholeResponseModal.tsx b/projects/app/src/components/core/chat/components/WholeResponseModal.tsx index 8dfc6a320..d6078e193 100644 --- a/projects/app/src/components/core/chat/components/WholeResponseModal.tsx +++ b/projects/app/src/components/core/chat/components/WholeResponseModal.tsx @@ -249,14 +249,7 @@ export const WholeResponseContent = ({ {activeModule.quoteList && activeModule.quoteList.length > 0 && ( - } + rawDom={} /> )} diff --git a/projects/app/src/components/core/dataset/RawSourceBox.tsx b/projects/app/src/components/core/dataset/RawSourceBox.tsx index 7957adfe0..4b860d8dc 100644 --- a/projects/app/src/components/core/dataset/RawSourceBox.tsx +++ b/projects/app/src/components/core/dataset/RawSourceBox.tsx @@ -5,7 +5,6 @@ import { useTranslation } from 'next-i18next'; import { getCollectionSourceAndOpen } from '@/web/core/dataset/hooks/readCollectionSource'; import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils'; import MyIcon from '@fastgpt/web/components/common/Icon'; -import { useI18n } from '@/web/context/I18n'; import type { readCollectionSourceBody } from '@/pages/api/core/dataset/collection/read'; type Props = BoxProps & @@ -33,7 +32,6 @@ const RawSourceBox = ({ ...props }: Props) => { const { t } = useTranslation(); - const { fileT } = useI18n(); const canPreview = !!sourceId && canView; @@ -51,7 +49,7 @@ const RawSourceBox = ({ return ( void }) { const { t } = useTranslation(); - const { loadAndGetTeamMembers } = useUserStore(); - const [inputValue, setInputValue] = React.useState(''); - const { data: teamMembers = [] } = useRequest2(loadAndGetTeamMembers, { - manual: false + const { data: teamMembers, ScrollData } = useScrollPagination(getTeamMembers, { + pageSize: 15 }); + const memberList = teamMembers.filter((item) => { return item.memberName.includes(inputValue); }); @@ -101,11 +101,6 @@ export function ChangeOwnerModal({ onOpenMemberListMenu(); setSelectedMember(null); }} - // onBlur={() => { - // setTimeout(() => { - // onCloseMemberListMenu(); - // }, 10); - // }} {...(selectedMember && { pl: '10' })} /> @@ -123,26 +118,28 @@ export function ChangeOwnerModal({ maxH={'300px'} overflow={'auto'} > - {memberList.map((item) => ( - { - setInputValue(item.memberName); - setSelectedMember(item); - onCloseMemberListMenu(); - }} - > - - - {item.memberName} - - - ))} + + {memberList.map((item) => ( + { + setInputValue(item.memberName); + setSelectedMember(item); + onCloseMemberListMenu(); + }} + > + + + {item.memberName} + + + ))} + )} diff --git a/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx b/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx index e51049b33..4d77f9ef8 100644 --- a/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx +++ b/projects/app/src/components/support/permission/MemberManager/MemberModal.tsx @@ -33,6 +33,10 @@ import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type import { OrgType } from '@fastgpt/global/support/user/team/org/type'; import { useContextSelector } from 'use-context-selector'; import { CollaboratorContext } from './context'; +import { getTeamMembers } from '@/web/support/user/team/api'; +import { getGroupList } from '@/web/support/user/team/group/api'; +import { getOrgList } from '@/web/support/user/team/org/api'; +import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; const HoverBoxStyle = { bgColor: 'myGray.50', @@ -47,30 +51,27 @@ function MemberModal({ addPermissionOnly?: boolean; }) { const { t } = useTranslation(); - const { userInfo, loadAndGetTeamMembers, loadAndGetGroups, loadAndGetOrgs } = useUserStore(); - + const { userInfo } = useUserStore(); const collaboratorList = useContextSelector(CollaboratorContext, (v) => v.collaboratorList); - const [searchText, setSearchText] = useState(''); const [filterClass, setFilterClass] = useState<'member' | 'org' | 'group'>(); + const { data: members, ScrollData } = useScrollPagination(getTeamMembers, { + pageSize: 15 + }); - const { data: [members = [], groups = [], orgs = []] = [], loading: loadingMembersAndGroups } = - useRequest2( - async () => { - if (!userInfo?.team?.teamId) return [[], []]; - return Promise.all([ - loadAndGetTeamMembers(true), - loadAndGetGroups(true), - loadAndGetOrgs(true) - ]); - }, - { - manual: false, - refreshDeps: [userInfo?.team?.teamId] - } - ); + const { data: [groups = [], orgs = []] = [], loading: loadingGroupsAndOrgs } = useRequest2( + async () => { + if (!userInfo?.team?.teamId) return [[], []]; + return Promise.all([getGroupList(), getOrgList()]); + }, + { + manual: false, + refreshDeps: [userInfo?.team?.teamId] + } + ); const [parentPath, setParentPath] = useState(''); + const paths = useMemo(() => { const splitPath = parentPath.split('/').filter(Boolean); return splitPath @@ -212,7 +213,7 @@ function MemberModal({ h={'100%'} maxH={'90vh'} isCentered - isLoading={loadingMembersAndGroups} + isLoading={loadingGroupsAndOrgs} > + {/* Entry */} {!searchText && !filterClass && ( <> {entryList.current.map((item) => { @@ -298,142 +300,135 @@ function MemberModal({ )} - - {filterMembers.map((member) => { - const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId); - const disabled = addOnly && collaborator !== undefined; - const onChange = () => { - if (disabled) return; - setSelectedMembers((state) => { - if (state.includes(member.tmbId)) { - return state.filter((v) => v !== member.tmbId); - } - return [...state, member.tmbId]; - }); - }; - return ( - - - - - {member.memberName} - - - - ); - })} - {filterOrgs.map((org) => { - const collaborator = collaboratorList?.find((v) => v.orgId === org._id); - const disabled = addOnly && collaborator !== undefined; - const onChange = () => { - if (disabled) return; - setSelectedOrgIdList((state) => { - if (state.includes(org._id)) { - return state.filter((v) => v !== org._id); - } - return [...state, org._id]; - }); - }; - return ( - - - - - {org.name} + {filterClass && ( + + {filterOrgs.map((org) => { + const onChange = () => { + setSelectedOrgIdList((state) => { + if (state.includes(org._id)) { + return state.filter((v) => v !== org._id); + } + return [...state, org._id]; + }); + }; + const collaborator = collaboratorList?.find((v) => v.orgId === org._id); + return ( + + + + + {org.name} + {org.count && ( + <> + + {org.count} + + + )} + + {org.count && ( - - {org.count} - + { + setParentPath(getOrgChildrenPath(org)); + }} + /> )} - - {org.count && ( - { - e.stopPropagation(); - setParentPath(getOrgChildrenPath(org)); - }} + ); + })} + {filterMembers.map((member) => { + const onChange = () => { + setSelectedMembers((state) => { + if (state.includes(member.tmbId)) { + return state.filter((v) => v !== member.tmbId); + } + return [...state, member.tmbId]; + }); + }; + const collaborator = collaboratorList?.find((v) => v.tmbId === member.tmbId); + return ( + + - )} - - ); - })} - {filterGroups.map((group) => { - const collaborator = collaboratorList?.find((v) => v.groupId === group._id); - const disabled = addOnly && collaborator !== undefined; - const onChange = () => { - if (disabled) return; - setSelectedGroupIdList((state) => { - if (state.includes(group._id)) { - return state.filter((v) => v !== group._id); - } - return [...state, group._id]; - }); - }; - return ( - - - - - {group.name === DefaultGroupName ? userInfo?.team.teamName : group.name} - - - - ); - })} - + + + {member.memberName} + + + + ); + })} + {filterGroups.map((group) => { + const onChange = () => { + setSelectedGroupIdList((state) => { + if (state.includes(group._id)) { + return state.filter((v) => v !== group._id); + } + return [...state, group._id]; + }); + }; + const collaborator = collaboratorList?.find((v) => v.groupId === group._id); + return ( + + + + + {group.name === DefaultGroupName ? userInfo?.team.teamName : group.name} + + + + ); + })} + + )} diff --git a/projects/app/src/global/core/api/appReq.d.ts b/projects/app/src/global/core/api/appReq.d.ts index 7668e5355..59a039afc 100644 --- a/projects/app/src/global/core/api/appReq.d.ts +++ b/projects/app/src/global/core/api/appReq.d.ts @@ -1,7 +1,7 @@ -import { RequestPaging } from '@/types'; +import { PaginationProps } from '@fastgpt/web/common/fetch/type'; -export type GetAppChatLogsParams = RequestPaging & { +export type GetAppChatLogsParams = PaginationProps<{ appId: string; dateStart: Date; dateEnd: Date; -}; +}>; diff --git a/projects/app/src/global/core/api/datasetReq.d.ts b/projects/app/src/global/core/api/datasetReq.d.ts index e896df0e2..22c691466 100644 --- a/projects/app/src/global/core/api/datasetReq.d.ts +++ b/projects/app/src/global/core/api/datasetReq.d.ts @@ -3,24 +3,24 @@ import { DatasetCollectionTypeEnum, DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; -import type { RequestPaging } from '@/types'; import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants'; import type { SearchTestItemType } from '@/types/core/dataset'; import { UploadChunkItemType } from '@fastgpt/global/core/dataset/type'; import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type'; import { PermissionTypeEnum } from '@fastgpt/global/support/permission/constant'; import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d'; +import { PaginationProps } from '@fastgpt/web/common/fetch/type'; /* ===== dataset ===== */ /* ======= collections =========== */ -export type GetDatasetCollectionsProps = RequestPaging & { +export type GetDatasetCollectionsProps = PaginationProps<{ datasetId: string; parentId?: string; searchText?: string; filterTags?: string[]; simple?: boolean; selectFolder?: boolean; -}; +}>; /* ==== data ===== */ diff --git a/projects/app/src/pages/account/team/components/EditInfoModal.tsx b/projects/app/src/pageComponents/account/team/EditInfoModal.tsx similarity index 100% rename from projects/app/src/pages/account/team/components/EditInfoModal.tsx rename to projects/app/src/pageComponents/account/team/EditInfoModal.tsx diff --git a/projects/app/src/pages/account/team/components/GroupManage/GroupInfoModal.tsx b/projects/app/src/pageComponents/account/team/GroupManage/GroupInfoModal.tsx similarity index 96% rename from projects/app/src/pages/account/team/components/GroupManage/GroupInfoModal.tsx rename to projects/app/src/pageComponents/account/team/GroupManage/GroupInfoModal.tsx index c2fb98e06..9fd81a310 100644 --- a/projects/app/src/pages/account/team/components/GroupManage/GroupInfoModal.tsx +++ b/projects/app/src/pageComponents/account/team/GroupManage/GroupInfoModal.tsx @@ -55,7 +55,7 @@ function GroupInfoModal({ onClose, editGroupId }: { onClose: () => void; editGro } ); - const { run: onCreate, loading: isLoadingCreate } = useRequest2( + const { runAsync: onCreate, loading: isLoadingCreate } = useRequest2( (data: GroupFormType) => { return postCreateGroup({ name: data.name, @@ -67,7 +67,7 @@ function GroupInfoModal({ onClose, editGroupId }: { onClose: () => void; editGro } ); - const { run: onUpdate, loading: isLoadingUpdate } = useRequest2( + const { runAsync: onUpdate, loading: isLoadingUpdate } = useRequest2( async (data: GroupFormType) => { if (!editGroupId) return; return putUpdateGroup({ diff --git a/projects/app/src/pages/account/team/components/GroupManage/GroupManageMember.tsx b/projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx similarity index 90% rename from projects/app/src/pages/account/team/components/GroupManage/GroupManageMember.tsx rename to projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx index 74e4be634..95528e7ec 100644 --- a/projects/app/src/pages/account/team/components/GroupManage/GroupManageMember.tsx +++ b/projects/app/src/pageComponents/account/team/GroupManage/GroupManageMember.tsx @@ -32,26 +32,26 @@ export type GroupFormType = { }[]; }; +// 1. Owner can not be deleted, toast +// 2. Owner/Admin can manage members +// 3. Owner can add/remove admins function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGroupId?: string }) { - // 1. Owner can not be deleted, toast - // 2. Owner/Admin can manage members - // 3. Owner can add/remove admins const { t } = useTranslation(); const { userInfo } = useUserStore(); const { toast } = useToast(); - const [hoveredMemberId, setHoveredMemberId] = useState(undefined); - const { - members: allMembers, - refetchGroups, - groups, - refetchMembers - } = useContextSelector(TeamContext, (v) => v); + const groups = useContextSelector(TeamContext, (v) => v.groups); + const refetchGroups = useContextSelector(TeamContext, (v) => v.refetchGroups); const group = useMemo(() => { return groups.find((item) => item._id === editGroupId); }, [editGroupId, groups]); + const allMembers = useContextSelector(TeamContext, (v) => v.members); + const refetchMembers = useContextSelector(TeamContext, (v) => v.refetchMembers); + const MemberScrollData = useContextSelector(TeamContext, (v) => v.MemberScrollData); + const [hoveredMemberId, setHoveredMemberId] = useState(); const [members, setMembers] = useState(group?.members || []); + const [searchKey, setSearchKey] = useState(''); const filtered = useMemo(() => { return [ @@ -62,7 +62,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro ]; }, [searchKey, allMembers]); - const { run: onUpdate, loading: isLoadingUpdate } = useRequest2( + const { runAsync: onUpdate, loading: isLoadingUpdate } = useRequest2( async () => { if (!editGroupId || !members.length) return; return putUpdateGroup({ @@ -155,7 +155,7 @@ function GroupEditModal({ onClose, editGroupId }: { onClose: () => void; editGro setSearchKey(e.target.value); }} /> - + {filtered.map((member) => { return ( void; editGro ); })} - + {t('common:chosen') + ': ' + members.length} - + {members.map((member) => { return ( void; editGro ); })} - + - + + diff --git a/projects/app/src/pages/account/team/components/GroupManage/GroupTransferOwnerModal.tsx b/projects/app/src/pageComponents/account/team/GroupManage/GroupTransferOwnerModal.tsx similarity index 100% rename from projects/app/src/pages/account/team/components/GroupManage/GroupTransferOwnerModal.tsx rename to projects/app/src/pageComponents/account/team/GroupManage/GroupTransferOwnerModal.tsx diff --git a/projects/app/src/pages/account/team/components/GroupManage/index.tsx b/projects/app/src/pageComponents/account/team/GroupManage/index.tsx similarity index 89% rename from projects/app/src/pages/account/team/components/GroupManage/index.tsx rename to projects/app/src/pageComponents/account/team/GroupManage/index.tsx index ba25e90b6..bd568d815 100644 --- a/projects/app/src/pages/account/team/components/GroupManage/index.tsx +++ b/projects/app/src/pageComponents/account/team/GroupManage/index.tsx @@ -24,50 +24,43 @@ import { useUserStore } from '@/web/support/user/useUserStore'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { deleteGroup } from '@/web/support/user/team/group/api'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; -import MemberTag from '../../../../../components/support/user/team/Info/MemberTag'; +import MemberTag from '../../../../components/support/user/team/Info/MemberTag'; import MyTooltip from '@fastgpt/web/components/common/MyTooltip'; import dynamic from 'next/dynamic'; import { useState } from 'react'; import IconButton from '../OrgManage/IconButton'; +import { MemberGroupType } from '@fastgpt/global/support/permission/memberGroup/type'; const ChangeOwnerModal = dynamic(() => import('./GroupTransferOwnerModal')); const GroupInfoModal = dynamic(() => import('./GroupInfoModal')); -const ManageGroupMemberModal = dynamic(() => import('./GroupManageMember')); +const GroupManageMember = dynamic(() => import('./GroupManageMember')); function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { const { t } = useTranslation(); const { userInfo } = useUserStore(); - const [editGroupId, setEditGroupId] = useState(); + const { groups, refetchGroups, members, refetchMembers } = useContextSelector( + TeamContext, + (v) => v + ); + + const [editGroup, setEditGroup] = useState(); const { isOpen: isOpenGroupInfo, onOpen: onOpenGroupInfo, onClose: onCloseGroupInfo } = useDisclosure(); - const { - isOpen: isOpenManageGroupMember, - onOpen: onOpenManageGroupMember, - onClose: onCloseManageGroupMember - } = useDisclosure(); - const onEditGroup = (groupId: string) => { - setEditGroupId(groupId); + + const onEditGroupInfo = (e: MemberGroupType) => { + setEditGroup(e); onOpenGroupInfo(); }; - const onManageMember = (groupId: string) => { - setEditGroupId(groupId); - onOpenManageGroupMember(); - }; const { ConfirmModal: ConfirmDeleteGroupModal, openConfirm: openDeleteGroupModal } = useConfirm({ type: 'delete', content: t('account_team:confirm_delete_group') }); - const { groups, refetchGroups, members, refetchMembers } = useContextSelector( - TeamContext, - (v) => v - ); - const { runAsync: delDeleteGroup } = useRequest2(deleteGroup, { onSuccess: () => { refetchGroups(); @@ -75,12 +68,21 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { } }); + const { + isOpen: isOpenManageGroupMember, + onOpen: onOpenManageGroupMember, + onClose: onCloseManageGroupMember + } = useDisclosure(); + const onManageMember = (e: MemberGroupType) => { + setEditGroup(e); + onOpenManageGroupMember(); + }; + const hasGroupManagePer = (group: (typeof groups)[0]) => userInfo?.team.permission.hasManagePer || ['admin', 'owner'].includes( group.members.find((item) => item.tmbId === userInfo?.team.tmbId)?.role ?? '' ); - const isGroupOwner = (group: (typeof groups)[0]) => userInfo?.team.permission.hasManagePer || group.members.find((item) => item.role === 'owner')?.tmbId === userInfo?.team.tmbId; @@ -90,8 +92,8 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { onOpen: onOpenChangeOwner, onClose: onCloseChangeOwner } = useDisclosure(); - const onChangeOwner = (groupId: string) => { - setEditGroupId(groupId); + const onChangeOwner = (e: MemberGroupType) => { + setEditGroup(e); onOpenChangeOwner(); }; @@ -173,7 +175,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { v.avatar)} groupId={group._id} /> ) : hasGroupManagePer(group) ? ( - onManageMember(group._id)}> + onManageMember(group)}> members.find((m) => m.tmbId === v.tmbId)?.avatar ?? '' @@ -202,14 +204,14 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { label: t('account_team:edit_info'), icon: 'edit', onClick: () => { - onEditGroup(group._id); + onEditGroupInfo(group); } }, { label: t('account_team:manage_member'), icon: 'support/team/group', onClick: () => { - onManageMember(group._id); + onManageMember(group); } }, ...(isGroupOwner(group) @@ -218,7 +220,7 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { label: t('account_team:transfer_ownership'), icon: 'modal/changePer', onClick: () => { - onChangeOwner(group._id); + onChangeOwner(group); }, type: 'primary' as MenuItemType }, @@ -246,25 +248,25 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { - {isOpenChangeOwner && editGroupId && ( - + {isOpenChangeOwner && editGroup && ( + )} {isOpenGroupInfo && ( { onCloseGroupInfo(); - setEditGroupId(undefined); + setEditGroup(undefined); }} - editGroupId={editGroupId} + editGroupId={editGroup?._id} /> )} - {isOpenManageGroupMember && ( - { onCloseManageGroupMember(); - setEditGroupId(undefined); + setEditGroup(undefined); }} - editGroupId={editGroupId} + editGroupId={editGroup._id} /> )} diff --git a/projects/app/src/pages/account/team/components/InviteModal.tsx b/projects/app/src/pageComponents/account/team/InviteModal.tsx similarity index 100% rename from projects/app/src/pages/account/team/components/InviteModal.tsx rename to projects/app/src/pageComponents/account/team/InviteModal.tsx diff --git a/projects/app/src/pages/account/team/components/MemberTable.tsx b/projects/app/src/pageComponents/account/team/MemberTable.tsx similarity index 53% rename from projects/app/src/pages/account/team/components/MemberTable.tsx rename to projects/app/src/pageComponents/account/team/MemberTable.tsx index 08b9af2bc..05eabd09b 100644 --- a/projects/app/src/pages/account/team/components/MemberTable.tsx +++ b/projects/app/src/pageComponents/account/team/MemberTable.tsx @@ -13,7 +13,6 @@ import { Tr, useDisclosure } from '@chakra-ui/react'; -import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; import { useTranslation } from 'next-i18next'; import { useUserStore } from '@/web/support/user/useUserStore'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; @@ -30,6 +29,9 @@ import { useToast } from '@fastgpt/web/hooks/useToast'; import { TeamErrEnum } from '@fastgpt/global/common/error/code/team'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { delLeaveTeam } from '@/web/support/user/team/api'; +import { postSyncMembers } from '@/web/support/user/api'; +import MyLoading from '@fastgpt/web/components/common/MyLoading'; +import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; const InviteModal = dynamic(() => import('./InviteModal')); const TeamTagModal = dynamic(() => import('@/components/support/user/team/TeamTagModal')); @@ -40,8 +42,16 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { const { userInfo, teamPlanStatus } = useUserStore(); const { feConfigs, setNotSufficientModalType } = useSystemStore(); - const { groups, refetchGroups, myTeams, refetchTeams, members, refetchMembers, onSwitchTeam } = - useContextSelector(TeamContext, (v) => v); + const { + groups, + refetchGroups, + myTeams, + refetchTeams, + members, + refetchMembers, + onSwitchTeam, + MemberScrollData + } = useContextSelector(TeamContext, (v) => v); const { isOpen: isOpenTeamTagsAsync, @@ -54,6 +64,8 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { type: 'delete' }); + const isSyncMember = feConfigs.register_method?.includes('sync'); + const { runAsync: onLeaveTeam } = useRequest2( async () => { const defaultTeam = myTeams.find((item) => item.defaultTeam) || myTeams[0]; @@ -72,8 +84,17 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { content: t('account_team:confirm_leave_team') }); + const { runAsync: onSyncMember, loading: isSyncing } = useRequest2(postSyncMembers, { + onSuccess() { + refetchMembers(); + }, + successToast: t('account_team:sync_member_success'), + errorToast: t('account_team:sync_member_failed') + }); + return ( <> + {isSyncing && } {Tabs} @@ -91,7 +112,21 @@ function MemberTable({ Tabs }: { Tabs: React.ReactNode }) { {t('account_team:label_sync')} )} - {userInfo?.team.permission.hasManagePer && ( + {userInfo?.team.permission.hasManagePer && isSyncMember && ( + + )} + {userInfo?.team.permission.hasManagePer && !isSyncMember && ( diff --git a/projects/app/src/pages/account/team/components/OrgManage/OrgMoveModal.tsx b/projects/app/src/pageComponents/account/team/OrgManage/OrgMoveModal.tsx similarity index 100% rename from projects/app/src/pages/account/team/components/OrgManage/OrgMoveModal.tsx rename to projects/app/src/pageComponents/account/team/OrgManage/OrgMoveModal.tsx diff --git a/projects/app/src/pages/account/team/components/OrgManage/OrgTree.tsx b/projects/app/src/pageComponents/account/team/OrgManage/OrgTree.tsx similarity index 100% rename from projects/app/src/pages/account/team/components/OrgManage/OrgTree.tsx rename to projects/app/src/pageComponents/account/team/OrgManage/OrgTree.tsx diff --git a/projects/app/src/pageComponents/account/team/OrgManage/index.tsx b/projects/app/src/pageComponents/account/team/OrgManage/index.tsx new file mode 100644 index 000000000..ddc080ba5 --- /dev/null +++ b/projects/app/src/pageComponents/account/team/OrgManage/index.tsx @@ -0,0 +1,369 @@ +import { useUserStore } from '@/web/support/user/useUserStore'; +import { + Box, + Divider, + Flex, + HStack, + Table, + TableContainer, + Tag, + Tbody, + Td, + Th, + Thead, + Tr, + VStack +} from '@chakra-ui/react'; +import type { OrgType } from '@fastgpt/global/support/user/team/org/type'; +import Avatar from '@fastgpt/web/components/common/Avatar'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import type { IconNameType } from '@fastgpt/web/components/common/Icon/type'; +import MyMenu from '@fastgpt/web/components/common/MyMenu'; +import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; +import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; +import { useTranslation } from 'next-i18next'; +import { useMemo, useState } from 'react'; +import { useContextSelector } from 'use-context-selector'; +import MemberTag from '@/components/support/user/team/Info/MemberTag'; +import { TeamContext } from '../context'; +import { deleteOrg, deleteOrgMember, getOrgList } from '@/web/support/user/team/org/api'; + +import IconButton from './IconButton'; +import { defaultOrgForm, type OrgFormType } from './OrgInfoModal'; + +import dynamic from 'next/dynamic'; +import MyBox from '@fastgpt/web/components/common/MyBox'; +import Path from '@/components/common/folder/Path'; +import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type'; +import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant'; +import { useSystemStore } from '@/web/common/system/useSystemStore'; + +const OrgInfoModal = dynamic(() => import('./OrgInfoModal')); +const OrgMemberManageModal = dynamic(() => import('./OrgMemberManageModal')); +const OrgMoveModal = dynamic(() => import('./OrgMoveModal')); + +function ActionButton({ + icon, + text, + onClick +}: { + icon: IconNameType; + text: string; + onClick: () => void; +}) { + return ( + + + {text} + + ); +} + +function OrgTable({ Tabs }: { Tabs: React.ReactNode }) { + const { t } = useTranslation(); + const { userInfo, isTeamAdmin } = useUserStore(); + + const { members, MemberScrollData } = useContextSelector(TeamContext, (v) => v); + const { feConfigs } = useSystemStore(); + + const isSyncMember = feConfigs.register_method?.includes('sync'); + const [parentPath, setParentPath] = useState(''); + const { + data: orgs = [], + loading: isLoadingOrgs, + refresh: refetchOrgs + } = useRequest2(getOrgList, { + manual: false, + refreshDeps: [userInfo?.team?.teamId] + }); + const currentOrgs = useMemo(() => { + if (orgs.length === 0) return []; + // Auto select the first org(root org is team) + if (parentPath === '') { + setParentPath(getOrgChildrenPath(orgs[0])); + return []; + } + + return orgs + .filter((org) => org.path === parentPath) + .map((item) => { + return { + ...item, + // Member + org + count: + item.members.length + orgs.filter((org) => org.path === getOrgChildrenPath(item)).length + }; + }); + }, [orgs, parentPath]); + const currentOrg = useMemo(() => { + const splitPath = parentPath.split('/'); + const currentOrgId = splitPath[splitPath.length - 1]; + if (!currentOrgId) return; + + return orgs.find((org) => org.pathId === currentOrgId); + }, [orgs, parentPath]); + + const paths = useMemo(() => { + const splitPath = parentPath.split('/').filter(Boolean); + return splitPath + .map((id) => { + const org = orgs.find((org) => org.pathId === id)!; + + if (org.path === '') return; + + return { + parentId: getOrgChildrenPath(org), + parentName: org.name + }; + }) + .filter(Boolean) as ParentTreePathItemType[]; + }, [parentPath, orgs]); + + const [editOrg, setEditOrg] = useState(); + const [manageMemberOrg, setManageMemberOrg] = useState(); + const [movingOrg, setMovingOrg] = useState(); + + // Delete org + const { ConfirmModal: ConfirmDeleteOrgModal, openConfirm: openDeleteOrgModal } = useConfirm({ + type: 'delete', + content: t('account_team:confirm_delete_org') + }); + const deleteOrgHandler = (orgId: string) => openDeleteOrgModal(() => deleteOrgReq(orgId))(); + const { runAsync: deleteOrgReq } = useRequest2(deleteOrg, { + onSuccess: () => { + refetchOrgs(); + } + }); + + // Delete member + const { ConfirmModal: ConfirmDeleteMember, openConfirm: openDeleteMemberModal } = useConfirm({ + type: 'delete', + content: t('account_team:confirm_delete_member') + }); + const { runAsync: deleteMemberReq } = useRequest2(deleteOrgMember, { + onSuccess: () => { + refetchOrgs(); + } + }); + + return ( + <> + + {Tabs} + + + + + + + + {/* Table */} + + + + + + {!isSyncMember && ( + + )} + + + + {currentOrgs.map((org) => ( + + + {isTeamAdmin && !isSyncMember && ( + + )} + + ))} + {currentOrg?.members.map((member) => { + const memberInfo = members.find((m) => m.tmbId === member.tmbId); + if (!memberInfo) return null; + + return ( + + + + + ); + })} + +
+ {t('common:Name')} + + {t('common:common.Action')} +
+ setParentPath(getOrgChildrenPath(org))} + > + + {org.count} + + + + } + menuList={[ + { + children: [ + { + icon: 'edit', + label: t('account_team:edit_info'), + onClick: () => setEditOrg(org) + }, + { + icon: 'common/file/move', + label: t('common:Move'), + onClick: () => setMovingOrg(org) + }, + { + icon: 'delete', + label: t('account_team:delete'), + type: 'danger', + onClick: () => deleteOrgHandler(org._id) + } + ] + } + ]} + /> +
+ + + {isTeamAdmin && !isSyncMember && ( + } + menuList={[ + { + children: [ + { + icon: 'delete', + label: t('account_team:delete'), + type: 'danger', + onClick: () => + openDeleteMemberModal(() => + deleteMemberReq(currentOrg._id, member.tmbId) + )() + } + ] + } + ]} + /> + )} +
+
+
+ + {/* Slider */} + {!isSyncMember && ( + + + + + {currentOrg?.name} + + {currentOrg?.path !== '' && ( + setEditOrg(currentOrg)} /> + )} + + {currentOrg?.description || t('common:common.no_intro')} + + + + + {t('common:common.Action')} + + {currentOrg && isTeamAdmin && ( + + { + setEditOrg({ + ...defaultOrgForm, + parentId: currentOrg?._id + }); + }} + /> + setManageMemberOrg(currentOrg)} + /> + {currentOrg?.path !== '' && ( + <> + setMovingOrg(currentOrg)} + /> + deleteOrgHandler(currentOrg._id)} + /> + + )} + + )} + + )} +
+
+ + {!!editOrg && ( + setEditOrg(undefined)} + onSuccess={refetchOrgs} + /> + )} + {!!movingOrg && ( + setMovingOrg(undefined)} + onSuccess={refetchOrgs} + /> + )} + {!!manageMemberOrg && ( + setManageMemberOrg(undefined)} + /> + )} + + + + + ); +} + +export default OrgTable; diff --git a/projects/app/src/pages/account/team/components/PermissionManage/index.tsx b/projects/app/src/pageComponents/account/team/PermissionManage/index.tsx similarity index 99% rename from projects/app/src/pages/account/team/components/PermissionManage/index.tsx rename to projects/app/src/pageComponents/account/team/PermissionManage/index.tsx index 4cf9860d4..b2228a3c3 100644 --- a/projects/app/src/pages/account/team/components/PermissionManage/index.tsx +++ b/projects/app/src/pageComponents/account/team/PermissionManage/index.tsx @@ -24,7 +24,7 @@ import { import { useUserStore } from '@/web/support/user/useUserStore'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import Avatar from '@fastgpt/web/components/common/Avatar'; -import MemberTag from '../../../../../components/support/user/team/Info/MemberTag'; +import MemberTag from '../../../../components/support/user/team/Info/MemberTag'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; import { TeamManagePermissionVal, diff --git a/projects/app/src/pages/account/team/components/SelectMember.tsx b/projects/app/src/pageComponents/account/team/SelectMember.tsx similarity index 100% rename from projects/app/src/pages/account/team/components/SelectMember.tsx rename to projects/app/src/pageComponents/account/team/SelectMember.tsx diff --git a/projects/app/src/pages/account/team/components/context.tsx b/projects/app/src/pageComponents/account/team/context.tsx similarity index 86% rename from projects/app/src/pages/account/team/components/context.tsx rename to projects/app/src/pageComponents/account/team/context.tsx index b76ed99c3..c4fd1ad05 100644 --- a/projects/app/src/pages/account/team/components/context.tsx +++ b/projects/app/src/pageComponents/account/team/context.tsx @@ -2,7 +2,7 @@ import React, { ReactNode, useState } from 'react'; import { createContext } from 'use-context-selector'; import type { EditTeamFormDataType } from './EditInfoModal'; import dynamic from 'next/dynamic'; -import { getTeamList, putSwitchTeam } from '@/web/support/user/team/api'; +import { getTeamList, getTeamMembers, putSwitchTeam } from '@/web/support/user/team/api'; import { TeamMemberStatusEnum } from '@fastgpt/global/support/user/team/constant'; import { useUserStore } from '@/web/support/user/useUserStore'; import type { TeamTmbItemType, TeamMemberItemType } from '@fastgpt/global/support/user/team/type'; @@ -10,7 +10,7 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { useTranslation } from 'next-i18next'; import { getGroupList } from '@/web/support/user/team/group/api'; import { MemberGroupListType } from '@fastgpt/global/support/permission/memberGroup/type'; -import { OrgType } from '@fastgpt/global/support/user/team/org/type'; +import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; const EditInfoModal = dynamic(() => import('./EditInfoModal')); @@ -26,6 +26,7 @@ type TeamModalContextType = { refetchTeams: () => void; refetchGroups: () => void; teamSize: number; + MemberScrollData: ReturnType['ScrollData']; }; export const TeamContext = createContext({ @@ -49,13 +50,14 @@ export const TeamContext = createContext({ throw new Error('Function not implemented.'); }, - teamSize: 0 + teamSize: 0, + MemberScrollData: () => <> }); export const TeamModalContextProvider = ({ children }: { children: ReactNode }) => { const { t } = useTranslation(); const [editTeamData, setEditTeamData] = useState(); - const { userInfo, initUserInfo, loadAndGetTeamMembers } = useUserStore(); + const { userInfo, initUserInfo } = useUserStore(); const { data: myTeams = [], @@ -69,18 +71,11 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode }) // member action const { data: members = [], - runAsync: refetchMembers, - loading: loadingMembers - } = useRequest2( - () => { - if (!userInfo?.team?.teamId) return Promise.resolve([]); - return loadAndGetTeamMembers(true); - }, - { - manual: false, - refreshDeps: [userInfo?.team?.teamId] - } - ); + isLoading: loadingMembers, + refreshList: refetchMembers, + total: memberTotal, + ScrollData: MemberScrollData + } = useScrollPagination(getTeamMembers, {}); const { runAsync: onSwitchTeam, loading: isSwitchingTeam } = useRequest2( async (teamId: string) => { @@ -115,7 +110,8 @@ export const TeamModalContextProvider = ({ children }: { children: ReactNode }) refetchMembers, groups, refetchGroups, - teamSize: members.length + teamSize: memberTotal, + MemberScrollData }; return ( diff --git a/projects/app/src/pages/account/bill/components/BillTable.tsx b/projects/app/src/pages/account/bill/components/BillTable.tsx index 08c744692..228de85cb 100644 --- a/projects/app/src/pages/account/bill/components/BillTable.tsx +++ b/projects/app/src/pages/account/bill/components/BillTable.tsx @@ -1,4 +1,4 @@ -import React, { useState, useCallback, useMemo, useEffect } from 'react'; +import React, { useState, useMemo, useEffect } from 'react'; import { Button, Table, @@ -25,7 +25,6 @@ import { billStatusMap, billTypeMap } from '@fastgpt/global/support/wallet/bill/constants'; -// import { usePagination } from '@/web/common/hooks/usePagination'; import MyBox from '@fastgpt/web/components/common/MyBox'; import { useRequest } from '@fastgpt/web/hooks/useRequest'; import { standardSubLevelMap, subModeMap } from '@fastgpt/global/support/wallet/sub/constants'; @@ -33,25 +32,23 @@ import MySelect from '@fastgpt/web/components/common/MySelect'; import MyModal from '@fastgpt/web/components/common/MyModal'; import { usePagination } from '@fastgpt/web/hooks/usePagination'; import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; -import { useI18n } from '@/web/context/I18n'; const BillTable = () => { const { t } = useTranslation(); - const { commonT } = useI18n(); const { toast } = useToast(); - const [billType, setBillType] = useState(''); + const [billType, setBillType] = useState(undefined); const [billDetail, setBillDetail] = useState(); const billTypeList = useMemo( () => [ - { label: t('account_bill:all'), value: '' }, + { label: t('account_bill:all'), value: undefined }, ...Object.entries(billTypeMap).map(([key, value]) => ({ label: t(value.label as any), value: key })) ] as { label: string; - value: BillTypeEnum | ''; + value: BillTypeEnum | undefined; }[], [t] ); @@ -62,8 +59,7 @@ const BillTable = () => { Pagination, getData, total - } = usePagination({ - api: getBills, + } = usePagination(getBills, { pageSize: 20, params: { type: billType @@ -110,7 +106,7 @@ const BillTable = () => { # - + void }) { const { t } = useTranslation(); - const { commonT } = useI18n(); return ( { isLoading, Pagination, total - } = usePagination({ - api: getInvoiceRecords, + } = usePagination(getInvoiceRecords, { pageSize: 20 }); diff --git a/projects/app/src/pages/account/info/index.tsx b/projects/app/src/pages/account/info/index.tsx index 48c71cc31..1adbff216 100644 --- a/projects/app/src/pages/account/info/index.tsx +++ b/projects/app/src/pages/account/info/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo, useRef } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { Box, Flex, @@ -160,6 +160,7 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => { color: 'myGray.900' }; + const isSyncMember = feConfigs.register_method?.includes('sync'); return ( {/* user info */} @@ -224,6 +225,7 @@ const MyInfo = ({ onOpenContact }: { onOpenContact: () => void }) => { {t('account_info:member_name')}:  void }) => { const { feConfigs } = useSystemStore(); const { t } = useTranslation(); const { isPc } = useSystem(); - const { userInfo, updateUserInfo } = useUserStore(); - - const { reset } = useForm({ - defaultValues: userInfo as UserType - }); return ( diff --git a/projects/app/src/pages/account/inform.tsx b/projects/app/src/pages/account/inform.tsx index 176abe602..d0612c338 100644 --- a/projects/app/src/pages/account/inform.tsx +++ b/projects/app/src/pages/account/inform.tsx @@ -1,13 +1,12 @@ import React from 'react'; import { Box, Button, Flex, useTheme } from '@chakra-ui/react'; import { getInforms, readInform } from '@/web/support/user/inform/api'; -import type { UserInformSchema } from '@fastgpt/global/support/user/inform/type'; import { formatTimeToChatTime } from '@fastgpt/global/common/string/time'; import { usePagination } from '@fastgpt/web/hooks/usePagination'; import { useLoading } from '@fastgpt/web/hooks/useLoading'; import { useTranslation } from 'next-i18next'; import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; -import AccountContainer, { TabEnum } from './components/AccountContainer'; +import AccountContainer from './components/AccountContainer'; import { serviceSideProps } from '@fastgpt/web/common/system/nextjs'; const InformTable = () => { @@ -23,8 +22,7 @@ const InformTable = () => { Pagination, getData, pageNum - } = usePagination({ - api: getInforms, + } = usePagination(getInforms, { pageSize: 20 }); diff --git a/projects/app/src/pages/account/promotion.tsx b/projects/app/src/pages/account/promotion.tsx index be9ed529d..5da3e0b48 100644 --- a/projects/app/src/pages/account/promotion.tsx +++ b/projects/app/src/pages/account/promotion.tsx @@ -25,7 +25,7 @@ import { usePagination } from '@fastgpt/web/hooks/usePagination'; import { useLoading } from '@fastgpt/web/hooks/useLoading'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; -import AccountContainer, { TabEnum } from './components/AccountContainer'; +import AccountContainer from './components/AccountContainer'; import { serviceSideProps } from '@fastgpt/web/common/system/nextjs'; const Promotion = () => { @@ -41,8 +41,7 @@ const Promotion = () => { total, pageSize, Pagination - } = usePagination({ - api: getPromotionRecords, + } = usePagination(getPromotionRecords, { pageSize: 20 }); diff --git a/projects/app/src/pages/account/team/components/OrgManage/index.tsx b/projects/app/src/pages/account/team/components/OrgManage/index.tsx deleted file mode 100644 index 241fc1aee..000000000 --- a/projects/app/src/pages/account/team/components/OrgManage/index.tsx +++ /dev/null @@ -1,354 +0,0 @@ -import { deleteOrg, deleteOrgMember } from '@/web/support/user/team/org/api'; -import { useUserStore } from '@/web/support/user/useUserStore'; -import { - Box, - Divider, - Flex, - HStack, - Table, - TableContainer, - Tag, - Tbody, - Td, - Th, - Thead, - Tr, - VStack -} from '@chakra-ui/react'; -import type { OrgType } from '@fastgpt/global/support/user/team/org/type'; -import Avatar from '@fastgpt/web/components/common/Avatar'; -import MyIcon from '@fastgpt/web/components/common/Icon'; -import type { IconNameType } from '@fastgpt/web/components/common/Icon/type'; -import MyMenu from '@fastgpt/web/components/common/MyMenu'; -import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; -import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; -import { useTranslation } from 'next-i18next'; -import { useMemo, useState } from 'react'; -import { useContextSelector } from 'use-context-selector'; -import MemberTag from '@/components/support/user/team/Info/MemberTag'; -import { TeamContext } from '../context'; -import { getOrgList } from '@/web/support/user/team/org/api'; - -import IconButton from './IconButton'; -import { defaultOrgForm, type OrgFormType } from './OrgInfoModal'; - -import dynamic from 'next/dynamic'; -import MyBox from '@fastgpt/web/components/common/MyBox'; -import Path from '@/components/common/folder/Path'; -import { ParentTreePathItemType } from '@fastgpt/global/common/parentFolder/type'; -import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant'; - -const OrgInfoModal = dynamic(() => import('./OrgInfoModal')); -const OrgMemberManageModal = dynamic(() => import('./OrgMemberManageModal')); -const OrgMoveModal = dynamic(() => import('./OrgMoveModal')); - -function ActionButton({ - icon, - text, - onClick -}: { - icon: IconNameType; - text: string; - onClick: () => void; -}) { - return ( - - - {text} - - ); -} - -function OrgTable({ Tabs }: { Tabs: React.ReactNode }) { - const { t } = useTranslation(); - const { userInfo, isTeamAdmin } = useUserStore(); - - const { members } = useContextSelector(TeamContext, (v) => v); - - const [parentPath, setParentPath] = useState(''); - const { - data: orgs = [], - loading: isLoadingOrgs, - refresh: refetchOrgs - } = useRequest2(getOrgList, { - manual: false, - refreshDeps: [userInfo?.team?.teamId] - }); - const currentOrgs = useMemo(() => { - if (orgs.length === 0) return []; - // Auto select the first org(root org is team) - if (parentPath === '') { - setParentPath(getOrgChildrenPath(orgs[0])); - return []; - } - - return orgs - .filter((org) => org.path === parentPath) - .map((item) => { - return { - ...item, - // Member + org - count: - item.members.length + orgs.filter((org) => org.path === getOrgChildrenPath(item)).length - }; - }); - }, [orgs, parentPath]); - const currentOrg = useMemo(() => { - const splitPath = parentPath.split('/'); - const currentOrgId = splitPath[splitPath.length - 1]; - if (!currentOrgId) return; - - return orgs.find((org) => org.pathId === currentOrgId); - }, [orgs, parentPath]); - - const paths = useMemo(() => { - const splitPath = parentPath.split('/').filter(Boolean); - return splitPath - .map((id) => { - const org = orgs.find((org) => org.pathId === id)!; - - if (org.path === '') return; - - return { - parentId: getOrgChildrenPath(org), - parentName: org.name - }; - }) - .filter(Boolean) as ParentTreePathItemType[]; - }, [parentPath, orgs]); - - const [editOrg, setEditOrg] = useState(); - const [manageMemberOrg, setManageMemberOrg] = useState(); - const [movingOrg, setMovingOrg] = useState(); - - // Delete org - const { ConfirmModal: ConfirmDeleteOrgModal, openConfirm: openDeleteOrgModal } = useConfirm({ - type: 'delete', - content: t('account_team:confirm_delete_org') - }); - const deleteOrgHandler = (orgId: string) => openDeleteOrgModal(() => deleteOrgReq(orgId))(); - const { runAsync: deleteOrgReq } = useRequest2(deleteOrg, { - onSuccess: () => { - refetchOrgs(); - } - }); - - // Delete member - const { ConfirmModal: ConfirmDeleteMember, openConfirm: openDeleteMemberModal } = useConfirm({ - type: 'delete', - content: t('account_team:confirm_delete_member') - }); - const { runAsync: deleteMemberReq } = useRequest2(deleteOrgMember, { - onSuccess: () => { - refetchOrgs(); - } - }); - - return ( - <> - - {Tabs} - - - - - - - {/* Table */} - - - - - - - - - - {currentOrgs.map((org) => ( - - - - - ))} - {currentOrg?.members.map((member) => { - const memberInfo = members.find((m) => m.tmbId === member.tmbId); - if (!memberInfo) return null; - - return ( - - - - - ); - })} - -
- {t('common:Name')} - - {t('common:common.Action')} -
- setParentPath(getOrgChildrenPath(org))} - > - - {org.count} - - - - {isTeamAdmin && ( - } - menuList={[ - { - children: [ - { - icon: 'edit', - label: t('account_team:edit_info'), - onClick: () => setEditOrg(org) - }, - { - icon: 'common/file/move', - label: t('common:Move'), - onClick: () => setMovingOrg(org) - }, - { - icon: 'delete', - label: t('account_team:delete'), - type: 'danger', - onClick: () => deleteOrgHandler(org._id) - } - ] - } - ]} - /> - )} -
- - - {isTeamAdmin && ( - } - menuList={[ - { - children: [ - { - icon: 'delete', - label: t('account_team:delete'), - type: 'danger', - onClick: () => - openDeleteMemberModal(() => - deleteMemberReq(currentOrg._id, member.tmbId) - )() - } - ] - } - ]} - /> - )} -
-
- {/* Slider */} - - - - - {currentOrg?.name} - - {currentOrg?.path !== '' && ( - setEditOrg(currentOrg)} /> - )} - - {currentOrg?.description || t('common:common.no_intro')} - - - - - {t('common:common.Action')} - - {currentOrg && isTeamAdmin && ( - - { - setEditOrg({ - ...defaultOrgForm, - parentId: currentOrg?._id - }); - }} - /> - setManageMemberOrg(currentOrg)} - /> - {currentOrg?.path !== '' && ( - <> - setMovingOrg(currentOrg)} - /> - deleteOrgHandler(currentOrg._id)} - /> - - )} - - )} - -
- - {!!editOrg && ( - setEditOrg(undefined)} - onSuccess={refetchOrgs} - /> - )} - {!!movingOrg && ( - setMovingOrg(undefined)} - onSuccess={refetchOrgs} - /> - )} - {!!manageMemberOrg && ( - setManageMemberOrg(undefined)} - /> - )} - - - -
- - ); -} - -export default OrgTable; diff --git a/projects/app/src/pages/account/team/index.tsx b/projects/app/src/pages/account/team/index.tsx index f7ec577d6..f8e41ef21 100644 --- a/projects/app/src/pages/account/team/index.tsx +++ b/projects/app/src/pages/account/team/index.tsx @@ -1,6 +1,6 @@ import { serviceSideProps } from '@fastgpt/web/common/system/nextjs'; import AccountContainer from '../components/AccountContainer'; -import { Box, Flex, useDisclosure } from '@chakra-ui/react'; +import { Box, Flex } from '@chakra-ui/react'; import Icon from '@fastgpt/web/components/common/Icon'; import { useTranslation } from 'next-i18next'; import TeamSelector from '../components/TeamSelector'; @@ -11,14 +11,15 @@ import { useRouter } from 'next/router'; import FillRowTabs from '@fastgpt/web/components/common/Tabs/FillRowTabs'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { TeamMemberRoleEnum } from '@fastgpt/global/support/user/team/constant'; -import { TeamContext, TeamModalContextProvider } from './components/context'; +import { TeamContext, TeamModalContextProvider } from '@/pageComponents/account/team/context'; import dynamic from 'next/dynamic'; -import MemberTable from './components/MemberTable'; -const PermissionManage = dynamic(() => import('./components/PermissionManage/index')); -const GroupManage = dynamic(() => import('./components/GroupManage/index')); - -const OrgManage = dynamic(() => import('./components/OrgManage/index')); +const MemberTable = dynamic(() => import('@/pageComponents/account/team/MemberTable')); +const PermissionManage = dynamic( + () => import('@/pageComponents/account/team/PermissionManage/index') +); +const GroupManage = dynamic(() => import('@/pageComponents/account/team/GroupManage/index')); +const OrgManage = dynamic(() => import('@/pageComponents/account/team/OrgManage/index')); export enum TeamTabEnum { member = 'member', @@ -34,7 +35,7 @@ const Team = () => { const { t } = useTranslation(); const { userInfo } = useUserStore(); - const { setEditTeamData, teamSize, isLoading } = useContextSelector(TeamContext, (v) => v); + const { setEditTeamData, isLoading, teamSize } = useContextSelector(TeamContext, (v) => v); const Tabs = useMemo( () => ( @@ -62,72 +63,81 @@ const Team = () => { return ( - {/* header */} - - - - - - {t('account:team')} - - - - - - {userInfo?.team?.role === TeamMemberRoleEnum.owner && ( - - { - if (!userInfo?.team) return; - setEditTeamData({ - id: userInfo.team.teamId, - name: userInfo.team.teamName, - avatar: userInfo.team.avatar - }); - }} - /> + + {/* header */} + + + + + + {t('account:team')} + - )} + + + + {userInfo?.team?.role === TeamMemberRoleEnum.owner && ( + + { + if (!userInfo?.team) return; + setEditTeamData({ + id: userInfo.team.teamId, + name: userInfo.team.teamName, + avatar: userInfo.team.avatar + }); + }} + /> + + )} + + + + {t('account_team:total_team_members', { amount: teamSize })} + + {/* table */} - {t('account_team:total_team_members', { amount: teamSize })} + {teamTab === TeamTabEnum.member && } + {teamTab === TeamTabEnum.org && } + {teamTab === TeamTabEnum.group && } + {teamTab === TeamTabEnum.permission && } - - {/* table */} - - {teamTab === TeamTabEnum.member && } - {teamTab === TeamTabEnum.org && } - {teamTab === TeamTabEnum.group && } - {teamTab === TeamTabEnum.permission && } - ); }; diff --git a/projects/app/src/pages/account/usage/index.tsx b/projects/app/src/pages/account/usage/index.tsx index 649b4448a..8db661730 100644 --- a/projects/app/src/pages/account/usage/index.tsx +++ b/projects/app/src/pages/account/usage/index.tsx @@ -23,15 +23,16 @@ import DateRangePicker, { import { addDays } from 'date-fns'; import dynamic from 'next/dynamic'; import { useTranslation } from 'next-i18next'; -import { useQuery } from '@tanstack/react-query'; import { useUserStore } from '@/web/support/user/useUserStore'; import Avatar from '@fastgpt/web/components/common/Avatar'; import MySelect from '@fastgpt/web/components/common/MySelect'; import { formatNumber } from '@fastgpt/global/common/math/tools'; import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; -import AccountContainer, { TabEnum } from '../components/AccountContainer'; +import AccountContainer from '../components/AccountContainer'; import { serviceSideProps } from '@fastgpt/web/common/system/nextjs'; +import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; +import { getTeamMembers } from '@/web/support/user/team/api'; const UsageDetail = dynamic(() => import('./UsageDetail')); @@ -44,7 +45,7 @@ const UsageTable = () => { }); const [usageSource, setUsageSource] = useState(''); const { isPc } = useSystem(); - const { userInfo, loadAndGetTeamMembers } = useUserStore(); + const { userInfo } = useUserStore(); const [usageDetail, setUsageDetail] = useState(); const sourceList = useMemo( @@ -63,10 +64,7 @@ const UsageTable = () => { ); const [selectTmbId, setSelectTmbId] = useState(userInfo?.team?.tmbId); - const { data: members = [] } = useQuery(['getMembers', userInfo?.team?.teamId], () => { - if (!userInfo?.team?.teamId) return []; - return loadAndGetTeamMembers(); - }); + const { data: members, ScrollData } = useScrollPagination(getTeamMembers, {}); const tmbList = useMemo( () => members.map((item) => ({ @@ -86,14 +84,13 @@ const UsageTable = () => { isLoading, Pagination, getData - } = usePagination({ - api: getUserUsages, + } = usePagination(getUserUsages, { pageSize: isPc ? 20 : 10, params: { dateStart: dateRange.from || new Date(), dateEnd: addDays(dateRange.to || new Date(), 1), - source: usageSource, - teamMemberId: selectTmbId + source: usageSource as UsageSourceEnum, + teamMemberId: selectTmbId ?? '' }, defaultRequest: false }); @@ -120,6 +117,7 @@ const UsageTable = () => { { } }; -const batchUpdateFields = async (batchSize = 2000) => { - // Update in batches - await MongoDatasetData.updateMany( - { initFullText: { $exists: true } }, - { - $unset: { - initFullText: 1, - fullTextToken: 1 - } - } - ); -}; +// const batchUpdateFields = async (batchSize = 2000) => { +// // Find documents that still have these fields +// const documents = await MongoDatasetData.find({ initFullText: { $exists: true } }, '_id') +// .limit(batchSize) +// .lean(); + +// if (documents.length === 0) return; + +// // Update in batches +// await MongoDatasetData.updateMany( +// { _id: { $in: documents.map((doc) => doc._id) } }, +// { +// $unset: { +// initFullText: 1 +// // fullTextToken: 1 +// } +// } +// ); + +// success += documents.length; +// console.log('Delete success:', success); +// await batchUpdateFields(batchSize); +// }; diff --git a/projects/app/src/pages/api/admin/initv4819.ts b/projects/app/src/pages/api/admin/initv4819.ts new file mode 100644 index 000000000..626fbdaa0 --- /dev/null +++ b/projects/app/src/pages/api/admin/initv4819.ts @@ -0,0 +1,55 @@ +import { NextAPI } from '@/service/middleware/entry'; +import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; +import { authCert } from '@fastgpt/service/support/permission/auth/common'; +import { MongoUser } from '@fastgpt/service/support/user/schema'; +import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema'; +import { NextApiRequest, NextApiResponse } from 'next'; + +/* + 简单版迁移:直接升级到最新镜像,会去除 MongoDatasetData 里的索引。直接执行这个脚本。 + 无缝迁移: + 1. 移动 User 表中的 avatar 字段到 TeamMember 表中。 +*/ +async function handler(req: NextApiRequest, res: NextApiResponse) { + await authCert({ req, authRoot: true }); + await moveUserAvatar(); + return { success: true }; +} + +export default NextAPI(handler); + +const moveUserAvatar = async () => { + try { + const users = await MongoUser.find({}, '_id avatar'); + console.log('Total users:', users.length); + let success = 0; + for await (const user of users) { + // @ts-ignore + if (!user.avatar) continue; + try { + await mongoSessionRun(async (session) => { + await MongoTeamMember.updateOne( + { + userId: user._id + }, + { + $set: { + avatar: (user as any).avatar // 删除 avatar 字段, 因为 Type 改了,所以这里不能直接写 user.avatar + } + }, + { session } + ); + // @ts-ignore + user.avatar = undefined; + await user.save({ session }); + }); + success++; + console.log('Move avatar success:', success); + } catch (error) { + console.error(error); + } + } + } catch (error) { + console.error(error); + } +}; diff --git a/projects/app/src/pages/api/common/system/getInitData.ts b/projects/app/src/pages/api/common/system/getInitData.ts index 33cdb4437..85d6413c1 100644 --- a/projects/app/src/pages/api/common/system/getInitData.ts +++ b/projects/app/src/pages/api/common/system/getInitData.ts @@ -8,7 +8,8 @@ async function handler(req: ApiRequestProps<{}, { bufferId?: string }>, res: Nex // If bufferId is the same as the current bufferId, return directly if (bufferId && global.systemInitBufferId && global.systemInitBufferId === bufferId) { return { - bufferId: global.systemInitBufferId + bufferId: global.systemInitBufferId, + systemVersion: global.systemVersion || '0.0.0' }; } diff --git a/projects/app/src/pages/api/core/app/getChatLogs.ts b/projects/app/src/pages/api/core/app/getChatLogs.ts index e625e0262..566c44a2b 100644 --- a/projects/app/src/pages/api/core/app/getChatLogs.ts +++ b/projects/app/src/pages/api/core/app/getChatLogs.ts @@ -1,6 +1,5 @@ import type { NextApiRequest, NextApiResponse } from 'next'; import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; -import type { PagingData } from '@/types'; import { AppLogsListItemType } from '@/types/app'; import { Types } from '@fastgpt/service/common/mongo'; import { addDays } from 'date-fns'; @@ -10,19 +9,22 @@ import { ChatItemCollectionName } from '@fastgpt/service/core/chat/chatItemSchem import { NextAPI } from '@/service/middleware/entry'; import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; import { readFromSecondary } from '@fastgpt/service/common/mongo/utils'; +import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination'; +import { PaginationResponse } from '@fastgpt/web/common/fetch/type'; +import { addSourceMember } from '@fastgpt/service/support/user/utils'; async function handler( req: NextApiRequest, _res: NextApiResponse -): Promise> { +): Promise> { const { - pageNum = 1, - pageSize = 20, appId, dateStart = addDays(new Date(), -7), dateEnd = new Date() } = req.body as GetAppChatLogsParams; + const { pageSize = 20, offset } = parsePaginationRequest(req); + if (!appId) { throw new Error('缺少参数'); } @@ -39,7 +41,7 @@ async function handler( } }; - const [data, total] = await Promise.all([ + const [list, total] = await Promise.all([ MongoChat.aggregate( [ { $match: where }, @@ -51,7 +53,7 @@ async function handler( updateTime: -1 } }, - { $skip: (pageNum - 1) * pageSize }, + { $skip: offset }, { $limit: pageSize }, { $lookup: { @@ -144,10 +146,14 @@ async function handler( MongoChat.countDocuments(where, { ...readFromSecondary }) ]); + const listWithSourceMember = await addSourceMember({ + list: list + }); + + const listWithoutTmbId = list.filter((item) => !item.tmbId); + return { - pageNum, - pageSize, - data, + list: listWithSourceMember.concat(listWithoutTmbId), total }; } diff --git a/projects/app/src/pages/api/core/app/list.ts b/projects/app/src/pages/api/core/app/list.ts index ee2b605e2..da515cd43 100644 --- a/projects/app/src/pages/api/core/app/list.ts +++ b/projects/app/src/pages/api/core/app/list.ts @@ -18,6 +18,7 @@ import { replaceRegChars } from '@fastgpt/global/common/string/tools'; import { concatPer } from '@fastgpt/service/support/permission/controller'; import { getGroupsByTmbId } from '@fastgpt/service/support/permission/memberGroup/controllers'; import { getOrgIdSetWithParentByTmbId } from '@fastgpt/service/support/permission/org/controllers'; +import { addSourceMember } from '@fastgpt/service/support/user/utils'; export type ListAppBody = { parentId?: ParentIdType; @@ -201,19 +202,9 @@ async function handler(req: ApiRequestProps): Promise app.permission.hasReadPer); - return formatApps.map((app) => ({ - _id: app._id, - tmbId: app.tmbId, - avatar: app.avatar, - type: app.type, - name: app.name, - intro: app.intro, - updateTime: app.updateTime, - permission: app.permission, - pluginData: app.pluginData, - inheritPermission: app.inheritPermission ?? true, - private: app.privateApp - })); + return addSourceMember({ + list: formatApps + }); } export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/app/version/list.ts b/projects/app/src/pages/api/core/app/version/list.ts index b0337c33e..c03971d30 100644 --- a/projects/app/src/pages/api/core/app/version/list.ts +++ b/projects/app/src/pages/api/core/app/version/list.ts @@ -6,6 +6,8 @@ import { ApiRequestProps } from '@fastgpt/service/type/next'; import { authApp } from '@fastgpt/service/support/permission/app/auth'; import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; import { VersionListItemType } from '@fastgpt/global/core/app/version'; +import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination'; +import { addSourceMember } from '@fastgpt/service/support/user/utils'; export type versionListBody = PaginationProps<{ appId: string; @@ -15,41 +17,40 @@ export type versionListResponse = PaginationResponse; async function handler( req: ApiRequestProps, - res: NextApiResponse + _res: NextApiResponse ): Promise { - const { offset, pageSize, appId } = req.body; + const { appId } = req.body; + const { offset, pageSize } = parsePaginationRequest(req); await authApp({ appId, req, per: WritePermissionVal, authToken: true }); const [result, total] = await Promise.all([ - MongoAppVersion.find( - { + (async () => { + const versions = await MongoAppVersion.find({ appId - }, - '_id appId versionName time isPublish tmbId' - ) - .sort({ - time: -1 }) - .skip(offset) - .limit(pageSize), + .sort({ + time: -1 + }) + .skip(offset) + .limit(pageSize) + .lean(); + + return addSourceMember({ + list: versions + }).then((list) => + list.map((item) => ({ + ...item, + isPublish: !!item.isPublish + })) + ); + })(), MongoAppVersion.countDocuments({ appId }) ]); - const versionList = result.map((item) => { - return { - _id: item._id, - appId: item.appId, - versionName: item.versionName, - time: item.time, - isPublish: item.isPublish, - tmbId: item.tmbId - }; - }); - return { total, - list: versionList + list: result }; } diff --git a/projects/app/src/pages/api/core/app/version/publish.test.ts b/projects/app/src/pages/api/core/app/version/publish.test.ts index a929fdde1..314af0252 100644 --- a/projects/app/src/pages/api/core/app/version/publish.test.ts +++ b/projects/app/src/pages/api/core/app/version/publish.test.ts @@ -12,7 +12,6 @@ describe('发布应用版本测试', () => { nodes: [], edges: [], chatConfig: {}, - type: AppTypeEnum.simple, isPublish: false, versionName: '1' }; diff --git a/projects/app/src/pages/api/core/chat/chatTest.ts b/projects/app/src/pages/api/core/chat/chatTest.ts index 3c0d64ddd..26e9b044d 100644 --- a/projects/app/src/pages/api/core/chat/chatTest.ts +++ b/projects/app/src/pages/api/core/chat/chatTest.ts @@ -164,6 +164,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { runningAppInfo: { id: appId, + teamId: app.teamId, + tmbId: app.tmbId + }, + runningUserInfo: { teamId, tmbId }, diff --git a/projects/app/src/pages/api/core/chat/getHistories.ts b/projects/app/src/pages/api/core/chat/getHistories.ts index ea38c84b8..2fafd8082 100644 --- a/projects/app/src/pages/api/core/chat/getHistories.ts +++ b/projects/app/src/pages/api/core/chat/getHistories.ts @@ -7,6 +7,7 @@ import { NextAPI } from '@/service/middleware/entry'; import { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type'; import { GetHistoriesProps } from '@/global/core/chat/api'; +import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination'; import { addMonths } from 'date-fns'; export type getHistoriesQuery = {}; @@ -17,9 +18,10 @@ export type getHistoriesResponse = {}; async function handler( req: ApiRequestProps, - res: ApiResponseType + _res: ApiResponseType ): Promise> { - const { appId, shareId, outLinkUid, teamId, teamToken, offset, pageSize, source } = req.body; + const { appId, shareId, outLinkUid, teamId, teamToken, source } = req.body; + const { offset, pageSize } = parsePaginationRequest(req); const match = await (async () => { if (shareId && outLinkUid) { diff --git a/projects/app/src/pages/api/core/chat/getPaginationRecords.ts b/projects/app/src/pages/api/core/chat/getPaginationRecords.ts index c779c11ec..82c11b81f 100644 --- a/projects/app/src/pages/api/core/chat/getPaginationRecords.ts +++ b/projects/app/src/pages/api/core/chat/getPaginationRecords.ts @@ -13,6 +13,7 @@ import { filterPublicNodeResponseData } from '@fastgpt/global/core/chat/utils'; import { GetChatTypeEnum } from '@/global/core/chat/constants'; import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type'; import { ChatItemType } from '@fastgpt/global/core/chat/type'; +import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination'; export type getPaginationRecordsQuery = {}; @@ -22,16 +23,11 @@ export type getPaginationRecordsResponse = PaginationResponse; async function handler( req: ApiRequestProps, - res: ApiResponseType + _res: ApiResponseType ): Promise { - const { - appId, - chatId, - offset, - pageSize = 10, - loadCustomFeedbacks, - type = GetChatTypeEnum.normal - } = req.body; + const { appId, chatId, loadCustomFeedbacks, type = GetChatTypeEnum.normal } = req.body; + + const { offset, pageSize } = parsePaginationRequest(req); if (!appId || !chatId) { return { diff --git a/projects/app/src/pages/api/core/chat/inputGuide/list.ts b/projects/app/src/pages/api/core/chat/inputGuide/list.ts index b6173add0..5660f822e 100644 --- a/projects/app/src/pages/api/core/chat/inputGuide/list.ts +++ b/projects/app/src/pages/api/core/chat/inputGuide/list.ts @@ -6,6 +6,7 @@ import { ApiRequestProps } from '@fastgpt/service/type/next'; import { ChatInputGuideSchemaType } from '@fastgpt/global/core/chat/inputGuide/type'; import { authApp } from '@fastgpt/service/support/permission/app/auth'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; +import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination'; export type ChatInputGuideProps = PaginationProps<{ appId: string; @@ -17,7 +18,8 @@ async function handler( req: ApiRequestProps, res: NextApiResponse ): Promise { - const { appId, pageSize, offset, searchKey } = req.body; + const { appId, searchKey } = req.body; + const { offset, pageSize } = parsePaginationRequest(req); await authApp({ req, appId, authToken: true, per: ReadPermissionVal }); diff --git a/projects/app/src/pages/api/core/dataset/collection/list.ts b/projects/app/src/pages/api/core/dataset/collection/list.ts index 7c7410275..117e10268 100644 --- a/projects/app/src/pages/api/core/dataset/collection/list.ts +++ b/projects/app/src/pages/api/core/dataset/collection/list.ts @@ -2,7 +2,6 @@ import type { NextApiRequest } from 'next'; import { DatasetTrainingCollectionName } from '@fastgpt/service/core/dataset/training/schema'; import { Types } from '@fastgpt/service/common/mongo'; import type { DatasetCollectionsListItemType } from '@/global/core/dataset/type.d'; -import type { GetDatasetCollectionsProps } from '@/global/core/api/datasetReq'; import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema'; import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constants'; import { authDataset } from '@fastgpt/service/support/permission/dataset/auth'; @@ -10,11 +9,10 @@ import { DatasetDataCollectionName } from '@fastgpt/service/core/dataset/data/sc import { startTrainingQueue } from '@/service/core/dataset/training/utils'; import { NextAPI } from '@/service/middleware/entry'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; -import { PagingData } from '@/types'; import { readFromSecondary } from '@fastgpt/service/common/mongo/utils'; import { collectionTagsToTagLabel } from '@fastgpt/service/core/dataset/collection/utils'; -async function handler(req: NextApiRequest): Promise> { +async function handler(req: NextApiRequest) { let { pageNum = 1, pageSize = 10, @@ -24,7 +22,7 @@ async function handler(req: NextApiRequest): Promise> { + let { + datasetId, + parentId = null, + searchText = '', + selectFolder = false, + filterTags = [], + simple = false + } = req.body as GetDatasetCollectionsProps; + let { pageSize, offset } = parsePaginationRequest(req); + pageSize = Math.min(pageSize, 30); + searchText = searchText?.replace(/'/g, ''); + + // auth dataset and get my role + const { teamId, permission } = await authDataset({ + req, + authToken: true, + authApiKey: true, + datasetId, + per: ReadPermissionVal + }); + + const match = { + teamId: new Types.ObjectId(teamId), + datasetId: new Types.ObjectId(datasetId), + parentId: parentId ? new Types.ObjectId(parentId) : null, + ...(selectFolder ? { type: DatasetCollectionTypeEnum.folder } : {}), + ...(searchText + ? { + name: new RegExp(searchText, 'i') + } + : {}), + ...(filterTags.length ? { tags: { $in: filterTags } } : {}) + }; + + const selectField = { + _id: 1, + parentId: 1, + tmbId: 1, + name: 1, + type: 1, + forbid: 1, + createTime: 1, + updateTime: 1, + trainingType: 1, + fileId: 1, + rawLink: 1, + tags: 1, + externalFileId: 1 + }; + + // not count data amount + if (simple) { + const collections = await MongoDatasetCollection.find(match, undefined, { + ...readFromSecondary + }) + .select(selectField) + .sort({ + updateTime: -1 + }) + .lean(); + + return { + list: await Promise.all( + collections.map(async (item) => ({ + ...item, + tags: await collectionTagsToTagLabel({ + datasetId, + tags: item.tags + }), + dataAmount: 0, + trainingAmount: 0, + permission + })) + ), + total: await MongoDatasetCollection.countDocuments(match) + }; + } + + const [collections, total]: [DatasetCollectionsListItemType[], number] = await Promise.all([ + MongoDatasetCollection.aggregate([ + { + $match: match + }, + { + $sort: { updateTime: -1 } + }, + { + $skip: offset + }, + { + $limit: pageSize + }, + // count training data + { + $lookup: { + from: DatasetTrainingCollectionName, + let: { id: '$_id', team_id: match.teamId, dataset_id: match.datasetId }, + pipeline: [ + { + $match: { + $expr: { + $and: [{ $eq: ['$teamId', '$$team_id'] }, { $eq: ['$collectionId', '$$id'] }] + } + } + }, + { $count: 'count' } + ], + as: 'trainingCount' + } + }, + // count collection total data + { + $lookup: { + from: DatasetDataCollectionName, + let: { id: '$_id', team_id: match.teamId, dataset_id: match.datasetId }, + pipeline: [ + { + $match: { + $expr: { + $and: [ + { $eq: ['$teamId', '$$team_id'] }, + { $eq: ['$datasetId', '$$dataset_id'] }, + { $eq: ['$collectionId', '$$id'] } + ] + } + } + }, + { $count: 'count' } + ], + as: 'dataCount' + } + }, + { + $project: { + ...selectField, + dataAmount: { + $ifNull: [{ $arrayElemAt: ['$dataCount.count', 0] }, 0] + }, + trainingAmount: { + $ifNull: [{ $arrayElemAt: ['$trainingCount.count', 0] }, 0] + } + } + } + ]), + MongoDatasetCollection.countDocuments(match, { + ...readFromSecondary + }) + ]); + + const list = await Promise.all( + collections.map(async (item) => ({ + ...item, + tags: await collectionTagsToTagLabel({ + datasetId, + tags: item.tags + }), + permission + })) + ); + + if (list.find((item) => item.trainingAmount > 0)) { + startTrainingQueue(); + } + + // count collections + return { + list, + total + }; +} + +export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/dataset/collection/scrollList.ts b/projects/app/src/pages/api/core/dataset/collection/scrollList.ts index cf30cad31..2b2920975 100644 --- a/projects/app/src/pages/api/core/dataset/collection/scrollList.ts +++ b/projects/app/src/pages/api/core/dataset/collection/scrollList.ts @@ -10,6 +10,7 @@ import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; import { ApiRequestProps } from '@fastgpt/service/type/next'; import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type'; import type { DatasetCollectionsListItemType } from '@/global/core/dataset/type.d'; +import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination'; export type GetScrollCollectionsProps = PaginationProps<{ datasetId: string; @@ -25,8 +26,6 @@ async function handler( ): Promise> { let { datasetId, - pageSize = 10, - offset, parentId = null, searchText = '', selectFolder = false, @@ -36,6 +35,7 @@ async function handler( if (!datasetId) { return Promise.reject(CommonErrEnum.missingParams); } + let { offset, pageSize } = parsePaginationRequest(req); searchText = searchText?.replace(/'/g, ''); pageSize = Math.min(pageSize, 30); diff --git a/projects/app/src/pages/api/core/dataset/data/list.ts b/projects/app/src/pages/api/core/dataset/data/list.ts index 4e6990732..a10f7e3ea 100644 --- a/projects/app/src/pages/api/core/dataset/data/list.ts +++ b/projects/app/src/pages/api/core/dataset/data/list.ts @@ -3,19 +3,21 @@ import { MongoDatasetData } from '@fastgpt/service/core/dataset/data/schema'; import { replaceRegChars } from '@fastgpt/global/common/string/tools'; import { NextAPI } from '@/service/middleware/entry'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; -import { PagingData, RequestPaging } from '@/types'; import { ApiRequestProps } from '@fastgpt/service/type/next'; import { DatasetDataListItemType } from '@/global/core/dataset/type'; +import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination'; +import { PaginationResponse } from '@fastgpt/web/common/fetch/type'; -export type GetDatasetDataListProps = RequestPaging & { +export type GetDatasetDataListProps = { searchText?: string; collectionId: string; }; async function handler( req: ApiRequestProps -): Promise> { - let { pageNum = 1, pageSize = 10, searchText = '', collectionId } = req.body; +): Promise> { + let { searchText = '', collectionId } = req.body; + let { offset, pageSize } = parsePaginationRequest(req); pageSize = Math.min(pageSize, 30); @@ -40,19 +42,17 @@ async function handler( : {}) }; - const [data, total] = await Promise.all([ + const [list, total] = await Promise.all([ MongoDatasetData.find(match, '_id datasetId collectionId q a chunkIndex') .sort({ chunkIndex: 1, updateTime: -1 }) - .skip((pageNum - 1) * pageSize) + .skip(offset) .limit(pageSize) .lean(), MongoDatasetData.countDocuments(match) ]); return { - pageNum, - pageSize, - data, + list, total }; } diff --git a/projects/app/src/pages/api/core/dataset/data/v2/list.ts b/projects/app/src/pages/api/core/dataset/data/v2/list.ts index 113fd70d4..0caa766d9 100644 --- a/projects/app/src/pages/api/core/dataset/data/v2/list.ts +++ b/projects/app/src/pages/api/core/dataset/data/v2/list.ts @@ -6,6 +6,7 @@ import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; import { ApiRequestProps } from '@fastgpt/service/type/next'; import { DatasetDataListItemType } from '@/global/core/dataset/type'; import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type'; +import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination'; export type GetDatasetDataListProps = PaginationProps & { searchText?: string; @@ -16,7 +17,8 @@ export type GetDatasetDataListRes = PaginationResponse; async function handler( req: ApiRequestProps ): Promise { - let { offset, pageSize = 10, searchText = '', collectionId } = req.body; + let { searchText = '', collectionId } = req.body; + let { offset, pageSize } = parsePaginationRequest(req); pageSize = Math.min(pageSize, 30); diff --git a/projects/app/src/pages/api/core/dataset/list.ts b/projects/app/src/pages/api/core/dataset/list.ts index 2a3e76c62..ad9ffca5a 100644 --- a/projects/app/src/pages/api/core/dataset/list.ts +++ b/projects/app/src/pages/api/core/dataset/list.ts @@ -1,8 +1,6 @@ -import type { DatasetListItemType } from '@fastgpt/global/core/dataset/type.d'; import { DatasetTypeEnum } from '@fastgpt/global/core/dataset/constants'; import { MongoDataset } from '@fastgpt/service/core/dataset/schema'; import { authUserPer } from '@fastgpt/service/support/permission/user/auth'; -import { getVectorModel } from '@fastgpt/service/core/ai/model'; import { NextAPI } from '@/service/middleware/entry'; import { DatasetPermission } from '@fastgpt/global/support/permission/dataset/controller'; import { @@ -19,6 +17,8 @@ import { replaceRegChars } from '@fastgpt/global/common/string/tools'; import { getGroupsByTmbId } from '@fastgpt/service/support/permission/memberGroup/controllers'; import { concatPer } from '@fastgpt/service/support/permission/controller'; import { getOrgIdSetWithParentByTmbId } from '@fastgpt/service/support/permission/org/controllers'; +import { addSourceMember } from '@fastgpt/service/support/user/utils'; +import { getVectorModel } from '@fastgpt/service/core/ai/model'; export type GetDatasetListBody = { parentId: ParentIdType; @@ -167,28 +167,24 @@ async function handler(req: ApiRequestProps) { })(); return { - ...dataset, + _id: dataset._id, + avatar: dataset.avatar, + name: dataset.name, + intro: dataset.intro, + type: dataset.type, + vectorModel: getVectorModel(dataset.vectorModel), + inheritPermission: dataset.inheritPermission, + tmbId: dataset.tmbId, + updateTime: dataset.updateTime, permission: Per, privateDataset }; }) .filter((app) => app.permission.hasReadPer); - const data = formatDatasets.map((item) => ({ - _id: item._id, - avatar: item.avatar, - name: item.name, - intro: item.intro, - type: item.type, - permission: item.permission, - vectorModel: getVectorModel(item.vectorModel), - inheritPermission: item.inheritPermission, - tmbId: item.tmbId, - updateTime: item.updateTime, - private: item.privateDataset - })); - - return data; + return addSourceMember({ + list: formatDatasets + }); } export default NextAPI(handler); diff --git a/projects/app/src/pages/api/core/workflow/debug.ts b/projects/app/src/pages/api/core/workflow/debug.ts index dfd7c72c5..87d7ce439 100644 --- a/projects/app/src/pages/api/core/workflow/debug.ts +++ b/projects/app/src/pages/api/core/workflow/debug.ts @@ -45,7 +45,11 @@ async function handler( requestOrigin: req.headers.origin, mode: 'debug', runningAppInfo: { - id: appId, + id: app._id, + teamId: app.teamId, + tmbId: app.tmbId + }, + runningUserInfo: { teamId, tmbId }, diff --git a/projects/app/src/pages/api/support/user/account/update.ts b/projects/app/src/pages/api/support/user/account/update.ts index 34bc06570..192649081 100644 --- a/projects/app/src/pages/api/support/user/account/update.ts +++ b/projects/app/src/pages/api/support/user/account/update.ts @@ -1,17 +1,18 @@ import { MongoUser } from '@fastgpt/service/support/user/schema'; import { authCert } from '@fastgpt/service/support/permission/auth/common'; import { UserUpdateParams } from '@/types/user'; -import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema'; /* update user info */ import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next'; import { NextAPI } from '@/service/middleware/entry'; import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun'; -import { getUserDetail } from '@fastgpt/service/support/user/controller'; import { refreshSourceAvatar } from '@fastgpt/service/common/file/image/controller'; +import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema'; + export type UserAccountUpdateQuery = {}; export type UserAccountUpdateBody = UserUpdateParams; export type UserAccountUpdateResponse = {}; + async function handler( req: ApiRequestProps, _res: ApiResponseType @@ -19,21 +20,33 @@ async function handler( const { avatar, timezone } = req.body; const { tmbId } = await authCert({ req, authToken: true }); - const user = await getUserDetail({ tmbId }); + // const user = await getUserDetail({ tmbId }); // 更新对应的记录 await mongoSessionRun(async (session) => { - await MongoUser.updateOne( - { - _id: user._id - }, - { - ...(avatar && { avatar }), - ...(timezone && { timezone }) - } - ).session(session); - - await refreshSourceAvatar(avatar, user.avatar, session); + const tmb = await MongoTeamMember.findById(tmbId).session(session); + if (timezone) { + await MongoUser.updateOne( + { + _id: tmb?.userId + }, + { + timezone + } + ).session(session); + } + // if avatar, update team member avatar + if (avatar) { + await MongoTeamMember.updateOne( + { + _id: tmbId + }, + { + avatar + } + ).session(session); + await refreshSourceAvatar(avatar, tmb?.avatar, session); + } }); return {}; diff --git a/projects/app/src/pages/api/v1/chat/completions.ts b/projects/app/src/pages/api/v1/chat/completions.ts index 23c245df3..52d483418 100644 --- a/projects/app/src/pages/api/v1/chat/completions.ts +++ b/projects/app/src/pages/api/v1/chat/completions.ts @@ -280,6 +280,10 @@ async function handler(req: NextApiRequest, res: NextApiResponse) { teamId: String(app.teamId), tmbId: String(app.tmbId) }, + runningUserInfo: { + teamId, + tmbId + }, uid: String(outLinkUserId || tmbId), chatId, diff --git a/projects/app/src/pages/app/detail/components/Logs/DetailLogsModal.tsx b/projects/app/src/pages/app/detail/components/Logs/DetailLogsModal.tsx index d034f2124..0098e8cec 100644 --- a/projects/app/src/pages/app/detail/components/Logs/DetailLogsModal.tsx +++ b/projects/app/src/pages/app/detail/components/Logs/DetailLogsModal.tsx @@ -164,8 +164,6 @@ const DetailLogsModal = ({ appId, chatId, onClose }: Props) => { showMarkIcon showVoiceIcon={false} chatType="log" - showRawSource - showNodeStatus /> )}
@@ -187,7 +185,12 @@ const Render = (props: Props) => { }, [appId, chatId]); return ( - + diff --git a/projects/app/src/pages/app/detail/components/Logs/index.tsx b/projects/app/src/pages/app/detail/components/Logs/index.tsx index 263a4c710..b104da20b 100644 --- a/projects/app/src/pages/app/detail/components/Logs/index.tsx +++ b/projects/app/src/pages/app/detail/components/Logs/index.tsx @@ -13,7 +13,7 @@ import { ModalBody, HStack } from '@chakra-ui/react'; -import Avatar from '@fastgpt/web/components/common/Avatar'; +import UserBox from '@fastgpt/web/components/common/UserBox'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { useTranslation } from 'next-i18next'; import { getAppChatLogs } from '@/web/core/app/api'; @@ -30,8 +30,6 @@ import { cardStyles } from '../constants'; import dynamic from 'next/dynamic'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; -import { useUserStore } from '@/web/support/user/useUserStore'; -import { useMount } from 'ahooks'; const DetailLogsModal = dynamic(() => import('./DetailLogsModal')); @@ -40,17 +38,11 @@ const Logs = () => { const { isPc } = useSystem(); const appId = useContextSelector(AppContext, (v) => v.appId); - const { teamMembers, loadAndGetTeamMembers } = useUserStore(); - - useMount(() => { - loadAndGetTeamMembers(); - }); const [dateRange, setDateRange] = useState({ from: addDays(new Date(), -7), to: new Date() }); - const { isOpen: isOpenMarkDesc, onOpen: onOpenMarkDesc, @@ -63,8 +55,7 @@ const Logs = () => { Pagination, getData, pageNum - } = usePagination({ - api: getAppChatLogs, + } = usePagination(getAppChatLogs, { pageSize: 20, params: { appId, @@ -139,15 +130,7 @@ const Logs = () => { {!!item.outLinkUid ? ( item.outLinkUid ) : ( - - v.tmbId === item.tmbId)?.avatar} - w="1.25rem" - /> - - {teamMembers?.find((v) => v.tmbId === item.tmbId)?.memberName} - - + )}
diff --git a/projects/app/src/pages/app/detail/components/PublishHistoriesSlider.tsx b/projects/app/src/pages/app/detail/components/PublishHistoriesSlider.tsx index 50b327d32..e367e1963 100644 --- a/projects/app/src/pages/app/detail/components/PublishHistoriesSlider.tsx +++ b/projects/app/src/pages/app/detail/components/PublishHistoriesSlider.tsx @@ -4,7 +4,7 @@ import { getWorkflowVersionList, updateAppVersion } from '@/web/core/app/api/version'; -import { useVirtualScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; +import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; import CustomRightDrawer from '@fastgpt/web/components/common/MyDrawer/CustomRightDrawer'; import { useTranslation } from 'next-i18next'; import { Box, BoxProps, Button, Flex, Input } from '@chakra-ui/react'; @@ -18,9 +18,7 @@ import Tag from '@fastgpt/web/components/common/Tag'; import MyIcon from '@fastgpt/web/components/common/Icon'; import MyPopover from '@fastgpt/web/components/common/MyPopover'; import MyBox from '@fastgpt/web/components/common/MyBox'; -import { useUserStore } from '@/web/support/user/useUserStore'; import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; -import { useSystemStore } from '@/web/common/system/useSystemStore'; import { useToast } from '@fastgpt/web/hooks/useToast'; import type { AppVersionSchemaType, VersionListItemType } from '@fastgpt/global/core/app/version'; import type { SimpleAppSnapshotType } from './SimpleApp/useSnapshots'; @@ -183,23 +181,18 @@ const TeamCloud = ({ }) => { const { t } = useTranslation(); const { appDetail } = useContextSelector(AppContext, (v) => v); - const { loadAndGetTeamMembers } = useUserStore(); - const { feConfigs } = useSystemStore(); - const { scrollDataList, ScrollList, isLoading, fetchData, setData } = useVirtualScrollPagination( - getWorkflowVersionList, - { - itemHeight: 40, - overscan: 20, - - pageSize: 30, - defaultParams: { - appId: appDetail._id - } - } - ); - const { data: members = [] } = useRequest2(loadAndGetTeamMembers, { - manual: !feConfigs.isPlus + const { + ScrollData, + data: scrollDataList, + setData, + isLoading + } = useScrollPagination(getWorkflowVersionList, { + pageSize: 30, + params: { + appId: appDetail._id + }, + refreshDeps: [appDetail._id] }); const [editIndex, setEditIndex] = useState(undefined); const [hoveredIndex, setHoveredIndex] = useState(undefined); @@ -237,15 +230,13 @@ const TeamCloud = ({ ); return ( - - {scrollDataList.map((data, index) => { - const item = data.data; - const firstPublishedIndex = scrollDataList.findIndex((data) => data.data.isPublish); - const tmb = members.find((member) => member.tmbId === item.tmbId); + + {scrollDataList.map((item, index) => { + const firstPublishedIndex = scrollDataList.findIndex((data) => data.isPublish); return ( - +
} > - {({ onClose }) => ( - + {() => ( + - + - - {tmb?.memberName} - - + + {item.sourceMember.name} + {item.sourceMember.status === 'leave' && ( + {t('common:user_leaved')} + )} + + {formatTime2YMDHMS(item.time)} @@ -349,6 +353,6 @@ const TeamCloud = ({ ); })} - + ); }; diff --git a/projects/app/src/pages/app/detail/components/SimpleApp/ChatTest.tsx b/projects/app/src/pages/app/detail/components/SimpleApp/ChatTest.tsx index ac63c0e26..0ef02db7e 100644 --- a/projects/app/src/pages/app/detail/components/SimpleApp/ChatTest.tsx +++ b/projects/app/src/pages/app/detail/components/SimpleApp/ChatTest.tsx @@ -91,7 +91,12 @@ const Render = ({ appForm }: Props) => { ); return ( - + diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/ChatTest.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/ChatTest.tsx index 87e27c03c..4834f0970 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/ChatTest.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/ChatTest.tsx @@ -158,7 +158,12 @@ const Render = (Props: Props) => { ); return ( - + diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx index 7a07ada84..b48d715da 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/NodeTemplatesModal.tsx @@ -50,7 +50,6 @@ import { useWorkflowUtils } from './hooks/useUtils'; import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants'; import { cloneDeep } from 'lodash'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; -import { useUserStore } from '@/web/support/user/useUserStore'; import { LoopStartNode } from '@fastgpt/global/core/workflow/template/system/loop/loopStart'; import { LoopEndNode } from '@fastgpt/global/core/workflow/template/system/loop/loopEnd'; import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants'; @@ -89,8 +88,6 @@ enum TemplateTypeEnum { const sliderWidth = 460; const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { - const { loadAndGetTeamMembers } = useUserStore(); - const [parentId, setParentId] = useState(''); const [searchKey, setSearchKey] = useState(''); const { feConfigs } = useSystemStore(); @@ -99,10 +96,6 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); const appId = useContextSelector(WorkflowContext, (v) => v.appId); - const { data: members = [] } = useRequest2(loadAndGetTeamMembers, { - manual: !feConfigs.isPlus - }); - const [templateType, setTemplateType] = useState(TemplateTypeEnum.basic); const { data: basicNodes } = useRequest2( @@ -162,19 +155,10 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { searchVal?: string; }) => { if (type === TemplateTypeEnum.teamPlugin) { - const teamApps = await getTeamPlugTemplates({ + return getTeamPlugTemplates({ parentId, searchKey: searchVal }).then((res) => res.filter((app) => app.id !== appId)); - - return teamApps.map((app) => { - const member = members.find((member) => member.tmbId === app.tmbId); - return { - ...app, - author: member?.memberName, - authorAvatar: member?.avatar - }; - }); } if (type === TemplateTypeEnum.systemPlugin) { return getSystemPlugTemplates({ @@ -188,7 +172,7 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => { setParentId(parentId); setTemplateType(type); }, - refreshDeps: [members, searchKey, templateType] + refreshDeps: [searchKey, templateType] } ); @@ -420,7 +404,6 @@ const RenderList = React.memo(function RenderList({ templates, type, onClose, - parentId, setParentId }: RenderListProps) { const { t } = useTranslation(); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeCode.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeCode.tsx index 3a58f3665..3c048ff4d 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeCode.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/NodeCode.tsx @@ -14,22 +14,18 @@ import RenderToolInput from './render/RenderToolInput'; import RenderOutput from './render/RenderOutput'; import CodeEditor from '@fastgpt/web/components/common/Textarea/CodeEditor'; import { Box, Flex } from '@chakra-ui/react'; -import { useI18n } from '@/web/context/I18n'; import { useConfirm } from '@fastgpt/web/hooks/useConfirm'; -import { getLatestNodeTemplate } from '@/web/core/workflow/utils'; -import { CodeNode } from '@fastgpt/global/core/workflow/template/system/sandbox'; +import { JS_TEMPLATE } from '@fastgpt/global/core/workflow/template/system/sandbox/constants'; const NodeCode = ({ data, selected }: NodeProps) => { const { t } = useTranslation(); - const { workflowT } = useI18n(); const { nodeId, inputs, outputs } = data; - const { splitToolInputs, onChangeNode, onResetNode } = useContextSelector( - WorkflowContext, - (ctx) => ctx - ); + + const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs); + const onChangeNode = useContextSelector(WorkflowContext, (ctx) => ctx.onChangeNode); const { ConfirmModal, openConfirm } = useConfirm({ - content: workflowT('code.Reset template confirm') + content: t('workflow:code.Reset template confirm') }); const CustomComponent = useMemo(() => { @@ -38,19 +34,24 @@ const NodeCode = ({ data, selected }: NodeProps) => { return ( - {'Javascript ' + workflowT('Code')} + {'Javascript ' + t('workflow:Code')} { - onResetNode({ - id: nodeId, - node: getLatestNodeTemplate(data, CodeNode) + onChangeNode({ + nodeId, + type: 'updateInput', + key: item.key, + value: { + ...item, + value: JS_TEMPLATE + } }); })} > - {workflowT('code.Reset template')} + {t('workflow:code.Reset template')} ) => { ); } }; - }, [data, nodeId, onChangeNode, onResetNode, openConfirm, workflowT]); + }, [nodeId, onChangeNode, openConfirm, t]); - const Render = useMemo(() => { - const { isTool, commonInputs } = splitToolInputs(inputs, nodeId); + const { isTool, commonInputs } = splitToolInputs(inputs, nodeId); - return ( - - {isTool && ( - <> - - - - - )} - - - - - - - - - - - ); - }, [ConfirmModal, CustomComponent, data, inputs, nodeId, outputs, selected, splitToolInputs, t]); - - return Render; + return ( + + {isTool && ( + <> + + + + + )} + + + + + + + + + + + ); }; export default React.memo(NodeCode); diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/Label.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/Label.tsx index d8fb9eb49..b372f2655 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/Label.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/Label.tsx @@ -1,5 +1,5 @@ import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d'; -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback } from 'react'; import { useTranslation } from 'next-i18next'; import { Box, Flex } from '@chakra-ui/react'; @@ -10,14 +10,14 @@ import { useContextSelector } from 'use-context-selector'; import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel'; -import VariableTip from '@/components/common/Textarea/MyTextarea/VariableTip'; type Props = { nodeId: string; input: FlowNodeInputItemType; + RightComponent?: React.JSX.Element; }; -const InputLabel = ({ nodeId, input }: Props) => { +const InputLabel = ({ nodeId, input, RightComponent }: Props) => { const { t } = useTranslation(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); @@ -68,11 +68,11 @@ const InputLabel = ({ nodeId, input }: Props) => { )} - {/* Variable picker tip */} - {input.renderTypeList[input.selectedTypeIndex ?? 0] === FlowNodeInputTypeEnum.textarea && ( + {/* Right Component */} + {RightComponent && ( <> - - + + {RightComponent} )} diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/index.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/index.tsx index f798ff805..d4c4e9017 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/index.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/index.tsx @@ -8,71 +8,72 @@ import InputLabel from './Label'; import type { RenderInputProps } from './type'; import { useSystemStore } from '@/web/common/system/useSystemStore'; -const RenderList: { - types: FlowNodeInputTypeEnum[]; - Component: React.ComponentType; -}[] = [ - { - types: [FlowNodeInputTypeEnum.reference], +const RenderList: Record< + FlowNodeInputTypeEnum, + | { + Component: React.ComponentType; + LableRightComponent?: React.ComponentType; + } + | undefined +> = { + [FlowNodeInputTypeEnum.reference]: { Component: dynamic(() => import('./templates/Reference')) }, - { - types: [FlowNodeInputTypeEnum.fileSelect], + [FlowNodeInputTypeEnum.fileSelect]: { Component: dynamic(() => import('./templates/Reference')) }, - { - types: [FlowNodeInputTypeEnum.select], + [FlowNodeInputTypeEnum.select]: { Component: dynamic(() => import('./templates/Select')) }, - { - types: [FlowNodeInputTypeEnum.numberInput], + [FlowNodeInputTypeEnum.numberInput]: { Component: dynamic(() => import('./templates/NumberInput')) }, - { - types: [FlowNodeInputTypeEnum.switch], + [FlowNodeInputTypeEnum.switch]: { Component: dynamic(() => import('./templates/Switch')) }, - { - types: [FlowNodeInputTypeEnum.selectApp], + [FlowNodeInputTypeEnum.selectApp]: { Component: dynamic(() => import('./templates/SelectApp')) }, - { - types: [FlowNodeInputTypeEnum.selectLLMModel], + [FlowNodeInputTypeEnum.selectLLMModel]: { Component: dynamic(() => import('./templates/SelectLLMModel')) }, - { - types: [FlowNodeInputTypeEnum.settingLLMModel], + [FlowNodeInputTypeEnum.settingLLMModel]: { Component: dynamic(() => import('./templates/SettingLLMModel')) }, - { - types: [FlowNodeInputTypeEnum.selectDataset], - Component: dynamic(() => import('./templates/SelectDataset')) + [FlowNodeInputTypeEnum.selectDataset]: { + Component: dynamic(() => + import('./templates/SelectDataset').then((mod) => mod.SelectDatasetRender) + ), + LableRightComponent: dynamic(() => + import('./templates/SelectDataset').then((mod) => mod.SwitchAuthTmb) + ) }, - { - types: [FlowNodeInputTypeEnum.selectDatasetParamsModal], + [FlowNodeInputTypeEnum.selectDatasetParamsModal]: { Component: dynamic(() => import('./templates/SelectDatasetParams')) }, - { - types: [FlowNodeInputTypeEnum.addInputParam], + [FlowNodeInputTypeEnum.addInputParam]: { Component: dynamic(() => import('./templates/DynamicInputs/index')) }, - { - types: [FlowNodeInputTypeEnum.JSONEditor], + [FlowNodeInputTypeEnum.JSONEditor]: { Component: dynamic(() => import('./templates/JsonEditor')) }, - { - types: [FlowNodeInputTypeEnum.settingDatasetQuotePrompt], + [FlowNodeInputTypeEnum.settingDatasetQuotePrompt]: { Component: dynamic(() => import('./templates/SettingQuotePrompt')) }, - { - types: [FlowNodeInputTypeEnum.input], + [FlowNodeInputTypeEnum.input]: { Component: dynamic(() => import('./templates/TextInput')) }, - { - types: [FlowNodeInputTypeEnum.textarea], - Component: dynamic(() => import('./templates/Textarea')) - } -]; + [FlowNodeInputTypeEnum.textarea]: { + Component: dynamic(() => import('./templates/Textarea')), + LableRightComponent: dynamic(() => + import('./templates/Textarea').then((mod) => mod.TextareaRightComponent) + ) + }, + + [FlowNodeInputTypeEnum.customVariable]: undefined, + [FlowNodeInputTypeEnum.hidden]: undefined, + [FlowNodeInputTypeEnum.custom]: undefined +}; const hideLabelTypeList = [FlowNodeInputTypeEnum.addInputParam]; @@ -101,7 +102,7 @@ const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props) return true; }); - }, [feConfigs?.isPlus, flowInputList]); + }, [filterProInputs]); return ( <> @@ -110,23 +111,41 @@ const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props) const RenderComponent = (() => { if (renderType === FlowNodeInputTypeEnum.custom && CustomComponent?.[input.key]) { - return <>{CustomComponent?.[input.key]({ ...input })}; + return { + Component: <>{CustomComponent?.[input.key]({ ...input })} + }; } - const Component = RenderList.find((item) => item.types.includes(renderType))?.Component; + const RenderItem = RenderList[renderType]; - if (!Component) return null; - return ; + if (!RenderItem) return null; + + return { + Component: ( + + ), + LableRightComponent: RenderItem.LableRightComponent ? ( + + ) : undefined + }; })(); return ( {!!input.label && !hideLabelTypeList.includes(renderType) && ( - + )} {!!RenderComponent && ( - {RenderComponent} + {RenderComponent.Component} )} diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx index 162208986..b4bdb70e8 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/SelectDataset.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useMemo, useState } from 'react'; import type { RenderInputProps } from '../type'; -import { Box, Button, Flex, Grid, useDisclosure, useTheme } from '@chakra-ui/react'; +import { Box, Button, Flex, Grid, Switch, useDisclosure, useTheme } from '@chakra-ui/react'; import { useDatasetStore } from '@/web/core/dataset/store/dataset'; import { SelectedDatasetType } from '@fastgpt/global/core/workflow/api'; import Avatar from '@fastgpt/web/components/common/Avatar'; @@ -12,12 +12,17 @@ import dynamic from 'next/dynamic'; import MyIcon from '@fastgpt/web/components/common/Icon'; import { useContextSelector } from 'use-context-selector'; import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context'; +import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip'; +import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants'; const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal')); -const SelectDatasetRender = ({ inputs = [], item, nodeId }: RenderInputProps) => { +export const SelectDatasetRender = React.memo(function SelectDatasetRender({ + inputs = [], + item, + nodeId +}: RenderInputProps) { const { t } = useTranslation(); - const theme = useTheme(); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const [data, setData] = useState({ @@ -80,8 +85,9 @@ const SelectDatasetRender = ({ inputs = [], item, nodeId }: RenderInputProps) => key={item._id} alignItems={'center'} h={10} - border={theme.borders.base} - borderColor={'myGray.200'} + boxShadow={'sm'} + bg={'white'} + border={'base'} px={2} borderRadius={'md'} > @@ -128,11 +134,47 @@ const SelectDatasetRender = ({ inputs = [], item, nodeId }: RenderInputProps) => onOpenDatasetSelect, selectedDatasets, selectedDatasetsValue, - t, - theme.borders.base + t ]); return Render; -}; +}); -export default React.memo(SelectDatasetRender); +export const SwitchAuthTmb = React.memo(function SwitchAuthTmb({ + inputs = [], + item, + nodeId +}: RenderInputProps) { + const { t } = useTranslation(); + const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); + + const authTmbIdInput = useMemo( + () => inputs.find((v) => v.key === NodeInputKeyEnum.authTmbId), + [inputs] + ); + + return authTmbIdInput ? ( + + {t('workflow:auth_tmb_id')} + + { + onChangeNode({ + nodeId, + key: NodeInputKeyEnum.authTmbId, + type: 'updateInput', + value: { + ...authTmbIdInput, + value: e.target.checked + } + }); + }} + /> + + ) : null; +}); + +export default SelectDatasetRender; diff --git a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Textarea.tsx b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Textarea.tsx index ef5d355ca..d190e6fb2 100644 --- a/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Textarea.tsx +++ b/projects/app/src/pages/app/detail/components/WorkflowComponents/Flow/nodes/render/RenderInput/templates/Textarea.tsx @@ -9,6 +9,7 @@ import { AppContext } from '@/pages/app/detail/components/context'; import { getEditorVariables } from '../../../../../utils'; import { WorkflowNodeEdgeContext } from '../../../../../context/workflowInitContext'; import { useSystemStore } from '@/web/common/system/useSystemStore'; +import VariableTip from '@/components/common/Textarea/MyTextarea/VariableTip'; const TextareaRender = ({ item, nodeId }: RenderInputProps) => { const { t } = useTranslation(); @@ -84,3 +85,10 @@ const TextareaRender = ({ item, nodeId }: RenderInputProps) => { }; export default React.memo(TextareaRender); + +export const TextareaRightComponent = React.memo(function TextareaRightComponent({ + item, + nodeId +}: RenderInputProps) { + return ; +}); diff --git a/projects/app/src/pages/app/detail/components/useChatTest.tsx b/projects/app/src/pages/app/detail/components/useChatTest.tsx index 7a204c309..22a54dfeb 100644 --- a/projects/app/src/pages/app/detail/components/useChatTest.tsx +++ b/projects/app/src/pages/app/detail/components/useChatTest.tsx @@ -141,8 +141,6 @@ export const useChatTest = ({ chatId={chatId} showMarkIcon chatType="chat" - showRawSource - showNodeStatus onStartChat={startChat} /> ) diff --git a/projects/app/src/pages/app/list/components/List.tsx b/projects/app/src/pages/app/list/components/List.tsx index ff3144164..66af89fbe 100644 --- a/projects/app/src/pages/app/list/components/List.tsx +++ b/projects/app/src/pages/app/list/components/List.tsx @@ -34,8 +34,8 @@ import { postCopyApp } from '@/web/core/app/api/app'; import { formatTimeToChatTime } from '@fastgpt/global/common/string/time'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; import { useChatStore } from '@/web/core/chat/context/useChatStore'; -import { useUserStore } from '@/web/support/user/useUserStore'; import { RequireOnlyOne } from '@fastgpt/global/common/type/utils'; +import UserBox from '@fastgpt/web/components/common/UserBox'; const HttpEditModal = dynamic(() => import('./HttpPluginEditModal')); const ListItem = () => { @@ -44,8 +44,6 @@ const ListItem = () => { const { parentId = null } = router.query; const { isPc } = useSystem(); - const { loadAndGetTeamMembers } = useUserStore(); - const { openConfirm: openMoveConfirm, ConfirmModal: MoveConfirmModal } = useConfirm({ type: 'common', title: t('common:move.confirm'), @@ -115,10 +113,6 @@ const ListItem = () => { successToast: t('app:create_copy_success') }); - const { data: members = [] } = useRequest2(loadAndGetTeamMembers, { - manual: false - }); - const { runAsync: onResumeInheritPermission } = useRequest2( () => { return resumeInheritPer(editPerApp!._id); @@ -145,7 +139,6 @@ const ListItem = () => { alignItems={'stretch'} > {myApps.map((app, index) => { - const owner = members.find((v) => v.tmbId === app.tmbId); return ( { color={'myGray.500'} > - {owner && ( - - - - {owner.memberName} - - - )} - + void; totalRecordsCount: number; }) => { const { t } = useTranslation(); @@ -71,7 +69,7 @@ const ChatHeader = ({ )} {/* control */} - {!isPlugin && } + {!isPlugin && } ); }; diff --git a/projects/app/src/pages/chat/components/ChatHistorySlider.tsx b/projects/app/src/pages/chat/components/ChatHistorySlider.tsx index 8b0259168..96fa84926 100644 --- a/projects/app/src/pages/chat/components/ChatHistorySlider.tsx +++ b/projects/app/src/pages/chat/components/ChatHistorySlider.tsx @@ -28,7 +28,6 @@ type HistoryItemType = { const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) => { const theme = useTheme(); const router = useRouter(); - const isUserChatPage = router.pathname === '/chat'; const { t } = useTranslation(); @@ -46,6 +45,7 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) = const appName = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.name); const appAvatar = useContextSelector(ChatItemContext, (v) => v.chatBoxData?.app.avatar); + const showRouteToAppDetail = useContextSelector(ChatItemContext, (v) => v.showRouteToAppDetail); const concatHistory = useMemo(() => { const formatHistories: HistoryItemType[] = histories.map((item) => { @@ -77,8 +77,8 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) = }); const canRouteToDetail = useMemo( - () => appId && userInfo?.team.permission.hasWritePer, - [appId, userInfo?.team.permission.hasWritePer] + () => appId && userInfo?.team.permission.hasWritePer && showRouteToAppDetail, + [appId, userInfo?.team.permission.hasWritePer, showRouteToAppDetail] ); return ( @@ -287,7 +287,7 @@ const ChatHistorySlider = ({ confirmClearText }: { confirmClearText: string }) = {/* exec */} - {!isPc && isUserChatPage && ( + {!isPc && !!canRouteToDetail && ( import('@/components/common/folder/SelectOneResource')); @@ -22,6 +24,8 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI const router = useRouter(); const isTeamChat = router.pathname === '/chat/team'; + const showRouteToAppDetail = useContextSelector(ChatItemContext, (v) => v.showRouteToAppDetail); + const getAppList = useCallback(async ({ parentId }: GetResourceFolderListProps) => { return getMyApps({ parentId, @@ -50,34 +54,36 @@ const SliderApps = ({ apps, activeAppId }: { apps: AppListItemType[]; activeAppI return ( - - {!isTeamChat && ( - router.push('/app/list')} - > - } - bg={'white'} - boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'} - size={'smSquare'} - borderRadius={'50%'} - aria-label={''} - /> - {t('common:core.chat.Exit Chat')} - - )} - + {showRouteToAppDetail && ( + <> + + router.push('/app/list')} + > + } + bg={'white'} + boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'} + size={'smSquare'} + borderRadius={'50%'} + aria-label={''} + /> + {t('common:core.chat.Exit Chat')} + + + + + )} {!isTeamChat && ( <> - void; -}) => { +const ToolMenu = ({ history }: { history: ChatItemType[] }) => { + const router = useRouter(); const { t } = useTranslation(); const { onExportChat } = useChatBox(); const onChangeChatId = useContextSelector(ChatContext, (v) => v.onChangeChatId); + const chatData = useContextSelector(ChatItemContext, (v) => v.chatBoxData); + const showRouteToAppDetail = useContextSelector(ChatItemContext, (v) => v.showRouteToAppDetail); - return history.length > 0 ? ( + return ( router.push(`/app/detail?appId=${chatData.appId}`) } ] } @@ -76,8 +75,6 @@ const ToolMenu = ({ : []) ]} /> - ) : ( - ); }; diff --git a/projects/app/src/pages/chat/index.tsx b/projects/app/src/pages/chat/index.tsx index e80019041..e574d0ff0 100644 --- a/projects/app/src/pages/chat/index.tsx +++ b/projects/app/src/pages/chat/index.tsx @@ -44,6 +44,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => { const theme = useTheme(); const { t } = useTranslation(); const { isPc } = useSystem(); + const { userInfo } = useUserStore(); const { setLastChatAppId, chatId, appId, outLinkAuthData } = useChatStore(); @@ -186,7 +187,6 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => { apps={myApps} history={chatRecords} showHistory - onRouteToAppDetail={() => router.push(`/app/detail?appId=${appId}`)} /> {/* chat box */} @@ -208,8 +208,6 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => { feedbackType={'user'} onStartChat={onStartChat} chatType={'chat'} - showRawSource - showNodeStatus isReady={!loading} /> )} @@ -221,8 +219,8 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => { ); }; -const Render = (props: { appId: string }) => { - const { appId } = props; +const Render = (props: { appId: string; isStandalone?: string }) => { + const { appId, isStandalone } = props; const { t } = useTranslation(); const { toast } = useToast(); const router = useRouter(); @@ -276,7 +274,12 @@ const Render = (props: { appId: string }) => { return source === ChatSourceEnum.online ? ( - + @@ -289,6 +292,7 @@ export async function getServerSideProps(context: any) { return { props: { appId: context?.query?.appId || '', + isStandalone: context?.query?.isStandalone || '', ...(await serviceSideProps(context, ['file', 'app', 'chat', 'workflow'])) } }; diff --git a/projects/app/src/pages/chat/share.tsx b/projects/app/src/pages/chat/share.tsx index 74f898be4..2dd85c879 100644 --- a/projects/app/src/pages/chat/share.tsx +++ b/projects/app/src/pages/chat/share.tsx @@ -55,7 +55,6 @@ type Props = { const OutLink = (props: Props) => { const { t } = useTranslation(); const router = useRouter(); - const { showRawSource, showNodeStatus } = props; const { shareId = '', showHistory = '1', @@ -287,8 +286,6 @@ const OutLink = (props: Props) => { feedbackType={'user'} onStartChat={startChat} chatType="share" - showRawSource={showRawSource} - showNodeStatus={showNodeStatus} /> )} @@ -340,7 +337,12 @@ const Render = (props: Props) => { return source === ChatSourceEnum.share ? ( - + diff --git a/projects/app/src/pages/chat/team.tsx b/projects/app/src/pages/chat/team.tsx index 5bb080de1..db0c5af31 100644 --- a/projects/app/src/pages/chat/team.tsx +++ b/projects/app/src/pages/chat/team.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import NextHead from '@/components/common/NextHead'; import { getTeamChatInfo } from '@/web/core/chat/api'; import { useRouter } from 'next/router'; @@ -20,8 +20,7 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest'; import ChatContextProvider, { ChatContext } from '@/web/core/chat/context/chatContext'; import { AppListItemType } from '@fastgpt/global/core/app/type'; import { useContextSelector } from 'use-context-selector'; -import { InitChatResponse } from '@/global/core/chat/api'; -import { defaultChatData, GetChatTypeEnum } from '@/global/core/chat/constants'; +import { GetChatTypeEnum } from '@/global/core/chat/constants'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { getNanoid } from '@fastgpt/global/common/string/tools'; @@ -226,8 +225,6 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => { feedbackType={'user'} onStartChat={startChat} chatType="team" - showRawSource - showNodeStatus /> )}
@@ -299,7 +296,12 @@ const Render = (props: Props) => { return source === ChatSourceEnum.team ? ( - + diff --git a/projects/app/src/pages/dataset/detail/components/CollectionCard/Context.tsx b/projects/app/src/pages/dataset/detail/components/CollectionCard/Context.tsx index 3ac4ea160..f43bf12bb 100644 --- a/projects/app/src/pages/dataset/detail/components/CollectionCard/Context.tsx +++ b/projects/app/src/pages/dataset/detail/components/CollectionCard/Context.tsx @@ -111,8 +111,7 @@ const CollectionPageContextProvider = ({ children }: { children: ReactNode }) => isLoading: isGetting, pageNum, pageSize - } = usePagination({ - api: getDatasetCollections, + } = usePagination(getDatasetCollections, { pageSize: 20, params: { datasetId, diff --git a/projects/app/src/pages/dataset/detail/components/Import/diffSource/APIDataset.tsx b/projects/app/src/pages/dataset/detail/components/Import/diffSource/APIDataset.tsx index e261a959a..e0cdff95c 100644 --- a/projects/app/src/pages/dataset/detail/components/Import/diffSource/APIDataset.tsx +++ b/projects/app/src/pages/dataset/detail/components/Import/diffSource/APIDataset.tsx @@ -49,7 +49,6 @@ const CustomAPIFileInput = () => { parentId: '', parentName: '' }); - const [parentUuid, setParentUuid] = useState(''); const [paths, setPaths] = useState([]); const [searchKey, setSearchKey] = useState(''); @@ -128,7 +127,7 @@ const CustomAPIFileInput = () => { const handleItemClick = useCallback( (item: APIFileItem) => { - if (item.type === 'folder') { + if (item.hasChild) { setPaths((state) => [...state, { parentId: item.id, parentName: item.name }]); return setParent({ parentId: item.id, @@ -251,6 +250,7 @@ const CustomAPIFileInput = () => { {item.name} + {item.hasChild && } ); })} diff --git a/projects/app/src/pages/dataset/list/component/List.tsx b/projects/app/src/pages/dataset/list/component/List.tsx index 709990a99..db711e176 100644 --- a/projects/app/src/pages/dataset/list/component/List.tsx +++ b/projects/app/src/pages/dataset/list/component/List.tsx @@ -28,10 +28,10 @@ import EmptyTip from '@fastgpt/web/components/common/EmptyTip'; import { useFolderDrag } from '@/components/common/folder/useFolderDrag'; import MyBox from '@fastgpt/web/components/common/MyBox'; import { useTranslation } from 'next-i18next'; -import { useUserStore } from '@/web/support/user/useUserStore'; import { useSystem } from '@fastgpt/web/hooks/useSystem'; import SideTag from './SideTag'; import { getModelProvider } from '@fastgpt/global/core/ai/provider'; +import UserBox from '@fastgpt/web/components/common/UserBox'; const EditResourceModal = dynamic(() => import('@/components/common/Modal/EditResourceModal')); @@ -39,7 +39,6 @@ function List() { const { setLoading } = useSystemStore(); const { isPc } = useSystem(); const { t } = useTranslation(); - const { loadAndGetTeamMembers } = useUserStore(); const { loadMyDatasets, setMoveDatasetId, @@ -81,10 +80,6 @@ function List() { } }); - const { data: members = [] } = useRequest2(loadAndGetTeamMembers, { - manual: false - }); - const editPerDataset = useMemo( () => (editPerDatasetIndex !== undefined ? myDatasets[editPerDatasetIndex] : undefined), [editPerDatasetIndex, myDatasets] @@ -156,7 +151,6 @@ function List() { alignItems={'stretch'} > {formatDatasets.map((dataset, index) => { - const owner = members.find((v) => v.tmbId === dataset.tmbId); const vectorModelAvatar = getModelProvider(dataset.vectorModel.provider)?.avatar; return ( @@ -265,14 +259,12 @@ function List() { color={'myGray.500'} > - {owner && ( - - - - {owner.memberName} - - - )} + { teamId: String(app.teamId), tmbId: String(app.tmbId) }, + runningUserInfo: { + teamId: String(app.teamId), + tmbId: String(app.tmbId) + }, uid: String(app.tmbId), runtimeNodes: storeNodes2RuntimeNodes(nodes, getWorkflowEntryNodeIds(nodes)), runtimeEdges: initWorkflowEdgeStatus(edges), diff --git a/projects/app/src/types/app.d.ts b/projects/app/src/types/app.d.ts index fffc4e194..cbc9dafee 100644 --- a/projects/app/src/types/app.d.ts +++ b/projects/app/src/types/app.d.ts @@ -13,6 +13,8 @@ import type { FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/no import type { ChatSchema } from '@fastgpt/global/core/chat/type'; import type { AppSchema } from '@fastgpt/global/core/app/type'; import { ChatModelType } from '@/constants/model'; +import { TeamMemberStatusEnum } from '@fastgpt/global/support/user/team/constant'; +import { SourceMember } from '@fastgpt/global/support/user/type'; export interface ShareAppItem { _id: string; @@ -45,4 +47,5 @@ export type AppLogsListItemType = { markCount: number; outLinkUid?: string; tmbId: string; + sourceMember: SourceMember; }; diff --git a/projects/app/src/types/index.d.ts b/projects/app/src/types/index.d.ts index 228ee1df8..84dfaf634 100644 --- a/projects/app/src/types/index.d.ts +++ b/projects/app/src/types/index.d.ts @@ -10,15 +10,6 @@ import { import { TrackEventName } from '@/web/common/system/constants'; import { SubPlanType } from '@fastgpt/global/support/wallet/sub/type'; -export type PagingData = { - pageNum: number; - pageSize: number; - data: T[]; - total?: number; -}; - -export type RequestPaging = { pageNum: number; pageSize: number; [key]: any }; - declare global { var qaQueueLen: number; var vectorQueueLen: number; diff --git a/projects/app/src/web/core/app/api.ts b/projects/app/src/web/core/app/api.ts index 26675705b..d1908a48e 100644 --- a/projects/app/src/web/core/app/api.ts +++ b/projects/app/src/web/core/app/api.ts @@ -5,7 +5,7 @@ import { AppUpdateParams, AppChangeOwnerBody } from '@/global/core/app/api'; import type { CreateAppBody } from '@/pages/api/core/app/create'; import type { ListAppBody } from '@/pages/api/core/app/list'; import { AppLogsListItemType } from '@/types/app'; -import { PagingData } from '@/types'; +import { PaginationResponse } from '@fastgpt/web/common/fetch/type'; /** * 获取应用列表 @@ -39,7 +39,7 @@ export const putAppById = (id: string, data: AppUpdateParams) => // =================== chat logs export const getAppChatLogs = (data: GetAppChatLogsParams) => - POST>(`/core/app/getChatLogs`, data); + POST>(`/core/app/getChatLogs`, data); export const resumeInheritPer = (appId: string) => GET(`/core/app/resumeInheritPermission`, { appId }); diff --git a/projects/app/src/web/core/app/api/plugin.ts b/projects/app/src/web/core/app/api/plugin.ts index ed13b625e..a2353e666 100644 --- a/projects/app/src/web/core/app/api/plugin.ts +++ b/projects/app/src/web/core/app/api/plugin.ts @@ -35,7 +35,8 @@ export const getTeamPlugTemplates = (data?: ListAppBody) => intro: app.intro, showStatus: false, version: app.pluginData?.nodeVersion || defaultNodeVersion, - isTool: true + isTool: true, + sourceMember: app.sourceMember })) ); diff --git a/projects/app/src/web/core/app/api/version.ts b/projects/app/src/web/core/app/api/version.ts index 967d55bfe..daa0adb68 100644 --- a/projects/app/src/web/core/app/api/version.ts +++ b/projects/app/src/web/core/app/api/version.ts @@ -1,5 +1,5 @@ import { PostPublishAppProps } from '@/global/core/app/api'; -import { GET, POST, DELETE, PUT } from '@/web/common/api/request'; +import { GET, POST } from '@/web/common/api/request'; import type { AppVersionSchemaType } from '@fastgpt/global/core/app/version'; import { PaginationProps } from '@fastgpt/web/common/fetch/type'; import type { diff --git a/projects/app/src/web/core/chat/context/chatItemContext.tsx b/projects/app/src/web/core/chat/context/chatItemContext.tsx index 991e2e90f..633fae4d2 100644 --- a/projects/app/src/web/core/chat/context/chatItemContext.tsx +++ b/projects/app/src/web/core/chat/context/chatItemContext.tsx @@ -9,6 +9,12 @@ import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { AppChatConfigType, VariableItemType } from '@fastgpt/global/core/app/type'; import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io'; +type ContextProps = { + showRouteToAppDetail: boolean; + showRouteToDatasetDetail: boolean; + isShowReadRawSource: boolean; + showNodeStatus: boolean; +}; type ChatBoxDataType = { appId: string; title?: string; @@ -37,7 +43,7 @@ type ChatItemContextType = { chatBoxData: ChatBoxDataType; setChatBoxData: React.Dispatch>; isPlugin: boolean; -}; +} & ContextProps; export const ChatItemContext = createContext({ ChatBoxRef: null, @@ -61,7 +67,15 @@ export const ChatItemContext = createContext({ /* Chat 对象的上下文 */ -const ChatItemContextProvider = ({ children }: { children: ReactNode }) => { +const ChatItemContextProvider = ({ + children, + showRouteToAppDetail, + showRouteToDatasetDetail, + isShowReadRawSource, + showNodeStatus +}: { + children: ReactNode; +} & ContextProps) => { const ChatBoxRef = useRef(null); const variablesForm = useForm(); @@ -113,16 +127,23 @@ const ChatItemContextProvider = ({ children }: { children: ReactNode }) => { pluginRunTab, setPluginRunTab, resetVariables, - clearChatRecords + clearChatRecords, + showRouteToAppDetail, + showRouteToDatasetDetail, + isShowReadRawSource, + showNodeStatus }; }, [ chatBoxData, - setChatBoxData, - clearChatRecords, isPlugin, + variablesForm, pluginRunTab, resetVariables, - variablesForm + clearChatRecords, + showRouteToAppDetail, + showRouteToDatasetDetail, + isShowReadRawSource, + showNodeStatus ]); return {children}; diff --git a/projects/app/src/web/core/chat/context/chatRecordContext.tsx b/projects/app/src/web/core/chat/context/chatRecordContext.tsx index 16adcd193..a01c1712d 100644 --- a/projects/app/src/web/core/chat/context/chatRecordContext.tsx +++ b/projects/app/src/web/core/chat/context/chatRecordContext.tsx @@ -2,7 +2,7 @@ import { getPaginationRecordsBody } from '@/pages/api/core/chat/getPaginationRec import { ChatSiteItemType } from '@fastgpt/global/core/chat/type'; import { PaginationResponse } from '@fastgpt/web/common/fetch/type'; import { useScrollPagination } from '@fastgpt/web/hooks/useScrollPagination'; -import React, { ReactNode, useEffect, useMemo, useState } from 'react'; +import React, { ReactNode, useMemo, useState } from 'react'; import { createContext, useContextSelector } from 'use-context-selector'; import { ChatItemContext } from './chatItemContext'; import { getChatRecords } from '../api'; @@ -68,7 +68,7 @@ const ChatRecordContextProvider = ({ const res = await getChatRecords(data); // First load scroll to bottom - if (data.offset === 0) { + if (Number(data.offset) === 0) { function scrollToBottom() { requestAnimationFrame( ChatBoxRef?.current ? () => ChatBoxRef?.current?.scrollToBottom?.() : scrollToBottom diff --git a/projects/app/src/web/core/dataset/api.ts b/projects/app/src/web/core/dataset/api.ts index 5a17b7fce..ac40e1a02 100644 --- a/projects/app/src/web/core/dataset/api.ts +++ b/projects/app/src/web/core/dataset/api.ts @@ -37,7 +37,6 @@ import type { DatasetCollectionItemType } from '@fastgpt/global/core/dataset/typ import { DatasetCollectionSyncResultEnum } from '@fastgpt/global/core/dataset/constants'; import type { DatasetDataItemType } from '@fastgpt/global/core/dataset/type'; import type { DatasetCollectionsListItemType } from '@/global/core/dataset/type.d'; -import { PagingData } from '@/types'; import type { getDatasetTrainingQueueResponse } from '@/pages/api/core/dataset/training/getDatasetTrainingQueue'; import type { rebuildEmbeddingBody } from '@/pages/api/core/dataset/training/rebuildEmbedding'; import type { @@ -66,8 +65,6 @@ import type { listExistIdQuery, listExistIdResponse } from '@/pages/api/core/dataset/apiDataset/listExistId'; -import { FeishuServer, YuqueServer } from '@fastgpt/global/core/dataset/apiDataset'; -import { RequireOnlyOne } from '@fastgpt/global/common/type/utils'; /* ======================== dataset ======================= */ export const getDatasets = (data: GetDatasetListBody) => @@ -110,7 +107,7 @@ export const postSearchText = (data: SearchTestProps) => /* ============================= collections ==================================== */ export const getDatasetCollections = (data: GetDatasetCollectionsProps) => - POST>(`/core/dataset/collection/list`, data); + POST>(`/core/dataset/collection/listV2`, data); export const getDatasetCollectionPathById = (parentId: string) => GET(`/core/dataset/collection/paths`, { parentId }); export const getDatasetCollectionById = (id: string) => diff --git a/projects/app/src/web/core/dataset/components/SelectCollections.tsx b/projects/app/src/web/core/dataset/components/SelectCollections.tsx index 7b09e0e7a..5ca8b139e 100644 --- a/projects/app/src/web/core/dataset/components/SelectCollections.tsx +++ b/projects/app/src/web/core/dataset/components/SelectCollections.tsx @@ -1,7 +1,7 @@ import MyIcon from '@fastgpt/web/components/common/Icon'; import MyModal from '@fastgpt/web/components/common/MyModal'; import ParentPaths from '@/components/common/ParentPaths'; -import { useRequest } from '@fastgpt/web/hooks/useRequest'; +import { useRequest, useRequest2 } from '@fastgpt/web/hooks/useRequest'; import { getDatasetCollectionPathById, getDatasetCollections } from '@/web/core/dataset/api'; import { Box, Flex, ModalFooter, Button, useTheme, Grid, Card, ModalBody } from '@chakra-ui/react'; import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constants'; @@ -48,20 +48,24 @@ const SelectCollections = ({ useQuery(['loadDatasetDetail', datasetId], () => loadDatasetDetail(datasetId)); - const { data, isLoading } = useQuery(['getDatasetCollections', parentId], () => - getDatasetCollections({ - datasetId, - parentId, - selectFolder: type === 'folder', - simple: true, - pageNum: 1, - pageSize: 50 - }) + const { data, loading: isLoading } = useRequest2( + () => + getDatasetCollections({ + datasetId, + parentId, + selectFolder: type === 'folder', + simple: true, + pageNum: 1, + pageSize: 50 + }), + { + manual: false, + refreshDeps: [datasetId, parentId, type] + } ); - const formatCollections = useMemo( () => - data?.data.map((collection) => { + data?.list.map((collection) => { const icon = getCollectionIcon(collection.type, collection.name); return { @@ -111,7 +115,7 @@ const SelectCollections = ({ title={ ({ + paths={paths.map((path) => ({ parentId: path.parentId, parentName: path.parentName }))} diff --git a/projects/app/src/web/core/workflow/utils.ts b/projects/app/src/web/core/workflow/utils.ts index 1d5c6f998..209b284f8 100644 --- a/projects/app/src/web/core/workflow/utils.ts +++ b/projects/app/src/web/core/workflow/utils.ts @@ -118,7 +118,7 @@ export const storeNode2FlowNode = ({ toolDescription: t(templateInput.toolDescription ?? (storeInput.toolDescription as any)), selectedTypeIndex: storeInput.selectedTypeIndex ?? templateInput.selectedTypeIndex, - value: storeInput.value ?? templateInput.value, + value: storeInput.value, valueType: storeInput.valueType ?? templateInput.valueType, label: storeInput.label ?? templateInput.label }; diff --git a/projects/app/src/web/support/activity/promotion/api.ts b/projects/app/src/web/support/activity/promotion/api.ts index b44cf67cb..abccbee88 100644 --- a/projects/app/src/web/support/activity/promotion/api.ts +++ b/projects/app/src/web/support/activity/promotion/api.ts @@ -1,6 +1,6 @@ -import { GET, POST, PUT } from '@/web/common/api/request'; +import { GET, POST } from '@/web/common/api/request'; import type { PromotionRecordType } from '@/global/support/api/userRes.d'; -import { PagingData, type RequestPaging } from '@/types'; +import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type'; /* get promotion init data */ export const getPromotionInitData = () => @@ -10,5 +10,8 @@ export const getPromotionInitData = () => }>('/proApi/support/activity/promotion/getPromotionData'); /* promotion records */ -export const getPromotionRecords = (data: RequestPaging) => - POST>(`/proApi/support/activity/promotion/getPromotions`, data); +export const getPromotionRecords = (data: PaginationProps) => + POST>( + `/proApi/support/activity/promotion/getPromotions`, + data + ); diff --git a/projects/app/src/web/support/user/api.ts b/projects/app/src/web/support/user/api.ts index 6c6e17022..b1d076d37 100644 --- a/projects/app/src/web/support/user/api.ts +++ b/projects/app/src/web/support/user/api.ts @@ -90,3 +90,5 @@ export const getCaptchaPic = (username: string) => GET<{ captchaImage: string; }>('/proApi/support/user/account/captcha/getImgCaptcha', { username }); + +export const postSyncMembers = () => POST('/proApi/support/user/team/org/sync'); diff --git a/projects/app/src/web/support/user/inform/api.ts b/projects/app/src/web/support/user/inform/api.ts index fb5d4434d..206ad2ab1 100644 --- a/projects/app/src/web/support/user/inform/api.ts +++ b/projects/app/src/web/support/user/inform/api.ts @@ -1,10 +1,10 @@ -import { GET, POST, PUT } from '@/web/common/api/request'; -import type { PagingData, RequestPaging } from '@/types'; +import { GET, POST } from '@/web/common/api/request'; import type { UserInformSchema } from '@fastgpt/global/support/user/inform/type'; import { SystemMsgModalValueType } from '@fastgpt/service/support/user/inform/type'; +import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type'; -export const getInforms = (data: RequestPaging) => - POST>(`/proApi/support/user/inform/list`, data); +export const getInforms = (data: PaginationProps) => + POST>(`/proApi/support/user/inform/list`, data); export const getUnreadCount = () => GET<{ diff --git a/projects/app/src/web/support/user/team/api.ts b/projects/app/src/web/support/user/team/api.ts index 34e5759f9..af49e4022 100644 --- a/projects/app/src/web/support/user/team/api.ts +++ b/projects/app/src/web/support/user/team/api.ts @@ -19,6 +19,7 @@ import { } from '@fastgpt/global/support/user/team/type.d'; import { FeTeamPlanStatusType, TeamSubSchema } from '@fastgpt/global/support/wallet/sub/type'; import { TeamInvoiceHeaderType } from '@fastgpt/global/support/user/team/type'; +import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type'; /* --------------- team ---------------- */ export const getTeamList = (status: `${TeamMemberSchema['status']}`) => @@ -30,8 +31,8 @@ export const putSwitchTeam = (teamId: string) => PUT(`/proApi/support/user/team/switch`, { teamId }); /* --------------- team member ---------------- */ -export const getTeamMembers = () => - GET(`/proApi/support/user/team/member/list`); +export const getTeamMembers = (props: PaginationProps) => + GET>(`/proApi/support/user/team/member/list`, props); export const postInviteTeamMember = (data: InviteMemberProps) => POST(`/proApi/support/user/team/member/invite`, data); export const putUpdateMemberName = (name: string) => diff --git a/projects/app/src/web/support/user/useUserStore.ts b/projects/app/src/web/support/user/useUserStore.ts index 040e17556..6bb30d135 100644 --- a/projects/app/src/web/support/user/useUserStore.ts +++ b/projects/app/src/web/support/user/useUserStore.ts @@ -30,9 +30,6 @@ type State = { teamPlanStatus: FeTeamPlanStatusType | null; initTeamPlanStatus: () => Promise; - teamMembers: TeamMemberItemType[]; - loadAndGetTeamMembers: (init?: boolean) => Promise; - teamMemberGroups: MemberGroupListType; myGroups: MemberGroupListType; loadAndGetGroups: (init?: boolean) => Promise; @@ -102,7 +99,7 @@ export const useUserStore = create()( }, // team teamPlanStatus: null, - initTeamPlanStatus() { + async initTeamPlanStatus() { return getTeamPlanStatus().then((res) => { set((state) => { state.teamPlanStatus = res; @@ -110,21 +107,6 @@ export const useUserStore = create()( return res; }); }, - teamMembers: [], - loadAndGetTeamMembers: async (init = false) => { - if (!useSystemStore.getState()?.feConfigs?.isPlus) return []; - - const randomRefresh = Math.random() > 0.7; - if (!randomRefresh && !init && get().teamMembers?.length) - return Promise.resolve(get().teamMembers); - - const res = await getTeamMembers(); - set((state) => { - state.teamMembers = res; - }); - - return res; - }, teamMemberGroups: [], teamOrgs: [], myGroups: [], diff --git a/projects/app/src/web/support/wallet/bill/api.ts b/projects/app/src/web/support/wallet/bill/api.ts index a04de528c..3058340b3 100644 --- a/projects/app/src/web/support/wallet/bill/api.ts +++ b/projects/app/src/web/support/wallet/bill/api.ts @@ -1,14 +1,14 @@ -import { PagingData, RequestPaging } from '@/types'; import { GET, POST } from '@/web/common/api/request'; import { CreateBillProps, CreateBillResponse } from '@fastgpt/global/support/wallet/bill/api'; import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants'; import type { BillSchemaType } from '@fastgpt/global/support/wallet/bill/type.d'; +import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type'; export const getBills = ( - data: RequestPaging & { + data: PaginationProps<{ type?: BillTypeEnum; - } -) => POST>(`/proApi/support/wallet/bill/list`, data); + }> +) => POST>(`/proApi/support/wallet/bill/list`, data); export const getWxPayQRCode = (data: CreateBillProps) => POST(`/proApi/support/wallet/bill/create`, data); diff --git a/projects/app/src/web/support/wallet/bill/invoice/api.ts b/projects/app/src/web/support/wallet/bill/invoice/api.ts index 7ec7772e8..c5523e8d8 100644 --- a/projects/app/src/web/support/wallet/bill/invoice/api.ts +++ b/projects/app/src/web/support/wallet/bill/invoice/api.ts @@ -1,8 +1,8 @@ -import { PagingData, RequestPaging } from '@/types'; import { GET, POST } from '@/web/common/api/request'; import { BillTypeEnum } from '@fastgpt/global/support/wallet/bill/constants'; import { InvoiceType } from '@fastgpt/global/support/wallet/bill/type'; import { InvoiceSchemaType } from '@fastgpt/global/support/wallet/bill/type'; +import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type'; export type invoiceBillDataType = { type: BillTypeEnum; price: number; @@ -16,5 +16,5 @@ export const getInvoiceBillsList = () => export const submitInvoice = (data: InvoiceType) => POST(`/proApi/support/wallet/bill/invoice/submit`, data); -export const getInvoiceRecords = (data: RequestPaging) => - POST>(`/proApi/support/wallet/bill/invoice/records`, data); +export const getInvoiceRecords = (data: PaginationProps) => + POST>(`/proApi/support/wallet/bill/invoice/records`, data); diff --git a/projects/app/src/web/support/wallet/usage/api.ts b/projects/app/src/web/support/wallet/usage/api.ts index 41b32be3d..c4ce4171a 100644 --- a/projects/app/src/web/support/wallet/usage/api.ts +++ b/projects/app/src/web/support/wallet/usage/api.ts @@ -1,10 +1,17 @@ -import { GET, POST } from '@/web/common/api/request'; +import { POST } from '@/web/common/api/request'; import { CreateTrainingUsageProps } from '@fastgpt/global/support/wallet/usage/api.d'; -import type { PagingData, RequestPaging } from '@/types'; +import { UsageSourceEnum } from '@fastgpt/global/support/wallet/usage/constants'; import type { UsageItemType } from '@fastgpt/global/support/wallet/usage/type'; +import { PaginationProps, PaginationResponse } from '@fastgpt/web/common/fetch/type'; -export const getUserUsages = (data: RequestPaging) => - POST>(`/proApi/support/wallet/usage/getUsage`, data); +export const getUserUsages = ( + data: PaginationProps<{ + dateStart: Date; + dateEnd: Date; + source?: UsageSourceEnum; + teamMemberId: string; + }> +) => POST>(`/proApi/support/wallet/usage/getUsage`, data); export const postCreateTrainingUsage = (data: CreateTrainingUsageProps) => POST(`/support/wallet/usage/createTrainingUsage`, data);