Compare commits

...

39 Commits

Author SHA1 Message Date
Archer
47f7b1a7a3 full text tmp code (#3561)
* full text tmp code

* fix: init

* fix: init

* remove tmp code

* remove tmp code

* 4818-alpha
2025-01-10 18:03:14 +08:00
Archer
fadb3e3ceb feat: tmp upgrade code (#3559)
* feat: tmp upgrade code

* fulltext search test

* update action
2025-01-10 14:41:29 +08:00
Archer
1c0b323b1b share page avatar (#3558)
* feat: init 4818

* share page avatar
2025-01-10 13:37:48 +08:00
heheer
b26345dcaa share link random avatar (#3541)
* share link random avatar

* fix

* delete unused code
2025-01-10 12:18:13 +08:00
Jiangween
cef8487ca1 docs:用户答疑的官方文档补充 (#3540)
* docs:用户答疑的官方文档补充

* 问题回答的内容修补
2025-01-10 11:07:04 +08:00
Archer
ed619edd47 perf: org permission (#3556) 2025-01-10 10:48:54 +08:00
a.e.
93cf5bb372 fix: MemberModal UI (#3553)
* fix: adapt MemberModal title and icon

* fix: adapt member modal

* fix: search input placeholder

* fix: add button text
2025-01-10 09:24:06 +08:00
Archer
f556fbf0d5 4.8.18 test (#3543)
* perf: login check

* doc

* perf: llm model config

* perf: team clb config
2025-01-07 14:21:05 +08:00
a.e.
07cc849877 refactor: team permission manager (#3535)
* perf: classify org, group and member

* refactor: team per manager

* fix: missing functions
2025-01-07 09:19:23 +08:00
Archer
2066094047 perf: async read file (#3531) 2025-01-06 12:43:33 +08:00
Archer
2bf1fce32a perf: file encoding;perf: leave team code;@c121914yu perf: full text search code (#3528)
* perf: text encoding

* perf: leave team code

* perf: full text search code

* fix: http status

* perf: embedding search and vector avatar
2025-01-06 12:43:33 +08:00
Archer
5465ca642f 4.8.18 test (#3524)
* perf: remove local token

* perf: index
2025-01-06 12:43:32 +08:00
Archer
c0d0c629c5 perf: full text collection and search code;perf: rename function (#3519)
* perf: full text collection and search code

* perf: rename function

* perf: notify modal

* remove invalid code

* perf: sso login

* perf: pay process
2025-01-06 12:43:32 +08:00
heheer
20c6c202d2 fix qrcode script (#3520)
* fix qrcode script

* i18n
2025-01-06 12:43:32 +08:00
Finley Ge
c8f60b47d0 feat: support wecom sso (#3518)
* feat: support wecom sso

* chore: remove unused wecom js-sdk dependency
2025-01-06 12:43:31 +08:00
heheer
8b2be89350 optimize payment process (#3517) 2025-01-06 12:43:31 +08:00
a.e.
a401d4d612 refactor: org pathId (#3516) 2025-01-06 12:43:30 +08:00
Jiangween
d24d29ac48 perf(plugin): improve searXNG empty result handling and documentation (#3507)
* perf(plugin): improve searXNG empty result handling and documentation

* 修改了文档和代码部分无搜索的结果的反馈
2025-01-06 12:43:29 +08:00
Archer
9abbc16036 perf: notify account (#3515) 2025-01-06 12:43:28 +08:00
heheer
28710ac05b force show update notification modal & fix login page text (#3512)
* fix login page English text

* update notification modal
2025-01-06 12:43:28 +08:00
Archer
3412d7009d perf: password check;perf: image upload check;perf: sso login check (#3509)
* perf: password check

* perf: image upload check

* perf: sso login check
2025-01-06 12:43:27 +08:00
a.e.
c3480b0ffa feat: permission manage UI for org (#3503) 2025-01-06 12:43:26 +08:00
Finley Ge
f89212f35f feat: support dataset changeOwner (#3483)
* feat: support dataset changeOwner

* chore: update dataset change owner api
2025-01-06 12:43:25 +08:00
Archer
03d1c6a651 i18n (#3501)
* name

* i18n
2025-01-06 12:43:25 +08:00
Archer
5d1d4ff64f perf: org permission check (#3500) 2025-01-06 12:43:24 +08:00
a.e.
fd9600c6f8 feat: org auth for app & dataset (#3498)
* feat: auth org resource permission

* feat: org auth support for app & dataset
2025-01-06 12:43:23 +08:00
Archer
efecfd44c3 perf: Team org ui (#3499)
* perf: org ui

* perf: org ui
2025-01-06 12:43:22 +08:00
a.e.
1fc77a126a feat: org CRUD (#3380)
* feat: add org schema

* feat: org manage UI

* feat: OrgInfoModal

* feat: org tree view

* feat: org management

* fix: init root org

* feat: org permission for app

* feat: org support for dataset

* fix: disable org role control

* styles: opt type signatures

* fix: remove unused permission

* feat: delete org collaborator
2025-01-06 12:43:22 +08:00
Archer
bb669ca3ff fix: plugin cost (#3533) 2025-01-06 12:43:04 +08:00
Archer
72ed72e595 fix: charts plugins (#3530) 2025-01-05 14:41:34 +08:00
Archer
e5735fddd1 Update 4817.md (#3523) 2025-01-03 16:39:55 +08:00
Archer
fa92ef86b9 fix: system title (#3522) 2025-01-03 15:30:46 +08:00
Archer
f39ea04178 feat: new provider (#3513) 2025-01-02 15:38:06 +08:00
Archer
bd4893ced9 更新 README.md (#3511) 2025-01-02 10:31:25 +08:00
Archer
b75e807f24 fix: tool choice run same tool will error (#3502) 2024-12-31 10:58:52 +08:00
Archer
b2fdefdc0c update config.json (#3497) 2024-12-30 13:53:12 +08:00
Archer
896fec4b82 update doc (#3496) 2024-12-30 13:51:05 +08:00
Archer
50bf7f9a3b V4.8.17 feature (#3493)
* split tokens into input and output (#3477)

* split tokens into input and output

* query extension & tool call & question guide

* fix

* perf: input and output tokens

* perf: tool call if else

* perf: remove code

* fix: extract usage count

* fix: qa usage count

---------

Co-authored-by: heheer <heheer@sealos.io>
2024-12-30 10:13:25 +08:00
Archer
da2831b948 update doc (#3488) 2024-12-27 20:21:08 +08:00
266 changed files with 6100 additions and 2913 deletions

View File

@@ -36,7 +36,7 @@ jobs:
password: ${{ secrets.GH_PAT }} password: ${{ secrets.GH_PAT }}
- name: Set DOCKER_REPO_TAGGED based on branch or tag - name: Set DOCKER_REPO_TAGGED based on branch or tag
run: | run: |
echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt-pr:${{ github.event.pull_request.number }}" >> $GITHUB_ENV echo "DOCKER_REPO_TAGGED=ghcr.io/${{ github.repository_owner }}/fastgpt-pr:${{ github.event.pull_request.head.sha }}" >> $GITHUB_ENV
- name: Build image for PR - name: Build image for PR
env: env:
DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }} DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }}
@@ -44,7 +44,7 @@ jobs:
docker buildx build \ docker buildx build \
-f projects/app/Dockerfile \ -f projects/app/Dockerfile \
--label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \ --label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=fastgpt-pr imae" \ --label "org.opencontainers.image.description=fastgpt-pr image" \
--label "org.opencontainers.image.licenses=Apache" \ --label "org.opencontainers.image.licenses=Apache" \
--push \ --push \
--cache-from=type=local,src=/tmp/.buildx-cache \ --cache-from=type=local,src=/tmp/.buildx-cache \

View File

@@ -215,4 +215,4 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
1. 允许作为后台服务直接商用,但不允许提供 SaaS 服务。 1. 允许作为后台服务直接商用,但不允许提供 SaaS 服务。
2. 未经商业授权,任何形式的商用服务均需保留相关版权信息。 2. 未经商业授权,任何形式的商用服务均需保留相关版权信息。
3. 完整请查看 [FastGPT Open Source License](./LICENSE) 3. 完整请查看 [FastGPT Open Source License](./LICENSE)
4. 联系方式Dennis@sealos.io[点击查看商业版定价策略](https://doc.tryfastgpt.ai/docs/commercial) 4. 联系方式Dennis@sealos.io[点击查看商业版定价策略](https://doc.tryfastgpt.ai/docs/shopping_cart/intro/)

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

View File

@@ -168,6 +168,7 @@ weight: 707
"reRankModels": [], "reRankModels": [],
"audioSpeechModels": [ "audioSpeechModels": [
{ {
"provider": "OpenAI",
"model": "tts-1", "model": "tts-1",
"name": "OpenAI TTS1", "name": "OpenAI TTS1",
"charsPointsPrice": 0, "charsPointsPrice": 0,
@@ -182,6 +183,7 @@ weight: 707
} }
], ],
"whisperModel": { "whisperModel": {
"provider": "OpenAI",
"model": "whisper-1", "model": "whisper-1",
"name": "Whisper1", "name": "Whisper1",
"charsPointsPrice": 0 "charsPointsPrice": 0
@@ -201,7 +203,9 @@ weight: 707
- OpenAI - OpenAI
- Claude - Claude
- Gemini - Gemini
- Meta
- MistralAI - MistralAI
- AliCloud - 阿里云
- Qwen - 通义千问 - Qwen - 通义千问
- Doubao - 豆包 - Doubao - 豆包
- ChatGLM - 智谱 - ChatGLM - 智谱
@@ -213,7 +217,10 @@ weight: 707
- Baichuan - 百川 - Baichuan - 百川
- Yi - 零一万物 - Yi - 零一万物
- Ernie - 文心一言 - Ernie - 文心一言
- StepFun - 阶跃星辰
- Ollama - Ollama
- BAAI - 智源研究院
- FishAudio
- Other - 其他 - Other - 其他

View File

@@ -19,6 +19,10 @@ images: []
## 二、通用问题 ## 二、通用问题
### 本地部署的限制
具体内容参考https://fael3z0zfze.feishu.cn/wiki/OFpAw8XzAi36Guk8dfucrCKUnjg。
### 能否纯本地运行 ### 能否纯本地运行
可以。需要准备好向量模型和LLM模型。 可以。需要准备好向量模型和LLM模型。

View File

@@ -148,7 +148,7 @@ FastGPT 在`pnpm i`后会执行`postinstall`脚本,用于自动生成`ChakraUI
## 加入社区 ## 加入社区
遇到困难了吗?有任何问题吗? 加入微信群与开发者和用户保持沟通。 遇到困难了吗?有任何问题吗? 加入飞书群与开发者和用户保持沟通。
<img width="400px" src="https://oss.laf.run/otnvvf-imgs/fastgpt-feishu1.png" class="medium-zoom-image" /> <img width="400px" src="https://oss.laf.run/otnvvf-imgs/fastgpt-feishu1.png" class="medium-zoom-image" />

View File

@@ -1,5 +1,5 @@
--- ---
title: 'V4.8.17(进行中)' title: 'V4.8.17(包含升级脚本)'
description: 'FastGPT V4.8.17 更新说明' description: 'FastGPT V4.8.17 更新说明'
icon: 'upgrade' icon: 'upgrade'
draft: false draft: false
@@ -7,7 +7,16 @@ toc: true
weight: 807 weight: 807
--- ---
## 运行升级脚本 ## 更新指南
### 1. 更新镜像:
- 更新 fastgpt 镜像 tag: v4.8.17-fix-title
- 更新 fastgpt-pro 商业版镜像 tag: v4.8.17
- Sandbox 镜像无需更新
### 2. 运行升级脚本
从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`{{host}} 替换成**FastGPT 域名**。 从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`{{host}} 替换成**FastGPT 域名**。
@@ -19,6 +28,11 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4817' \
会将用户绑定的 OpenAI 账号移动到团队中。 会将用户绑定的 OpenAI 账号移动到团队中。
## 调整 completions 接口返回值
/api/v1/chat/completions 接口返回值调整,对话节点、工具节点等使用到模型的节点,将不再返回 `tokens` 字段,改为返回 `inputTokens``outputTokens` 字段,分别表示输入和输出的 Token 数量。
## 完整更新内容 ## 完整更新内容
1. 新增 - 简易模式工具调用支持数组类型插件。 1. 新增 - 简易模式工具调用支持数组类型插件。
@@ -27,10 +41,12 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4817' \
4. 新增 - 商业版支持后台配置模板市场。 4. 新增 - 商业版支持后台配置模板市场。
5. 新增 - 商业版支持后台配置自定义工作流变量,用于与业务系统鉴权打通。 5. 新增 - 商业版支持后台配置自定义工作流变量,用于与业务系统鉴权打通。
6. 新增 - 搜索测试接口支持问题优化。 6. 新增 - 搜索测试接口支持问题优化。
7. 优化 - Markdown 大小测试,超出 20 万字符不使用 Markdown 组件,避免崩溃 7. 新增 - 工作流中 Input Token 和 Output Token 分开记录展示。并修复部分请求未记录输出 Token 计费问题
8. 优化 - 知识库搜索参数,滑动条支持输入模式,可以更精准的控制 8. 优化 - Markdown 大小测试,超出 20 万字符不使用 Markdown 组件,避免崩溃
9. 优化 - 可用模型展示 9. 优化 - 知识库搜索参数,滑动条支持输入模式,可以更精准的控制。
10. 优化 - Mongo 查询语句,增加 virtual 字段 10. 优化 - 可用模型展示UI
11. 修复 - 文件返回接口缺少 Content-Length 头,导致通过非同源文件上传时,阿里 vision 模型无法识别图片 11. 优化 - Mongo 查询语句,增加 virtual 字段
12. 修复 - 去除判断器两端字符串隐藏换行符,避免判断器失效 12. 修复 - 文件返回接口缺少 Content-Length 头,导致通过非同源文件上传时,阿里 vision 模型无法识别图片
13. 修复 - 变量更新节点,手动输入更新内容时候,非字符串类型数据类型无法自动转化。 13. 修复 - 去除判断器两端字符串隐藏换行符,避免判断器失效。
14. 修复 - 变量更新节点,手动输入更新内容时候,非字符串类型数据类型无法自动转化。
15. 修复 - 豆包模型无法工具调用。

View File

@@ -0,0 +1,37 @@
---
title: 'V4.8.18(进行中)'
description: 'FastGPT V4.8.18 更新说明'
icon: 'upgrade'
draft: false
toc: true
weight: 806
---
## 更新指南
### 2. 运行升级脚本
从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`{{host}} 替换成**FastGPT 域名**。
```bash
curl --location --request POST 'https://{{host}}/api/admin/initv4818' \
--header 'rootkey: {{rootkey}}' \
--header 'Content-Type: application/json'
```
会迁移全文检索表,时间较长,迁移期间全文检索会失效,日志中会打印已经迁移的数据长度。
## 完整更新内容
1. 新增 - 支持部门架构权限模式。
2. 新增 - 支持配置自定跨域安全策略,默认全开。
3. 优化 - 分享链接随机生成用户头像。
4. 优化 - 图片上传安全校验。并增加头像图片唯一存储,确保不会累计存储。
5. 优化 - Mongo 全文索引表分离。
6. 优化 - 知识库检索查询语句合并,同时减少查库数量。
7. 优化 - 文件编码检测,减少 CSV 文件乱码概率。
8. 优化 - 异步读取文件内容,减少进程阻塞。
9. 优化 - 文件阅读HTML 直接下载,不允许在线阅读。
10. 修复 - HTML 文件上传base64 图片无法自动转图片链接。
11. 修复 - 插件计费错误。

View File

@@ -13,4 +13,12 @@ weight: 908
但是,当连续问题之间的关联性较小,模型判断的准确度可能会受到限制。在这种情况下,我们可以引入全局变量的概念来记录分类结果。在后续的问题分类阶段,首先检查全局变量是否存有分类结果。如果有,那么直接沿用该结果;若没有,则让模型自行判断。 但是,当连续问题之间的关联性较小,模型判断的准确度可能会受到限制。在这种情况下,我们可以引入全局变量的概念来记录分类结果。在后续的问题分类阶段,首先检查全局变量是否存有分类结果。如果有,那么直接沿用该结果;若没有,则让模型自行判断。
建议:构建批量运行脚本进行测试,评估问题分类的准确性。 建议:构建批量运行脚本进行测试,评估问题分类的准确性。
## 系统编排配置中的定时执行,如果用户打开分享的连接,停留在那个页面,定时执行触发问题
发布后,后台生效。
## AI对话回答要求中的Markdown语法取消
在针对知识库的回答要求里有, 要给它配置提示词,不然他就是默认的,默认的里面就有该语法。

View File

@@ -14,4 +14,51 @@ weight: 910
## 知识库配置里的文件处理模型是什么?与索引模型有什么区别? ## 知识库配置里的文件处理模型是什么?与索引模型有什么区别?
* **文件处理模型**:用于数据处理的【增强处理】和【问答拆分】。在【增强处理】中,生成相关问题和摘要,在【问答拆分】中执行问答对生成。 * **文件处理模型**:用于数据处理的【增强处理】和【问答拆分】。在【增强处理】中,生成相关问题和摘要,在【问答拆分】中执行问答对生成。
* **索引模型**:用于向量化,即通过对文本数据进行处理和组织,构建出一个能够快速查询的数据结构。 * **索引模型**:用于向量化,即通过对文本数据进行处理和组织,构建出一个能够快速查询的数据结构。
## 基于知识库的查询但是问题相关的答案过多。ai回答到一半就不继续回答。
FastGPT回复长度计算公式:
最大回复=min(配置的最大回复(内置的限制),最大上下文(输入和输出的总和)-历史记录)
18K模型->输入与输出的和
输出增多->输入减小
所以可以:
1. 检查配置的最大回复(回复上限)
2. 减小输入来增大输出,即减小历史记录,在工作流其实也就是“聊天记录”
配置的最大回复:
![](/imgs/dataset1.png)
![](/imgs/dataset2.png)
1. 私有化部署的时候,后台配模型参数,可以在配置最大上文时候,预留一些空间,比如 128000 的模型,可以只配置 120000, 剩余的空间后续会被安排给输出
## 受到模型上下文的限制,有时候达不到聊天记录的轮次,连续对话字数过多就会报上下文不够的错误。
FastGPT回复长度计算公式:
最大回复=min(配置的最大回复(内置的限制),最大上下文(输入和输出的总和)-历史记录)
18K模型->输入与输出的和
输出增多->输入减小
所以可以:
1. 检查配置的最大回复(回复上限)
2. 减小输入来增大输出,即减小历史记录,在工作流其实也就是“聊天记录”
配置的最大回复:
![](/imgs/dataset1.png)
![](/imgs/dataset2.png)
1. 私有化部署的时候,后台配模型参数,可以在配置最大上文时候,预留一些空间,比如 128000 的模型,可以只配置 120000, 剩余的空间后续会被安排给输出

View File

@@ -160,6 +160,18 @@ default_doi_resolver: 'oadoi.org'
} }
``` ```
* 搜索结果为空时会返回友好提示:
```Bash
{
"result": "[]",
"error": {
"message": "No search results",
"code": 500
}
}
```
* 失败时通过 Promise.reject 可能返回错误信息: * 失败时通过 Promise.reject 可能返回错误信息:
```Bash ```Bash

View File

@@ -114,15 +114,15 @@ services:
# fastgpt # fastgpt
sandbox: sandbox:
container_name: sandbox container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.8.16 # git image: ghcr.io/labring/fastgpt-sandbox:v4.8.17 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.16 # 阿里云 # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.17 # 阿里云
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
fastgpt: fastgpt:
container_name: fastgpt container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.8.16 # git image: ghcr.io/labring/fastgpt:v4.8.17 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.16 # 阿里云 # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.17 # 阿里云
ports: ports:
- 3000:3000 - 3000:3000
networks: networks:

View File

@@ -72,15 +72,15 @@ services:
# fastgpt # fastgpt
sandbox: sandbox:
container_name: sandbox container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.8.16 # git image: ghcr.io/labring/fastgpt-sandbox:v4.8.17 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.16 # 阿里云 # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.17 # 阿里云
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
fastgpt: fastgpt:
container_name: fastgpt container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.8.16 # git image: ghcr.io/labring/fastgpt:v4.8.17 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.16 # 阿里云 # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.17 # 阿里云
ports: ports:
- 3000:3000 - 3000:3000
networks: networks:

View File

@@ -53,15 +53,15 @@ services:
wait $$! wait $$!
sandbox: sandbox:
container_name: sandbox container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.8.16 # git image: ghcr.io/labring/fastgpt-sandbox:v4.8.17 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.16 # 阿里云 # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.17 # 阿里云
networks: networks:
- fastgpt - fastgpt
restart: always restart: always
fastgpt: fastgpt:
container_name: fastgpt container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.8.16 # git image: ghcr.io/labring/fastgpt:v4.8.17 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.16 # 阿里云 # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.17 # 阿里云
ports: ports:
- 3000:3000 - 3000:3000
networks: networks:

View File

@@ -1,14 +1,20 @@
import { i18nT } from '../../../../web/i18n/utils';
import { ErrType } from '../errorCode'; import { ErrType } from '../errorCode';
/* dataset: 507000 */ /* dataset: 507000 */
const startCode = 507000; const startCode = 507000;
export enum CommonErrEnum { export enum CommonErrEnum {
invalidParams = 'invalidParams',
fileNotFound = 'fileNotFound', fileNotFound = 'fileNotFound',
unAuthFile = 'unAuthFile', unAuthFile = 'unAuthFile',
missingParams = 'missingParams', missingParams = 'missingParams',
inheritPermissionError = 'inheritPermissionError' inheritPermissionError = 'inheritPermissionError'
} }
const datasetErr = [ const datasetErr = [
{
statusText: CommonErrEnum.fileNotFound,
message: i18nT('common:error.invalid_params')
},
{ {
statusText: CommonErrEnum.fileNotFound, statusText: CommonErrEnum.fileNotFound,
message: 'error.fileNotFound' message: 'error.fileNotFound'

View File

@@ -1,9 +1,11 @@
import { ErrType } from '../errorCode';
import { i18nT } from '../../../../web/i18n/utils'; import { i18nT } from '../../../../web/i18n/utils';
import type { ErrType } from '../errorCode';
/* team: 500000 */ /* team: 500000 */
export enum TeamErrEnum { export enum TeamErrEnum {
notUser = 'notUser',
teamOverSize = 'teamOverSize', teamOverSize = 'teamOverSize',
unAuthTeam = 'unAuthTeam', unAuthTeam = 'unAuthTeam',
teamMemberOverSize = 'teamMemberOverSize',
aiPointsNotEnough = 'aiPointsNotEnough', aiPointsNotEnough = 'aiPointsNotEnough',
datasetSizeNotEnough = 'datasetSizeNotEnough', datasetSizeNotEnough = 'datasetSizeNotEnough',
datasetAmountNotEnough = 'datasetAmountNotEnough', datasetAmountNotEnough = 'datasetAmountNotEnough',
@@ -14,11 +16,22 @@ export enum TeamErrEnum {
groupNameEmpty = 'groupNameEmpty', groupNameEmpty = 'groupNameEmpty',
groupNameDuplicate = 'groupNameDuplicate', groupNameDuplicate = 'groupNameDuplicate',
groupNotExist = 'groupNotExist', groupNotExist = 'groupNotExist',
orgMemberNotExist = 'orgMemberNotExist',
orgMemberDuplicated = 'orgMemberDuplicated',
orgNotExist = 'orgNotExist',
orgParentNotExist = 'orgParentNotExist',
cannotMoveToSubPath = 'cannotMoveToSubPath',
cannotModifyRootOrg = 'cannotModifyRootOrg',
cannotDeleteNonEmptyOrg = 'cannotDeleteNonEmptyOrg',
cannotDeleteDefaultGroup = 'cannotDeleteDefaultGroup', cannotDeleteDefaultGroup = 'cannotDeleteDefaultGroup',
userNotActive = 'userNotActive' userNotActive = 'userNotActive'
} }
const teamErr = [ const teamErr = [
{
statusText: TeamErrEnum.notUser,
message: i18nT('common:code_error.team_error.not_user')
},
{ {
statusText: TeamErrEnum.teamOverSize, statusText: TeamErrEnum.teamOverSize,
message: i18nT('common:code_error.team_error.over_size') message: i18nT('common:code_error.team_error.over_size')
@@ -71,6 +84,34 @@ const teamErr = [
{ {
statusText: TeamErrEnum.userNotActive, statusText: TeamErrEnum.userNotActive,
message: i18nT('common:code_error.team_error.user_not_active') message: i18nT('common:code_error.team_error.user_not_active')
},
{
statusText: TeamErrEnum.orgMemberNotExist,
message: i18nT('common:code_error.team_error.org_member_not_exist')
},
{
statusText: TeamErrEnum.orgMemberDuplicated,
message: i18nT('common:code_error.team_error.org_member_duplicated')
},
{
statusText: TeamErrEnum.orgNotExist,
message: i18nT('common:code_error.team_error.org_not_exist')
},
{
statusText: TeamErrEnum.orgParentNotExist,
message: i18nT('common:code_error.team_error.org_parent_not_exist')
},
{
statusText: TeamErrEnum.cannotMoveToSubPath,
message: i18nT('common:code_error.team_error.cannot_move_to_sub_path')
},
{
statusText: TeamErrEnum.cannotModifyRootOrg,
message: i18nT('common:code_error.team_error.cannot_modify_root_org')
},
{
statusText: TeamErrEnum.cannotDeleteNonEmptyOrg,
message: i18nT('common:code_error.team_error.cannot_delete_non_empty_org')
} }
]; ];

View File

@@ -2,25 +2,16 @@ import { ErrType } from '../errorCode';
import { i18nT } from '../../../../web/i18n/utils'; import { i18nT } from '../../../../web/i18n/utils';
/* team: 503000 */ /* team: 503000 */
export enum UserErrEnum { export enum UserErrEnum {
unAuthUser = 'unAuthUser',
unAuthRole = 'unAuthRole', unAuthRole = 'unAuthRole',
binVisitor = 'binVisitor', binVisitor = 'binVisitor',
balanceNotEnough = 'balanceNotEnough', balanceNotEnough = 'balanceNotEnough',
unAuthSso = 'unAuthSso' unAuthSso = 'unAuthSso'
} }
const errList = [ const errList = [
{
statusText: UserErrEnum.unAuthUser,
message: i18nT('common:code_error.user_error.un_auth_user')
},
{ {
statusText: UserErrEnum.binVisitor, statusText: UserErrEnum.binVisitor,
message: i18nT('common:code_error.user_error.bin_visitor') message: i18nT('common:code_error.user_error.bin_visitor')
}, // 身份校验未通过 },
{
statusText: UserErrEnum.binVisitor,
message: i18nT('common:code_error.user_error.bin_visitor_guest')
}, // 游客身份
{ {
statusText: UserErrEnum.balanceNotEnough, statusText: UserErrEnum.balanceNotEnough,
message: i18nT('common:code_error.user_error.balance_not_enough') message: i18nT('common:code_error.user_error.balance_not_enough')

View File

@@ -1,10 +1,7 @@
import { MongoImageTypeEnum } from './image/constants';
import { OutLinkChatAuthProps } from '../../support/permission/chat.d'; import { OutLinkChatAuthProps } from '../../support/permission/chat.d';
export type preUploadImgProps = OutLinkChatAuthProps & { export type preUploadImgProps = OutLinkChatAuthProps & {
type: `${MongoImageTypeEnum}`; // expiredTime?: Date;
expiredTime?: Date;
metadata?: Record<string, any>; metadata?: Record<string, any>;
}; };
export type UploadImgProps = preUploadImgProps & { export type UploadImgProps = preUploadImgProps & {

View File

@@ -1,61 +1,5 @@
export const imageBaseUrl = '/api/system/img/'; export const imageBaseUrl = '/api/system/img/';
export enum MongoImageTypeEnum {
systemAvatar = 'systemAvatar',
appAvatar = 'appAvatar',
pluginAvatar = 'pluginAvatar',
datasetAvatar = 'datasetAvatar',
userAvatar = 'userAvatar',
teamAvatar = 'teamAvatar',
groupAvatar = 'groupAvatar',
chatImage = 'chatImage',
collectionImage = 'collectionImage'
}
export const mongoImageTypeMap = {
[MongoImageTypeEnum.systemAvatar]: {
label: 'appAvatar',
unique: true
},
[MongoImageTypeEnum.appAvatar]: {
label: 'appAvatar',
unique: true
},
[MongoImageTypeEnum.pluginAvatar]: {
label: 'pluginAvatar',
unique: true
},
[MongoImageTypeEnum.datasetAvatar]: {
label: 'datasetAvatar',
unique: true
},
[MongoImageTypeEnum.userAvatar]: {
label: 'userAvatar',
unique: true
},
[MongoImageTypeEnum.teamAvatar]: {
label: 'teamAvatar',
unique: true
},
[MongoImageTypeEnum.groupAvatar]: {
label: 'groupAvatar',
unique: true
},
[MongoImageTypeEnum.chatImage]: {
label: 'chatImage',
unique: false
},
[MongoImageTypeEnum.collectionImage]: {
label: 'collectionImage',
unique: false
}
};
export const uniqueImageTypeList = Object.entries(mongoImageTypeMap)
.filter(([key, value]) => value.unique)
.map(([key]) => key as `${MongoImageTypeEnum}`);
export const FolderIcon = 'file/fill/folder'; export const FolderIcon = 'file/fill/folder';
export const FolderImgUrl = '/imgs/files/folder.svg'; export const FolderImgUrl = '/imgs/files/folder.svg';
export const HttpPluginImgUrl = '/imgs/app/httpPluginFill.svg'; export const HttpPluginImgUrl = '/imgs/app/httpPluginFill.svg';

View File

@@ -1,12 +1,8 @@
import { MongoImageTypeEnum } from './constants';
export type MongoImageSchemaType = { export type MongoImageSchemaType = {
_id: string; _id: string;
teamId: string; teamId: string;
binary: Buffer; binary: Buffer;
createTime: Date;
expiredTime?: Date; expiredTime?: Date;
type: `${MongoImageTypeEnum}`;
metadata?: { metadata?: {
mime?: string; // image mime type. mime?: string; // image mime type.

View File

@@ -2,6 +2,7 @@ import { detect } from 'jschardet';
import { documentFileType, imageFileType } from './constants'; import { documentFileType, imageFileType } from './constants';
import { ChatFileTypeEnum } from '../../core/chat/constants'; import { ChatFileTypeEnum } from '../../core/chat/constants';
import { UserChatItemValueItemType } from '../../core/chat/type'; import { UserChatItemValueItemType } from '../../core/chat/type';
import * as fs from 'fs';
export const formatFileSize = (bytes: number): string => { export const formatFileSize = (bytes: number): string => {
if (bytes === 0) return '0 B'; if (bytes === 0) return '0 B';
@@ -16,6 +17,22 @@ export const formatFileSize = (bytes: number): string => {
export const detectFileEncoding = (buffer: Buffer) => { export const detectFileEncoding = (buffer: Buffer) => {
return detect(buffer.slice(0, 200))?.encoding?.toLocaleLowerCase(); return detect(buffer.slice(0, 200))?.encoding?.toLocaleLowerCase();
}; };
export const detectFileEncodingByPath = async (path: string) => {
// Get 64KB file head
const MAX_BYTES = 64 * 1024;
const buffer = Buffer.alloc(MAX_BYTES);
const fd = await fs.promises.open(path, 'r');
try {
// Read file head
const { bytesRead } = await fd.read(buffer, 0, MAX_BYTES, 0);
const actualBuffer = buffer.slice(0, bytesRead);
return detect(actualBuffer)?.encoding?.toLocaleLowerCase();
} finally {
await fd.close();
}
};
// Url => user upload file type // Url => user upload file type
export const parseUrlToFileType = (url: string): UserChatItemValueItemType['file'] | undefined => { export const parseUrlToFileType = (url: string): UserChatItemValueItemType['file'] | undefined => {

View File

@@ -25,17 +25,22 @@ export const simpleText = (text = '') => {
return text; return text;
}; };
/* export const valToStr = (val: any) => {
replace {{variable}} to value if (val === undefined) return 'undefined';
*/ if (val === null) return 'null';
if (typeof val === 'object') return JSON.stringify(val);
return String(val);
};
// replace {{variable}} to value
export function replaceVariable(text: any, obj: Record<string, string | number>) { export function replaceVariable(text: any, obj: Record<string, string | number>) {
if (typeof text !== 'string') return text; if (typeof text !== 'string') return text;
for (const key in obj) { for (const key in obj) {
const val = obj[key]; const val = obj[key];
const formatVal = typeof val === 'object' ? JSON.stringify(val) : String(val); const formatVal = valToStr(val);
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), () => formatVal);
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), formatVal);
} }
return text || ''; return text || '';
} }

View File

@@ -1,6 +1,9 @@
export const HUMAN_ICON = `/icon/human.svg`; export const HUMAN_ICON = `/icon/human.svg`;
export const LOGO_ICON = `/icon/logo.svg`; export const LOGO_ICON = `/icon/logo.svg`;
export const HUGGING_FACE_ICON = `/imgs/model/huggingface.svg`; export const HUGGING_FACE_ICON = `/imgs/model/huggingface.svg`;
export const DEFAULT_TEAM_AVATAR = `/imgs/avatar/defaultTeamAvatar.svg`; export const DEFAULT_TEAM_AVATAR = `/imgs/avatar/defaultTeamAvatar.svg`;
export const DEFAULT_ORG_AVATAR = '/imgs/avatar/defaultOrgAvatar.svg';
export const DEFAULT_USER_AVATAR = '/imgs/avatar/BlueAvatar.svg';
export const isProduction = process.env.NODE_ENV === 'production'; export const isProduction = process.env.NODE_ENV === 'production';

View File

@@ -73,6 +73,11 @@ export type FastGPTFeConfigsType = {
google?: string; google?: string;
wechat?: string; wechat?: string;
dingtalk?: string; dingtalk?: string;
wecom?: {
corpid?: string;
agentid?: string;
secret?: string;
};
microsoft?: { microsoft?: {
clientId?: string; clientId?: string;
tenantId?: string; tenantId?: string;

View File

@@ -1,6 +1,13 @@
import type { ModelProviderIdType } from './provider'; import type { ModelProviderIdType } from './provider';
export type LLMModelItemType = { type PriceType = {
charsPointsPrice?: number; // 1k chars=n points; 60s=n points;
// If inputPrice is set, the input-output charging scheme is adopted
inputPrice?: number; // 1k tokens=n points
outputPrice?: number; // 1k tokens=n points
};
export type LLMModelItemType = PriceType & {
provider: ModelProviderIdType; provider: ModelProviderIdType;
model: string; model: string;
name: string; name: string;
@@ -10,8 +17,6 @@ export type LLMModelItemType = {
quoteMaxToken: number; quoteMaxToken: number;
maxTemperature: number; maxTemperature: number;
charsPointsPrice: number; // 1k chars=n points
censor?: boolean; censor?: boolean;
vision?: boolean; vision?: boolean;
@@ -33,13 +38,12 @@ export type LLMModelItemType = {
fieldMap?: Record<string, string>; fieldMap?: Record<string, string>;
}; };
export type VectorModelItemType = { export type VectorModelItemType = PriceType & {
provider: ModelProviderIdType; provider: ModelProviderIdType;
model: string; // model name model: string; // model name
name: string; // show name name: string; // show name
avatar?: string; avatar?: string;
defaultToken: number; // split text default token defaultToken: number; // split text default token
charsPointsPrice: number; // 1k tokens=n points
maxToken: number; // model max token maxToken: number; // model max token
weight: number; // training weight weight: number; // training weight
hidden?: boolean; // Disallow creation hidden?: boolean; // Disallow creation
@@ -48,25 +52,23 @@ export type VectorModelItemType = {
queryConfig?: Record<string, any>; // Custom parameters for query queryConfig?: Record<string, any>; // Custom parameters for query
}; };
export type ReRankModelItemType = { export type ReRankModelItemType = PriceType & {
provider: ModelProviderIdType;
model: string; model: string;
name: string; name: string;
charsPointsPrice: number;
requestUrl: string; requestUrl: string;
requestAuth: string; requestAuth: string;
}; };
export type AudioSpeechModelType = { export type AudioSpeechModelType = PriceType & {
provider: ModelProviderIdType; provider: ModelProviderIdType;
model: string; model: string;
name: string; name: string;
charsPointsPrice: number;
voices: { label: string; value: string; bufferId: string }[]; voices: { label: string; value: string; bufferId: string }[];
}; };
export type STTModelType = { export type STTModelType = PriceType & {
provider: ModelProviderIdType; provider: ModelProviderIdType;
model: string; model: string;
name: string; name: string;
charsPointsPrice: number; // 60s = n points
}; };

View File

@@ -4,20 +4,25 @@ export type ModelProviderIdType =
| 'OpenAI' | 'OpenAI'
| 'Claude' | 'Claude'
| 'Gemini' | 'Gemini'
| 'Meta'
| 'MistralAI' | 'MistralAI'
| 'Groq' | 'Groq'
| 'AliCloud'
| 'Qwen' | 'Qwen'
| 'Doubao' | 'Doubao'
| 'ChatGLM' | 'ChatGLM'
| 'DeepSeek' | 'DeepSeek'
| 'Ernie'
| 'Moonshot' | 'Moonshot'
| 'MiniMax' | 'MiniMax'
| 'SparkDesk' | 'SparkDesk'
| 'Hunyuan' | 'Hunyuan'
| 'Baichuan' | 'Baichuan'
| 'StepFun'
| 'Yi' | 'Yi'
| 'Ernie'
| 'Ollama' | 'Ollama'
| 'BAAI'
| 'FishAudio'
| 'Other'; | 'Other';
export type ModelProviderType = { export type ModelProviderType = {
@@ -42,6 +47,11 @@ export const ModelProviderList: ModelProviderType[] = [
name: 'Gemini', name: 'Gemini',
avatar: 'model/gemini' avatar: 'model/gemini'
}, },
{
id: 'Meta',
name: 'Meta',
avatar: 'model/meta'
},
{ {
id: 'MistralAI', id: 'MistralAI',
name: 'MistralAI', name: 'MistralAI',
@@ -52,6 +62,11 @@ export const ModelProviderList: ModelProviderType[] = [
name: 'Groq', name: 'Groq',
avatar: 'model/groq' avatar: 'model/groq'
}, },
{
id: 'AliCloud',
name: i18nT('common:model_alicloud'),
avatar: 'model/alicloud'
},
{ {
id: 'Qwen', id: 'Qwen',
name: i18nT('common:model_qwen'), name: i18nT('common:model_qwen'),
@@ -67,6 +82,11 @@ export const ModelProviderList: ModelProviderType[] = [
name: i18nT('common:model_chatglm'), name: i18nT('common:model_chatglm'),
avatar: 'model/chatglm' avatar: 'model/chatglm'
}, },
{
id: 'Ernie',
name: i18nT('common:model_ernie'),
avatar: 'model/ernie'
},
{ {
id: 'DeepSeek', id: 'DeepSeek',
name: 'DeepSeek', name: 'DeepSeek',
@@ -97,21 +117,32 @@ export const ModelProviderList: ModelProviderType[] = [
name: i18nT('common:model_baichuan'), name: i18nT('common:model_baichuan'),
avatar: 'model/baichuan' avatar: 'model/baichuan'
}, },
{
id: 'StepFun',
name: i18nT('common:model_stepfun'),
avatar: 'model/stepfun'
},
{ {
id: 'Yi', id: 'Yi',
name: i18nT('common:model_yi'), name: i18nT('common:model_yi'),
avatar: 'model/yi' avatar: 'model/yi'
}, },
{
id: 'Ernie',
name: i18nT('common:model_ernie'),
avatar: 'model/ernie'
},
{ {
id: 'Ollama', id: 'Ollama',
name: 'Ollama', name: 'Ollama',
avatar: 'model/ollama' avatar: 'model/ollama'
}, },
{
id: 'BAAI',
name: i18nT('common:model_baai'),
avatar: 'model/BAAI'
},
{
id: 'FishAudio',
name: 'FishAudio',
avatar: 'model/fishaudio'
},
{ {
id: 'Other', id: 'Other',
name: i18nT('common:model_other'), name: i18nT('common:model_other'),

View File

@@ -1,6 +1,6 @@
import { RequireOnlyOne } from '../../common/type/utils'; import type { RequireOnlyOne } from '../../common/type/utils';
import { import {
UpdateClbPermissionProps, type UpdateClbPermissionProps,
UpdatePermissionBody UpdatePermissionBody
} from '../../support/permission/collaborator'; } from '../../support/permission/collaborator';
import { PermissionValueType } from '../../support/permission/type'; import { PermissionValueType } from '../../support/permission/type';
@@ -14,4 +14,5 @@ export type AppCollaboratorDeleteParams = {
} & RequireOnlyOne<{ } & RequireOnlyOne<{
tmbId: string; tmbId: string;
groupId: string; groupId: string;
orgId: string;
}>; }>;

View File

@@ -11,4 +11,5 @@ export type DatasetCollaboratorDeleteParams = {
} & RequireOnlyOne<{ } & RequireOnlyOne<{
tmbId: string; tmbId: string;
groupId: string; groupId: string;
orgId: string;
}>; }>;

View File

@@ -112,6 +112,15 @@ export type DatasetDataSchemaType = {
rebuilding?: boolean; rebuilding?: boolean;
}; };
export type DatasetDataTextSchemaType = {
_id: string;
teamId: string;
datasetId: string;
collectionId: string;
dataId: string;
fullTextToken: string;
};
export type DatasetTrainingSchemaType = { export type DatasetTrainingSchemaType = {
_id: string; _id: string;
userId: string; userId: string;

View File

@@ -107,7 +107,9 @@ export type DispatchNodeResponseType = {
mergeSignId?: string; mergeSignId?: string;
// bill // bill
tokens?: number; tokens?: number; // deprecated
inputTokens?: number;
outputTokens?: number;
model?: string; model?: string;
contextTotalLen?: number; contextTotalLen?: number;
totalPoints?: number; totalPoints?: number;
@@ -157,6 +159,8 @@ export type DispatchNodeResponseType = {
// tool // tool
toolCallTokens?: number; toolCallTokens?: number;
toolCallInputTokens?: number;
toolCallOutputTokens?: number;
toolDetail?: ChatHistoryItemResType[]; toolDetail?: ChatHistoryItemResType[];
toolStop?: boolean; toolStop?: boolean;

View File

@@ -9,7 +9,7 @@ import { isValidReferenceValueFormat } from '../utils';
import { FlowNodeOutputItemType, ReferenceValueType } from '../type/io'; import { FlowNodeOutputItemType, ReferenceValueType } from '../type/io';
import { ChatItemType, NodeOutputItemType } from '../../../core/chat/type'; import { ChatItemType, NodeOutputItemType } from '../../../core/chat/type';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '../../../core/chat/constants'; import { ChatItemValueTypeEnum, ChatRoleEnum } from '../../../core/chat/constants';
import { replaceVariable } from '../../../common/string/tools'; import { replaceVariable, valToStr } from '../../../common/string/tools';
export const getMaxHistoryLimitFromNodes = (nodes: StoreNodeItemType[]): number => { export const getMaxHistoryLimitFromNodes = (nodes: StoreNodeItemType[]): number => {
let limit = 10; let limit = 10;
@@ -343,11 +343,7 @@ export function replaceEditorVariable({
if (input) return getReferenceVariableValue({ value: input.value, nodes, variables }); if (input) return getReferenceVariableValue({ value: input.value, nodes, variables });
})(); })();
const formatVal = (() => { const formatVal = valToStr(variableVal);
if (variableVal === undefined) return 'undefined';
if (variableVal === null) return 'null';
return typeof variableVal === 'object' ? JSON.stringify(variableVal) : String(variableVal);
})();
const regex = new RegExp(`\\{\\{\\$(${nodeId}\\.${id})\\$\\}\\}`, 'g'); const regex = new RegExp(`\\{\\{\\$(${nodeId}\\.${id})\\$\\}\\}`, 'g');
text = text.replace(regex, () => formatVal); text = text.replace(regex, () => formatVal);

View File

@@ -13,6 +13,7 @@
"next": "14.2.5", "next": "14.2.5",
"openai": "4.61.0", "openai": "4.61.0",
"openapi-types": "^12.1.3", "openapi-types": "^12.1.3",
"json5": "^2.2.3",
"timezones-list": "^3.0.2" "timezones-list": "^3.0.2"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -10,22 +10,18 @@ export type CollaboratorItemType = {
} & RequireOnlyOne<{ } & RequireOnlyOne<{
tmbId: string; tmbId: string;
groupId: string; groupId: string;
orgId: string;
}>; }>;
export type UpdateClbPermissionProps = { export type UpdateClbPermissionProps = {
members?: string[]; members?: string[];
groups?: string[]; groups?: string[];
orgs?: string[];
permission: PermissionValueType; permission: PermissionValueType;
}; };
export type DeleteClbPermissionProps = RequireOnlyOne<{ export type DeletePermissionQuery = RequireOnlyOne<{
tmbId: string; tmbId?: string;
groupId: string; groupId?: string;
}>; orgId?: string;
export type UpdatePermissionBody = {
permission: PermissionValueType;
} & RequireOnlyOne<{
memberId: string;
groupId: string;
}>; }>;

View File

@@ -1,8 +1,9 @@
import { UserModelSchema } from '../user/type'; import { UserModelSchema } from '../user/type';
import { RequireOnlyOne } from '../../common/type/utils'; import { RequireOnlyOne } from '../../common/type/utils';
import { TeamMemberSchema } from '../user/team/type'; import { TeamMemberSchema } from '../user/team/type';
import { AuthUserTypeEnum, PermissionKeyEnum, PerResourceTypeEnum } from './constant';
import { MemberGroupSchemaType } from './memberGroup/type'; import { MemberGroupSchemaType } from './memberGroup/type';
import type { TeamMemberWithUserSchema } from '../user/team/type';
import { AuthUserTypeEnum, type PermissionKeyEnum, type PerResourceTypeEnum } from './constant';
// PermissionValueType, the type of permission's value is a number, which is a bit field actually. // PermissionValueType, the type of permission's value is a number, which is a bit field actually.
// It is spired by the permission system in Linux. // It is spired by the permission system in Linux.
@@ -29,6 +30,7 @@ export type ResourcePermissionType = {
} & RequireOnlyOne<{ } & RequireOnlyOne<{
tmbId: string; tmbId: string;
groupId: string; groupId: string;
orgId: string;
}>; }>;
export type ResourcePerWithTmbWithUser = Omit<ResourcePermissionType, 'tmbId'> & { export type ResourcePerWithTmbWithUser = Omit<ResourcePermissionType, 'tmbId'> & {

View File

@@ -17,5 +17,6 @@ export enum OAuthEnum {
wechat = 'wechat', wechat = 'wechat',
microsoft = 'microsoft', microsoft = 'microsoft',
dingtalk = 'dingtalk', dingtalk = 'dingtalk',
wecom = 'wecom',
sso = 'sso' sso = 'sso'
} }

View File

@@ -0,0 +1,3 @@
export function checkIsWecomTerminal() {
return /wxwork/i.test(navigator.userAgent);
}

View File

@@ -0,0 +1,32 @@
export type postCreateOrgData = {
name: string;
parentId: string;
description?: string;
avatar?: string;
};
export type putUpdateOrgMembersData = {
orgId: string;
members: {
tmbId: string;
// role: `${OrgMemberRole}`;
}[];
};
export type putUpdateOrgData = {
orgId: string;
name?: string;
avatar?: string;
description?: string;
};
export type putMoveOrgType = {
orgId: string;
targetOrgId: string;
};
// type putChnageOrgOwnerData = {
// orgId: string;
// tmbId: string;
// toAdmin?: boolean;
// };

View File

@@ -0,0 +1,12 @@
import { OrgSchemaType } from './type';
export const OrgCollectionName = 'team_orgs';
export const OrgMemberCollectionName = 'team_org_members';
export const getOrgChildrenPath = (org: OrgSchemaType) => `${org.path}/${org.pathId}`;
// export enum OrgMemberRole {
// owner = 'owner',
// admin = 'admin',
// member = 'member'
// }

View File

@@ -0,0 +1,25 @@
import type { TeamPermission } from 'support/permission/user/controller';
import { ResourcePermissionType } from '../type';
type OrgSchemaType = {
_id: string;
teamId: string;
pathId: string;
path: string;
name: string;
avatar?: string;
description?: string;
updateTime: Date;
};
type OrgMemberSchemaType = {
teamId: string;
orgId: string;
tmbId: string;
};
type OrgType = Omit<OrgSchemaType, 'avatar'> & {
avatar: string;
members: OrgMemberSchemaType[];
permission: TeamPermission;
};

View File

@@ -55,10 +55,11 @@ export type TeamMemberWithTeamAndUserSchema = TeamMemberSchema & {
export type TeamTmbItemType = { export type TeamTmbItemType = {
userId: string; userId: string;
teamId: string; teamId: string;
teamAvatar?: string;
teamName: string; teamName: string;
memberName: string; memberName: string;
avatar: string; avatar: string;
balance: number; balance?: number;
tmbId: string; tmbId: string;
teamDomain: string; teamDomain: string;
defaultTeam: boolean; defaultTeam: boolean;

View File

@@ -0,0 +1,16 @@
export const getRandomUserAvatar = () => {
const defaultAvatars = [
'/imgs/avatar/RoyalBlueAvatar.svg',
'/imgs/avatar/PurpleAvatar.svg',
'/imgs/avatar/AdoraAvatar.svg',
'/imgs/avatar/OrangeAvatar.svg',
'/imgs/avatar/RedAvatar.svg',
'/imgs/avatar/GrayModernAvatar.svg',
'/imgs/avatar/TealAvatar.svg',
'/imgs/avatar/GreenAvatar.svg',
'/imgs/avatar/BrightBlueAvatar.svg',
'/imgs/avatar/BlueAvatar.svg'
];
return defaultAvatars[Math.floor(Math.random() * defaultAvatars.length)];
};

View File

@@ -23,7 +23,8 @@ export type BillSchemaType = {
}; };
export type ChatNodeUsageType = { export type ChatNodeUsageType = {
tokens?: number; inputTokens?: number;
outputTokens?: number;
totalPoints: number; totalPoints: number;
moduleName: string; moduleName: string;
model?: string; model?: string;

View File

@@ -2,9 +2,13 @@ import { CreateUsageProps } from './api';
import { UsageSourceEnum } from './constants'; import { UsageSourceEnum } from './constants';
export type UsageListItemCountType = { export type UsageListItemCountType = {
tokens?: number; inputTokens?: number;
outputTokens?: number;
charsLength?: number; charsLength?: number;
duration?: number; duration?: number;
// deprecated
tokens?: number;
}; };
export type UsageListItemType = UsageListItemCountType & { export type UsageListItemType = UsageListItemCountType & {
moduleName: string; moduleName: string;

View File

@@ -35,24 +35,26 @@ export const list = [...staticPluginList, ...packagePluginList];
/* Get plugins */ /* Get plugins */
export const getCommunityPlugins = () => { export const getCommunityPlugins = () => {
return list.map<SystemPluginTemplateItemType>((name) => { return Promise.all(
const config = require(`./src/${name}/template.json`); list.map<Promise<SystemPluginTemplateItemType>>(async (name) => {
const config = (await import(`./src/${name}/template.json`))?.default;
const isFolder = list.find((item) => item.startsWith(`${name}/`)); const isFolder = list.find((item) => item.startsWith(`${name}/`));
const parentIdList = name.split('/').slice(0, -1); const parentIdList = name.split('/').slice(0, -1);
const parentId = const parentId =
parentIdList.length > 0 ? `${PluginSourceEnum.community}-${parentIdList.join('/')}` : null; parentIdList.length > 0 ? `${PluginSourceEnum.community}-${parentIdList.join('/')}` : null;
return { return {
...config, ...config,
id: `${PluginSourceEnum.community}-${name}`, id: `${PluginSourceEnum.community}-${name}`,
isFolder, isFolder,
parentId, parentId,
isActive: true, isActive: true,
isOfficial: true isOfficial: true
}; };
}); })
);
}; };
export const getSystemPluginTemplates = () => { export const getSystemPluginTemplates = () => {

View File

@@ -4,8 +4,8 @@ import { SystemPluginSpecialResponse } from '../../../type.d';
type Props = { type Props = {
title: string; title: string;
xAxis: string; xAxis: string[];
yAxis: string; yAxis: string[];
chartType: string; chartType: string;
}; };
@@ -27,7 +27,12 @@ type Option = {
series: SeriesData[]; // 使用定义的类型 series: SeriesData[]; // 使用定义的类型
}; };
const generateChart = async (title: string, xAxis: string, yAxis: string, chartType: string) => { const generateChart = async (
title: string,
xAxis: string[],
yAxis: string[],
chartType: string
) => {
// @ts-ignore 无法使用dom如使用jsdom会出现生成图片无法正常展示有高手可以帮忙解决 // @ts-ignore 无法使用dom如使用jsdom会出现生成图片无法正常展示有高手可以帮忙解决
const chart = echarts.init(undefined, undefined, { const chart = echarts.init(undefined, undefined, {
renderer: 'svg', // 必须使用 SVG 模式 renderer: 'svg', // 必须使用 SVG 模式
@@ -36,21 +41,11 @@ const generateChart = async (title: string, xAxis: string, yAxis: string, chartT
height: 300 height: 300
}); });
let parsedXAxis: string[] = [];
let parsedYAxis: number[] = [];
try {
parsedXAxis = json5.parse(xAxis);
parsedYAxis = json5.parse(yAxis);
} catch (error: any) {
console.error('解析数据时出错:', error);
return Promise.reject('Data error');
}
const option: Option = { const option: Option = {
backgroundColor: '#f5f5f5', backgroundColor: '#f5f5f5',
title: { text: title }, title: { text: title },
tooltip: {}, tooltip: {},
xAxis: { data: parsedXAxis }, xAxis: { data: xAxis },
yAxis: {}, yAxis: {},
series: [] // 初始化为空数组 series: [] // 初始化为空数组
}; };
@@ -58,18 +53,18 @@ const generateChart = async (title: string, xAxis: string, yAxis: string, chartT
// 根据 chartType 生成不同的图表 // 根据 chartType 生成不同的图表
switch (chartType) { switch (chartType) {
case '柱状图': case '柱状图':
option.series.push({ name: 'Sample', type: 'bar', data: parsedYAxis }); option.series.push({ name: 'Sample', type: 'bar', data: yAxis.map(Number) });
break; break;
case '折线图': case '折线图':
option.series.push({ name: 'Sample', type: 'line', data: parsedYAxis }); option.series.push({ name: 'Sample', type: 'line', data: yAxis.map(Number) });
break; break;
case '饼图': case '饼图':
option.series.push({ option.series.push({
name: 'Sample', name: 'Sample',
type: 'pie', type: 'pie',
data: parsedYAxis.map((value, index) => ({ data: yAxis.map((value, index) => ({
value, value: Number(value),
name: parsedXAxis[index] // 使用 xAxis 作为饼图的名称 name: xAxis[index] // 使用 xAxis 作为饼图的名称
})) }))
}); });
break; break;

View File

@@ -1,6 +1,6 @@
{ {
"author": "silencezhang", "author": "silencezhang",
"version": "4812", "version": "4817",
"name": "基础图表", "name": "基础图表",
"avatar": "core/workflow/template/baseChart", "avatar": "core/workflow/template/baseChart",
"intro": "根据数据生成图表可根据chartType生成柱状图折线图饼图", "intro": "根据数据生成图表可根据chartType生成柱状图折线图饼图",
@@ -68,7 +68,7 @@
"canEdit": true, "canEdit": true,
"key": "yAxis", "key": "yAxis",
"label": "yAxis", "label": "yAxis",
"description": "y轴数据例如['1', '2', '3']", "description": "y轴数据例如[1,2,3]",
"defaultValue": "", "defaultValue": "",
"list": [ "list": [
{ {
@@ -77,7 +77,7 @@
} }
], ],
"required": true, "required": true,
"toolDescription": "y轴数据例如['1', '2', '3']" "toolDescription": "y轴数据例如[1,2,3]"
}, },
{ {
"renderTypeList": ["select", "reference"], "renderTypeList": ["select", "reference"],
@@ -145,8 +145,8 @@
"flowNodeType": "pluginOutput", "flowNodeType": "pluginOutput",
"showStatus": false, "showStatus": false,
"position": { "position": {
"x": 2122.252754006148, "x": 2128.8138851197145,
"y": -63.5218674613718 "y": -63.52186746137181
}, },
"version": "481", "version": "481",
"inputs": [ "inputs": [
@@ -154,10 +154,12 @@
"renderTypeList": ["reference"], "renderTypeList": ["reference"],
"valueType": "string", "valueType": "string",
"canEdit": true, "canEdit": true,
"key": "相对路径URL", "key": "图表 url",
"label": "相对路径URL", "label": "图表 url",
"description": "可用使用markdown格式展示图片![图片](url)", "description": "可用使用markdown格式展示图片![图片](url)",
"value": ["ws0DFKJnCPhk", "bzaYjKyQFOw2"] "value": ["ws0DFKJnCPhk", "bzaYjKyQFOw2"],
"isToolOutput": true,
"required": true
} }
], ],
"outputs": [] "outputs": []
@@ -170,8 +172,8 @@
"flowNodeType": "httpRequest468", "flowNodeType": "httpRequest468",
"showStatus": true, "showStatus": true,
"position": { "position": {
"x": 1216.5166647574395, "x": 1264.2009472531117,
"y": -206.30162946606856 "y": -455.0773486762623
}, },
"version": "481", "version": "481",
"inputs": [ "inputs": [
@@ -275,7 +277,7 @@
"key": "system_httpJsonBody", "key": "system_httpJsonBody",
"renderTypeList": ["hidden"], "renderTypeList": ["hidden"],
"valueType": "any", "valueType": "any",
"value": "{\r\n \"title\": \"{{title-plugin}}\",\r\n \"xAxis\": \"{{xAxis-plugin}}\",\r\n \"yAxis\": \"{{yAxis-plugin}}\",\r\n \"chartType\": \"{{chartType-plugin}}\"\r\n}", "value": "{\r\n \"title\": \"{{$pluginInput.title$}}\",\r\n \"xAxis\": {{$pluginInput.xAxis$}},\r\n \"yAxis\": {{$pluginInput.yAxis$}},\r\n \"chartType\": \"{{$pluginInput.chartType$}}\"\r\n}",
"label": "", "label": "",
"required": false, "required": false,
"valueDesc": "", "valueDesc": "",
@@ -306,126 +308,6 @@
"description": "", "description": "",
"debugLabel": "", "debugLabel": "",
"toolDescription": "" "toolDescription": ""
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "title-plugin",
"label": "title-plugin",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "title"]
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "xAxis-plugin",
"label": "xAxis-plugin",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "xAxis"]
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "yAxis-plugin",
"label": "yAxis-plugin",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "yAxis"]
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "chartType-plugin",
"label": "chartType-plugin",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"arrayAny",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "chartType"]
} }
], ],
"outputs": [ "outputs": [

View File

@@ -2,7 +2,7 @@
"author": "", "author": "",
"version": "488", "version": "488",
"name": "飞书 webhook", "name": "飞书 webhook",
"avatar": "/appMarketTemplates/plugin-feishu/avatar.svg", "avatar": "core/app/templates/plugin-feishu",
"intro": "向飞书机器人发起 webhook 请求。", "intro": "向飞书机器人发起 webhook 请求。",
"courseUrl": "https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot#f62e72d5", "courseUrl": "https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot#f62e72d5",
"showStatus": false, "showStatus": false,

View File

@@ -48,6 +48,16 @@ const main = async (props: Props, retry = 3): Response => {
}); });
}); });
if (results.length === 0) {
return {
result: JSON.stringify([]),
error: {
message: 'No search results',
code: 500
}
};
}
return { return {
result: JSON.stringify(results.slice(0, 10)) result: JSON.stringify(results.slice(0, 10))
}; };

View File

@@ -4,7 +4,7 @@ import fsp from 'fs/promises';
import fs from 'fs'; import fs from 'fs';
import { DatasetFileSchema } from '@fastgpt/global/core/dataset/type'; import { DatasetFileSchema } from '@fastgpt/global/core/dataset/type';
import { MongoChatFileSchema, MongoDatasetFileSchema } from './schema'; import { MongoChatFileSchema, MongoDatasetFileSchema } from './schema';
import { detectFileEncoding } from '@fastgpt/global/common/file/tools'; import { detectFileEncoding, detectFileEncodingByPath } from '@fastgpt/global/common/file/tools';
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common'; import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { MongoRawTextBuffer } from '../../buffer/rawText/schema'; import { MongoRawTextBuffer } from '../../buffer/rawText/schema';
import { readRawContentByFileBuffer } from '../read/utils'; import { readRawContentByFileBuffer } from '../read/utils';
@@ -36,7 +36,6 @@ export async function uploadFile({
path, path,
filename, filename,
contentType, contentType,
encoding,
metadata = {} metadata = {}
}: { }: {
bucketName: `${BucketNameEnum}`; bucketName: `${BucketNameEnum}`;
@@ -45,7 +44,6 @@ export async function uploadFile({
path: string; path: string;
filename: string; filename: string;
contentType?: string; contentType?: string;
encoding: string;
metadata?: Record<string, any>; metadata?: Record<string, any>;
}) { }) {
if (!path) return Promise.reject(`filePath is empty`); if (!path) return Promise.reject(`filePath is empty`);
@@ -59,7 +57,7 @@ export async function uploadFile({
// Add default metadata // Add default metadata
metadata.teamId = teamId; metadata.teamId = teamId;
metadata.uid = uid; metadata.uid = uid;
metadata.encoding = encoding; metadata.encoding = await detectFileEncodingByPath(path);
// create a gridfs bucket // create a gridfs bucket
const bucket = getGridBucket(bucketName); const bucket = getGridBucket(bucketName);

View File

@@ -1,43 +1,92 @@
import { UploadImgProps } from '@fastgpt/global/common/file/api'; import { UploadImgProps } from '@fastgpt/global/common/file/api';
import { imageBaseUrl } from '@fastgpt/global/common/file/image/constants'; import { imageBaseUrl } from '@fastgpt/global/common/file/image/constants';
import { MongoImage } from './schema'; import { MongoImage } from './schema';
import { ClientSession } from '../../../common/mongo'; import { ClientSession, Types } from '../../../common/mongo';
import { guessBase64ImageType } from '../utils'; import { guessBase64ImageType } from '../utils';
import { readFromSecondary } from '../../mongo/utils'; import { readFromSecondary } from '../../mongo/utils';
import { addHours } from 'date-fns';
export const maxImgSize = 1024 * 1024 * 12; export const maxImgSize = 1024 * 1024 * 12;
const base64MimeRegex = /data:image\/([^\)]+);base64/; const base64MimeRegex = /data:image\/([^\)]+);base64/;
export async function uploadMongoImg({ export async function uploadMongoImg({
type,
base64Img, base64Img,
teamId, teamId,
expiredTime,
metadata, metadata,
shareId shareId,
forever = false
}: UploadImgProps & { }: UploadImgProps & {
teamId: string; teamId: string;
forever?: Boolean;
}) { }) {
if (base64Img.length > maxImgSize) { if (base64Img.length > maxImgSize) {
return Promise.reject('Image too large'); return Promise.reject('Image too large');
} }
const [base64Mime, base64Data] = base64Img.split(','); const [base64Mime, base64Data] = base64Img.split(',');
// Check if mime type is valid
if (!base64MimeRegex.test(base64Mime)) {
return Promise.reject('Invalid image mime type');
}
const mime = `image/${base64Mime.match(base64MimeRegex)?.[1] ?? 'image/jpeg'}`; const mime = `image/${base64Mime.match(base64MimeRegex)?.[1] ?? 'image/jpeg'}`;
const binary = Buffer.from(base64Data, 'base64'); const binary = Buffer.from(base64Data, 'base64');
const extension = mime.split('/')[1]; const extension = mime.split('/')[1];
const { _id } = await MongoImage.create({ const { _id } = await MongoImage.create({
type,
teamId, teamId,
binary, binary,
expiredTime,
metadata: Object.assign({ mime }, metadata), metadata: Object.assign({ mime }, metadata),
shareId shareId,
expiredTime: forever ? undefined : addHours(new Date(), 1)
}); });
return `${process.env.FE_DOMAIN || ''}${process.env.NEXT_PUBLIC_BASE_URL || ''}${imageBaseUrl}${String(_id)}.${extension}`; return `${process.env.FE_DOMAIN || ''}${process.env.NEXT_PUBLIC_BASE_URL || ''}${imageBaseUrl}${String(_id)}.${extension}`;
} }
const getIdFromPath = (path?: string) => {
if (!path) return;
const paths = path.split('/');
const name = paths[paths.length - 1];
if (!name) return;
const id = name.split('.')[0];
if (!id || !Types.ObjectId.isValid(id)) return;
return id;
};
// 删除旧的头像,新的头像去除过期时间
export const refreshSourceAvatar = async (
path?: string,
oldPath?: string,
session?: ClientSession
) => {
const newId = getIdFromPath(path);
const oldId = getIdFromPath(oldPath);
if (!newId) return;
await MongoImage.updateOne({ _id: newId }, { $unset: { expiredTime: 1 } }, { session });
if (oldId) {
await MongoImage.deleteOne({ _id: oldId }, { session });
}
};
export const removeImageByPath = (path?: string, session?: ClientSession) => {
if (!path) return;
const paths = path.split('/');
const name = paths[paths.length - 1];
if (!name) return;
const id = name.split('.')[0];
if (!id || !Types.ObjectId.isValid(id)) return;
return MongoImage.deleteOne({ _id: id }, { session });
};
export async function readMongoImg({ id }: { id: string }) { export async function readMongoImg({ id }: { id: string }) {
const formatId = id.replace(/\.[^/.]+$/, ''); const formatId = id.replace(/\.[^/.]+$/, '');

View File

@@ -1,8 +1,7 @@
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant'; import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
import { connectionMongo, getMongoModel, type Model } from '../../mongo'; import { connectionMongo, getMongoModel } from '../../mongo';
import { MongoImageSchemaType } from '@fastgpt/global/common/file/image/type.d'; import { MongoImageSchemaType } from '@fastgpt/global/common/file/image/type.d';
import { mongoImageTypeMap } from '@fastgpt/global/common/file/image/constants'; const { Schema } = connectionMongo;
const { Schema, model, models } = connectionMongo;
const ImageSchema = new Schema({ const ImageSchema = new Schema({
teamId: { teamId: {
@@ -14,27 +13,15 @@ const ImageSchema = new Schema({
type: Date, type: Date,
default: () => new Date() default: () => new Date()
}, },
expiredTime: { expiredTime: Date,
type: Date binary: Buffer,
}, metadata: Object
binary: {
type: Buffer
},
type: {
type: String,
enum: Object.keys(mongoImageTypeMap),
required: true
},
metadata: {
type: Object
}
}); });
try { try {
// tts expired60 Minutes // tts expired60 Minutes
ImageSchema.index({ expiredTime: 1 }, { expireAfterSeconds: 60 * 60 }); ImageSchema.index({ expiredTime: 1 }, { expireAfterSeconds: 60 * 60 });
ImageSchema.index({ type: 1 }); ImageSchema.index({ type: 1 });
ImageSchema.index({ createTime: 1 });
// delete related img // delete related img
ImageSchema.index({ teamId: 1, 'metadata.relatedId': 1 }); ImageSchema.index({ teamId: 1, 'metadata.relatedId': 1 });
} catch (error) { } catch (error) {

View File

@@ -1,5 +1,4 @@
import { uploadMongoImg } from '../image/controller'; import { uploadMongoImg } from '../image/controller';
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
import FormData from 'form-data'; import FormData from 'form-data';
import { WorkerNameEnum, runWorker } from '../../../worker/utils'; import { WorkerNameEnum, runWorker } from '../../../worker/utils';
@@ -8,7 +7,6 @@ import type { ReadFileResponse } from '../../../worker/readFile/type';
import axios from 'axios'; import axios from 'axios';
import { addLog } from '../../system/log'; import { addLog } from '../../system/log';
import { batchRun } from '@fastgpt/global/common/fn/utils'; import { batchRun } from '@fastgpt/global/common/fn/utils';
import { addHours } from 'date-fns';
import { matchMdImgTextAndUpload } from '@fastgpt/global/common/string/markdown'; import { matchMdImgTextAndUpload } from '@fastgpt/global/common/string/markdown';
export type readRawTextByLocalFileParams = { export type readRawTextByLocalFileParams = {
@@ -22,7 +20,7 @@ export const readRawTextByLocalFile = async (params: readRawTextByLocalFileParam
const extension = path?.split('.')?.pop()?.toLowerCase() || ''; const extension = path?.split('.')?.pop()?.toLowerCase() || '';
const buffer = fs.readFileSync(path); const buffer = await fs.promises.readFile(path);
const { rawText } = await readRawContentByFileBuffer({ const { rawText } = await readRawContentByFileBuffer({
extension, extension,
@@ -114,10 +112,9 @@ export const readRawContentByFileBuffer = async ({
if (imageList) { if (imageList) {
await batchRun(imageList, async (item) => { await batchRun(imageList, async (item) => {
const src = await uploadMongoImg({ const src = await uploadMongoImg({
type: MongoImageTypeEnum.collectionImage,
base64Img: `data:${item.mime};base64,${item.base64}`, base64Img: `data:${item.mime};base64,${item.base64}`,
teamId, teamId,
expiredTime: addHours(new Date(), 1), // expiredTime: addHours(new Date(), 1),
metadata: { metadata: {
...metadata, ...metadata,
mime: item.mime mime: item.mime

View File

@@ -3,10 +3,13 @@ import NextCors from 'nextjs-cors';
export async function withNextCors(req: NextApiRequest, res: NextApiResponse) { export async function withNextCors(req: NextApiRequest, res: NextApiResponse) {
const methods = ['GET', 'eHEAD', 'PUT', 'PATCH', 'POST', 'DELETE']; const methods = ['GET', 'eHEAD', 'PUT', 'PATCH', 'POST', 'DELETE'];
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',');
const origin = req.headers.origin; const origin = req.headers.origin;
await NextCors(req, res, { await NextCors(req, res, {
methods, methods,
origin: origin, origin: allowedOrigins || origin,
optionsSuccessStatus: 200 optionsSuccessStatus: 200
}); });
} }

View File

@@ -9,10 +9,10 @@ import { jsonRes } from '../response';
// unit: times/s // unit: times/s
// how to use? // how to use?
// export default NextAPI(useQPSLimit(10), handler); // limit 10 times per second for a ip // export default NextAPI(useQPSLimit(10), handler); // limit 10 times per second for a ip
export function useReqFrequencyLimit(seconds: number, limit: number) { export function useReqFrequencyLimit(seconds: number, limit: number, force = false) {
return async (req: ApiRequestProps, res: NextApiResponse) => { return async (req: ApiRequestProps, res: NextApiResponse) => {
const ip = requestIp.getClientIp(req); const ip = requestIp.getClientIp(req);
if (!ip || process.env.USE_IP_LIMIT !== 'true') { if (!ip || (process.env.USE_IP_LIMIT !== 'true' && !force)) {
return; return;
} }
try { try {
@@ -22,10 +22,9 @@ export function useReqFrequencyLimit(seconds: number, limit: number) {
expiredTime: addSeconds(new Date(), seconds) expiredTime: addSeconds(new Date(), seconds)
}); });
} catch (_) { } catch (_) {
res.status(429);
jsonRes(res, { jsonRes(res, {
code: 429, code: 429,
message: ERROR_ENUM.tooManyRequest error: ERROR_ENUM.tooManyRequest
}); });
} }
}; };

View File

@@ -33,7 +33,7 @@ export const jsonRes = <T = any>(
addLog.error(`Api response error: ${url}`, ERROR_RESPONSE[errResponseKey]); addLog.error(`Api response error: ${url}`, ERROR_RESPONSE[errResponseKey]);
return res.json(ERROR_RESPONSE[errResponseKey]); return res.status(code).json(ERROR_RESPONSE[errResponseKey]);
} }
// another error // another error

View File

@@ -15,6 +15,9 @@ export const initFastGPTConfig = (config?: FastGPTConfigFileType) => {
global.subPlans = config.subPlans; global.subPlans = config.subPlans;
global.llmModels = config.llmModels; global.llmModels = config.llmModels;
global.llmModelPriceType = global.llmModels.some((item) => typeof item.inputPrice === 'number')
? 'IO'
: 'Tokens';
global.vectorModels = config.vectorModels; global.vectorModels = config.vectorModels;
global.audioSpeechModels = config.audioSpeechModels; global.audioSpeechModels = config.audioSpeechModels;
global.whisperModel = config.whisperModel; global.whisperModel = config.whisperModel;

View File

@@ -0,0 +1,11 @@
{
"provider": "OpenAI",
"model": "text-embedding-ada-002",
"name": "text-embedding-ada-002",
"defaultToken": 512, // 默认分块 token
"maxToken": 3000, // 最大分块 token
"weight": 0, // 权重
"charsPointsPrice": 0 // 积分/1k token
}

View File

@@ -0,0 +1,33 @@
{
"provider": "OpenAI",
"model": "gpt-4o-mini",
"name": "GPT-4o-mini", // alias
"maxContext": 125000, // 最大上下文
"maxResponse": 16000, // 最大回复
"quoteMaxToken": 60000, // 最大引用
"maxTemperature": 1.2, // 最大温度
"presencePenaltyRange": [-2, 2], // 惩罚系数范围
"frequencyPenaltyRange": [-2, 2], // 频率惩罚系数范围
"responseFormatList": ["text", "json_object", "json_schema"], // 响应格式
"showStopSign": true, // 是否显示停止符号
"vision": true, // 是否支持图片识别
"toolChoice": true, // 是否支持工具调用
"functionCall": false, // 是否支持函数调用(一般都可以 false 了,基本不用了)
"defaultSystemChatPrompt": "", // 默认系统提示
"datasetProcess": true, // 用于知识库文本处理
"usedInClassify": true, // 用于问题分类
"customCQPrompt": "", // 自定义问题分类提示
"usedInExtractFields": true, // 用于提取字段
"customExtractPrompt": "", // 自定义提取提示
"usedInToolCall": true, // 用于工具调用
"usedInQueryExtension": true, // 用于问题优化
"defaultConfig": {}, // 额外的自定义 body
"fieldMap": {}, // body 字段映射
"censor": false, // 是否开启敏感词过滤
"charsPointsPrice": 0 // n 积分/1k token
}

View File

@@ -0,0 +1,6 @@
{
"provider": "BAAI",
"model": "bge-reranker-v2-m3",
"name": "bge-reranker-v2-m3",
"charsPointsPrice": 0
}

View File

@@ -0,0 +1,6 @@
{
"provider": "OpenAI",
"model": "whisper-1",
"name": "whisper-1",
"charsPointsPrice": 0
}

View File

@@ -0,0 +1,32 @@
{
"provider": "OpenAI",
"model": "tts-1",
"name": "TTS1",
"charsPointsPrice": 0,
"voices": [
{
"label": "Alloy",
"value": "alloy"
},
{
"label": "Echo",
"value": "echo"
},
{
"label": "Fable",
"value": "fable"
},
{
"label": "Onyx",
"value": "onyx"
},
{
"label": "Nova",
"value": "nova"
},
{
"label": "Shimmer",
"value": "shimmer"
}
]
}

View File

@@ -1,6 +1,6 @@
import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d'; import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d';
import { createChatCompletion } from '../config'; import { createChatCompletion } from '../config';
import { countGptMessagesTokens } from '../../../common/string/tiktoken/index'; import { countGptMessagesTokens, countPromptTokens } from '../../../common/string/tiktoken/index';
import { loadRequestMessages } from '../../chat/utils'; import { loadRequestMessages } from '../../chat/utils';
import { llmCompletionsBodyFormat } from '../utils'; import { llmCompletionsBodyFormat } from '../utils';
import { import {
@@ -20,7 +20,8 @@ export async function createQuestionGuide({
customPrompt?: string; customPrompt?: string;
}): Promise<{ }): Promise<{
result: string[]; result: string[];
tokens: number; inputTokens: number;
outputTokens: number;
}> { }> {
const concatMessages: ChatCompletionMessageParam[] = [ const concatMessages: ChatCompletionMessageParam[] = [
...messages, ...messages,
@@ -29,6 +30,10 @@ export async function createQuestionGuide({
content: `${customPrompt || PROMPT_QUESTION_GUIDE}\n${PROMPT_QUESTION_GUIDE_FOOTER}` content: `${customPrompt || PROMPT_QUESTION_GUIDE}\n${PROMPT_QUESTION_GUIDE_FOOTER}`
} }
]; ];
const requestMessages = await loadRequestMessages({
messages: concatMessages,
useVision: false
});
const { response: data } = await createChatCompletion({ const { response: data } = await createChatCompletion({
body: llmCompletionsBodyFormat( body: llmCompletionsBodyFormat(
@@ -36,10 +41,7 @@ export async function createQuestionGuide({
model, model,
temperature: 0.1, temperature: 0.1,
max_tokens: 200, max_tokens: 200,
messages: await loadRequestMessages({ messages: requestMessages,
messages: concatMessages,
useVision: false
}),
stream: false stream: false
}, },
model model
@@ -51,13 +53,15 @@ export async function createQuestionGuide({
const start = answer.indexOf('['); const start = answer.indexOf('[');
const end = answer.lastIndexOf(']'); const end = answer.lastIndexOf(']');
const tokens = await countGptMessagesTokens(concatMessages); const inputTokens = await countGptMessagesTokens(requestMessages);
const outputTokens = await countPromptTokens(answer);
if (start === -1 || end === -1) { if (start === -1 || end === -1) {
addLog.warn('Create question guide error', { answer }); addLog.warn('Create question guide error', { answer });
return { return {
result: [], result: [],
tokens: 0 inputTokens: 0,
outputTokens: 0
}; };
} }
@@ -69,14 +73,16 @@ export async function createQuestionGuide({
try { try {
return { return {
result: json5.parse(jsonStr), result: json5.parse(jsonStr),
tokens inputTokens,
outputTokens
}; };
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return { return {
result: [], result: [],
tokens: 0 inputTokens: 0,
outputTokens: 0
}; };
} }
} }

View File

@@ -1,7 +1,7 @@
import { replaceVariable } from '@fastgpt/global/common/string/tools'; import { replaceVariable } from '@fastgpt/global/common/string/tools';
import { createChatCompletion } from '../config'; import { createChatCompletion } from '../config';
import { ChatItemType } from '@fastgpt/global/core/chat/type'; import { ChatItemType } from '@fastgpt/global/core/chat/type';
import { countGptMessagesTokens } from '../../../common/string/tiktoken/index'; import { countGptMessagesTokens, countPromptTokens } from '../../../common/string/tiktoken/index';
import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt'; import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
import { getLLMModel } from '../model'; import { getLLMModel } from '../model';
import { llmCompletionsBodyFormat } from '../utils'; import { llmCompletionsBodyFormat } from '../utils';
@@ -121,7 +121,8 @@ export const queryExtension = async ({
rawQuery: string; rawQuery: string;
extensionQueries: string[]; extensionQueries: string[];
model: string; model: string;
tokens: number; inputTokens: number;
outputTokens: number;
}> => { }> => {
const systemFewShot = chatBg const systemFewShot = chatBg
? `Q: 对话背景。 ? `Q: 对话背景。
@@ -166,7 +167,8 @@ A: ${chatBg}
rawQuery: query, rawQuery: query,
extensionQueries: [], extensionQueries: [],
model, model,
tokens: 0 inputTokens: 0,
outputTokens: 0
}; };
} }
@@ -181,7 +183,8 @@ A: ${chatBg}
rawQuery: query, rawQuery: query,
extensionQueries: Array.isArray(queries) ? queries : [], extensionQueries: Array.isArray(queries) ? queries : [],
model, model,
tokens: await countGptMessagesTokens(messages) inputTokens: await countGptMessagesTokens(messages),
outputTokens: await countPromptTokens(answer)
}; };
} catch (error) { } catch (error) {
addLog.error(`Query extension error`, error); addLog.error(`Query extension error`, error);
@@ -189,7 +192,8 @@ A: ${chatBg}
rawQuery: query, rawQuery: query,
extensionQueries: [], extensionQueries: [],
model, model,
tokens: 0 inputTokens: 0,
outputTokens: 0
}; };
} }
}; };

View File

@@ -4,6 +4,7 @@ export const getLLMModel = (model?: string) => {
global.llmModels[0] global.llmModels[0]
); );
}; };
export const getDatasetModel = (model?: string) => { export const getDatasetModel = (model?: string) => {
return ( return (
global.llmModels global.llmModels

View File

@@ -1,11 +1,15 @@
import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type'; import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
import { PluginRuntimeType } from '@fastgpt/global/core/plugin/type'; import { PluginRuntimeType } from '@fastgpt/global/core/plugin/type';
import { splitCombinePluginId } from './controller';
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
/* /*
Plugin points calculation: Plugin points calculation:
1. Return 0 if error 1. 系统插件/商业版插件:
2. Add configured points if commercial plugin - 有错误:返回 0
3. Add sum of child nodes points - 无错误:返回 单次积分 + 子流程积分(可配置)
2. 个人插件
- 返回 子流程积分
*/ */
export const computedPluginUsage = async ({ export const computedPluginUsage = async ({
plugin, plugin,
@@ -16,13 +20,16 @@ export const computedPluginUsage = async ({
childrenUsage: ChatNodeUsageType[]; childrenUsage: ChatNodeUsageType[];
error?: boolean; error?: boolean;
}) => { }) => {
if (error) { const { source } = await splitCombinePluginId(plugin.id);
return 0; const childrenUsages = childrenUsage.reduce((sum, item) => sum + (item.totalPoints || 0), 0);
if (source !== PluginSourceEnum.personal) {
if (error) return 0;
const pluginCurrentCost = plugin.currentCost ?? 0;
return plugin.hasTokenFee ? pluginCurrentCost + childrenUsages : pluginCurrentCost;
} }
const childrenIUsages = childrenUsage.reduce((sum, item) => sum + (item.totalPoints || 0), 0); return childrenUsages;
const pluginCurrentCose = plugin.currentCost ?? 0;
return plugin.hasTokenFee ? pluginCurrentCose + childrenIUsages : pluginCurrentCose;
}; };

View File

@@ -86,24 +86,21 @@ const ChatItemSchema = new Schema({
}); });
try { try {
ChatItemSchema.index({ dataId: 1 }, { background: true }); ChatItemSchema.index({ dataId: 1 });
/* delete by app; /* delete by app;
delete by chat id; delete by chat id;
get chat list; get chat list;
get chat logs; get chat logs;
close custom feedback; close custom feedback;
*/ */
ChatItemSchema.index({ appId: 1, chatId: 1, dataId: 1 }, { background: true }); ChatItemSchema.index({ appId: 1, chatId: 1, dataId: 1 });
// admin charts // admin charts
ChatItemSchema.index({ time: -1, obj: 1 }, { background: true }); ChatItemSchema.index({ time: -1, obj: 1 });
// timer, clear history // timer, clear history
ChatItemSchema.index({ teamId: 1, time: -1 }, { background: true }); ChatItemSchema.index({ teamId: 1, time: -1 });
// Admin charts // Admin charts
ChatItemSchema.index( ChatItemSchema.index({ obj: 1, time: -1 }, { partialFilterExpression: { obj: 'Human' } });
{ obj: 1, time: -1 },
{ background: true, partialFilterExpression: { obj: 'Human' } }
);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }

View File

@@ -81,19 +81,19 @@ const ChatSchema = new Schema({
}); });
try { try {
ChatSchema.index({ chatId: 1 }, { background: true }); ChatSchema.index({ chatId: 1 });
// get user history // get user history
ChatSchema.index({ tmbId: 1, appId: 1, top: -1, updateTime: -1 }, { background: true }); ChatSchema.index({ tmbId: 1, appId: 1, top: -1, updateTime: -1 });
// delete by appid; clear history; init chat; update chat; auth chat; get chat; // delete by appid; clear history; init chat; update chat; auth chat; get chat;
ChatSchema.index({ appId: 1, chatId: 1 }, { background: true }); ChatSchema.index({ appId: 1, chatId: 1 });
// get chat logs; // get chat logs;
ChatSchema.index({ teamId: 1, appId: 1, updateTime: -1 }, { background: true }); ChatSchema.index({ teamId: 1, appId: 1, updateTime: -1 });
// get share chat history // get share chat history
ChatSchema.index({ shareId: 1, outLinkUid: 1, updateTime: -1 }, { background: true }); ChatSchema.index({ shareId: 1, outLinkUid: 1, updateTime: -1 });
// timer, clear history // timer, clear history
ChatSchema.index({ teamId: 1, updateTime: -1 }, { background: true }); ChatSchema.index({ teamId: 1, updateTime: -1 });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }

View File

@@ -24,6 +24,7 @@ import { pushDataListToTrainingQueue } from '../training/controller';
import { MongoImage } from '../../../common/file/image/schema'; import { MongoImage } from '../../../common/file/image/schema';
import { hashStr } from '@fastgpt/global/common/string/tools'; import { hashStr } from '@fastgpt/global/common/string/tools';
import { addDays } from 'date-fns'; import { addDays } from 'date-fns';
import { MongoDatasetDataText } from '../data/dataTextSchema';
export const createCollectionAndInsertData = async ({ export const createCollectionAndInsertData = async ({
dataset, dataset,
@@ -240,12 +241,12 @@ export const delCollectionRelatedSource = async ({
.map((item) => item?.metadata?.relatedImgId || '') .map((item) => item?.metadata?.relatedImgId || '')
.filter(Boolean); .filter(Boolean);
// delete files // Delete files
await delFileByFileIdList({ await delFileByFileIdList({
bucketName: BucketNameEnum.dataset, bucketName: BucketNameEnum.dataset,
fileIdList fileIdList
}); });
// delete images // Delete images
await delImgByRelatedId({ await delImgByRelatedId({
teamId, teamId,
relateIds: relatedImageIds, relateIds: relatedImageIds,
@@ -273,7 +274,7 @@ export async function delCollection({
const datasetIds = Array.from(new Set(collections.map((item) => String(item.datasetId)))); const datasetIds = Array.from(new Set(collections.map((item) => String(item.datasetId))));
const collectionIds = collections.map((item) => String(item._id)); const collectionIds = collections.map((item) => String(item._id));
// delete training data // Delete training data
await MongoDatasetTraining.deleteMany({ await MongoDatasetTraining.deleteMany({
teamId, teamId,
datasetIds: { $in: datasetIds }, datasetIds: { $in: datasetIds },
@@ -285,11 +286,16 @@ export async function delCollection({
await delCollectionRelatedSource({ collections, session }); await delCollectionRelatedSource({ collections, session });
} }
// delete dataset.datas // Delete dataset_datas
await MongoDatasetData.deleteMany( await MongoDatasetData.deleteMany(
{ teamId, datasetIds: { $in: datasetIds }, collectionId: { $in: collectionIds } }, { teamId, datasetIds: { $in: datasetIds }, collectionId: { $in: collectionIds } },
{ session } { session }
); );
// Delete dataset_data_texts
await MongoDatasetDataText.deleteMany(
{ teamId, datasetIds: { $in: datasetIds }, collectionId: { $in: collectionIds } },
{ session }
);
// delete collections // delete collections
await MongoDatasetCollection.deleteMany( await MongoDatasetCollection.deleteMany(

View File

@@ -6,6 +6,7 @@ import { ClientSession } from '../../common/mongo';
import { MongoDatasetTraining } from './training/schema'; import { MongoDatasetTraining } from './training/schema';
import { MongoDatasetData } from './data/schema'; import { MongoDatasetData } from './data/schema';
import { deleteDatasetDataVector } from '../../common/vectorStore/controller'; import { deleteDatasetDataVector } from '../../common/vectorStore/controller';
import { MongoDatasetDataText } from './data/dataTextSchema';
/* ============= dataset ========== */ /* ============= dataset ========== */
/* find all datasetId by top datasetId */ /* find all datasetId by top datasetId */
@@ -92,7 +93,7 @@ export async function delDatasetRelevantData({
{ session } { session }
).lean(); ).lean();
// image and file // Delete Image and file
await delCollectionRelatedSource({ collections, session }); await delCollectionRelatedSource({ collections, session });
// delete collections // delete collections
@@ -101,9 +102,15 @@ export async function delDatasetRelevantData({
datasetId: { $in: datasetIds } datasetId: { $in: datasetIds }
}).session(session); }).session(session);
// delete dataset.datas(Not need session) // No session delete:
// Delete dataset_data_texts
await MongoDatasetDataText.deleteMany({
teamId,
datasetId: { $in: datasetIds }
});
// delete dataset_datas
await MongoDatasetData.deleteMany({ teamId, datasetId: { $in: datasetIds } }); await MongoDatasetData.deleteMany({ teamId, datasetId: { $in: datasetIds } });
// no session delete: delete files, vector data // Delete vector data
await deleteDatasetDataVector({ teamId, datasetIds }); await deleteDatasetDataVector({ teamId, datasetIds });
} }

View File

@@ -0,0 +1,45 @@
import { connectionMongo, getMongoModel } from '../../../common/mongo';
const { Schema } = connectionMongo;
import { DatasetDataSchemaType } from '@fastgpt/global/core/dataset/type.d';
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
import { DatasetCollectionName } from '../schema';
import { DatasetColCollectionName } from '../collection/schema';
import { DatasetDataCollectionName } from './schema';
export const DatasetDataTextCollectionName = 'dataset_data_texts';
const DatasetDataTextSchema = new Schema({
teamId: {
type: Schema.Types.ObjectId,
ref: TeamCollectionName,
required: true
},
datasetId: {
type: Schema.Types.ObjectId,
ref: DatasetCollectionName,
required: true
},
collectionId: {
type: Schema.Types.ObjectId,
ref: DatasetColCollectionName,
required: true
},
dataId: {
type: Schema.Types.ObjectId,
ref: DatasetDataCollectionName,
required: true
},
fullTextToken: String
});
try {
DatasetDataTextSchema.index({ teamId: 1, datasetId: 1, fullTextToken: 'text' });
DatasetDataTextSchema.index({ dataId: 1 }, { unique: true });
} catch (error) {
console.log(error);
}
export const MongoDatasetDataText = getMongoModel<DatasetDataSchemaType>(
DatasetDataTextCollectionName,
DatasetDataTextSchema
);

View File

@@ -1,4 +1,4 @@
import { connectionMongo, getMongoModel, type Model } from '../../../common/mongo'; import { connectionMongo, getMongoModel } from '../../../common/mongo';
const { Schema, model, models } = connectionMongo; const { Schema, model, models } = connectionMongo;
import { DatasetDataSchemaType } from '@fastgpt/global/core/dataset/type.d'; import { DatasetDataSchemaType } from '@fastgpt/global/core/dataset/type.d';
import { import {
@@ -39,10 +39,6 @@ const DatasetDataSchema = new Schema({
type: String, type: String,
default: '' default: ''
}, },
fullTextToken: {
type: String,
default: ''
},
indexes: { indexes: {
type: [ type: [
{ {
@@ -71,17 +67,11 @@ const DatasetDataSchema = new Schema({
type: Number, type: Number,
default: 0 default: 0
}, },
inited: { rebuilding: Boolean,
type: Boolean
},
rebuilding: Boolean
});
DatasetDataSchema.virtual('collection', { // Abandon
ref: DatasetColCollectionName, fullTextToken: String,
localField: 'collectionId', initFullText: Boolean
foreignField: '_id',
justOne: true
}); });
try { try {
@@ -93,13 +83,15 @@ try {
chunkIndex: 1, chunkIndex: 1,
updateTime: -1 updateTime: -1
}); });
// full text index // FullText tmp full text index
DatasetDataSchema.index({ teamId: 1, datasetId: 1, fullTextToken: 'text' }); // DatasetDataSchema.index({ teamId: 1, datasetId: 1, fullTextToken: 'text' });
// Recall vectors after data matching // Recall vectors after data matching
DatasetDataSchema.index({ teamId: 1, datasetId: 1, collectionId: 1, 'indexes.dataId': 1 }); DatasetDataSchema.index({ teamId: 1, datasetId: 1, collectionId: 1, 'indexes.dataId': 1 });
DatasetDataSchema.index({ updateTime: 1 }); DatasetDataSchema.index({ updateTime: 1 });
// rebuild data // rebuild data
DatasetDataSchema.index({ rebuilding: 1, teamId: 1, datasetId: 1 }); DatasetDataSchema.index({ rebuilding: 1, teamId: 1, datasetId: 1 });
DatasetDataSchema.index({ initFullText: 1 });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }

View File

@@ -8,8 +8,8 @@ import { getVectorsByText } from '../../ai/embedding';
import { getVectorModel } from '../../ai/model'; import { getVectorModel } from '../../ai/model';
import { MongoDatasetData } from '../data/schema'; import { MongoDatasetData } from '../data/schema';
import { import {
DatasetCollectionSchemaType,
DatasetDataSchemaType, DatasetDataSchemaType,
DatasetDataTextSchemaType,
SearchDataResponseItemType SearchDataResponseItemType
} from '@fastgpt/global/core/dataset/type'; } from '@fastgpt/global/core/dataset/type';
import { MongoDatasetCollection } from '../collection/schema'; import { MongoDatasetCollection } from '../collection/schema';
@@ -23,6 +23,7 @@ import { Types } from '../../../common/mongo';
import json5 from 'json5'; import json5 from 'json5';
import { MongoDatasetCollectionTags } from '../tag/schema'; import { MongoDatasetCollectionTags } from '../tag/schema';
import { readFromSecondary } from '../../../common/mongo/utils'; import { readFromSecondary } from '../../../common/mongo/utils';
import { MongoDatasetDataText } from '../data/dataTextSchema';
type SearchDatasetDataProps = { type SearchDatasetDataProps = {
teamId: string; teamId: string;
@@ -266,57 +267,60 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
filterCollectionIdList filterCollectionIdList
}); });
// get q and a // Get data and collections
const dataList = await MongoDatasetData.find( const collectionIdList = Array.from(new Set(results.map((item) => item.collectionId)));
{ const [dataList, collections] = await Promise.all([
teamId, MongoDatasetData.find(
datasetId: { $in: datasetIds }, {
collectionId: { $in: Array.from(new Set(results.map((item) => item.collectionId))) }, teamId,
'indexes.dataId': { $in: results.map((item) => item.id?.trim()) } datasetId: { $in: datasetIds },
}, collectionId: { $in: collectionIdList },
'datasetId collectionId updateTime q a chunkIndex indexes' 'indexes.dataId': { $in: results.map((item) => item.id?.trim()) }
) },
.populate<{ collection: DatasetCollectionSchemaType }>( '_id datasetId collectionId updateTime q a chunkIndex indexes',
'collection', { ...readFromSecondary }
'name fileId rawLink externalFileId externalFileUrl' ).lean(),
) MongoDatasetCollection.find(
.lean(); {
_id: { $in: collectionIdList }
},
'_id name fileId rawLink externalFileId externalFileUrl',
{ ...readFromSecondary }
).lean()
]);
// add score to data(It's already sorted. The first one is the one with the most points) const formatResult = results
const concatResults = dataList.map((data) => { .map((item, index) => {
const dataIdList = data.indexes.map((item) => item.dataId); const collection = collections.find((col) => String(col._id) === String(item.collectionId));
if (!collection) {
console.log('Collection is not found', item);
return;
}
const data = dataList.find((data) =>
data.indexes.some((index) => index.dataId === item.id)
);
if (!data) {
console.log('Data is not found', item);
return;
}
const maxScoreResult = results.find((item) => { const score = item?.score || 0;
return dataIdList.includes(item.id);
});
return { const result: SearchDataResponseItemType = {
...data, id: String(data._id),
score: maxScoreResult?.score || 0 updateTime: data.updateTime,
}; q: data.q,
}); a: data.a,
chunkIndex: data.chunkIndex,
datasetId: String(data.datasetId),
collectionId: String(data.collectionId),
...getCollectionSourceData(collection),
score: [{ type: SearchScoreTypeEnum.embedding, value: score, index }]
};
concatResults.sort((a, b) => b.score - a.score); return result;
})
const formatResult = concatResults.map((data, index) => { .filter(Boolean) as SearchDataResponseItemType[];
if (!data.collectionId) {
console.log('Collection is not found', data);
}
const result: SearchDataResponseItemType = {
id: String(data._id),
updateTime: data.updateTime,
q: data.q,
a: data.a,
chunkIndex: data.chunkIndex,
datasetId: String(data.datasetId),
collectionId: String(data.collectionId),
...getCollectionSourceData(data.collection),
score: [{ type: SearchScoreTypeEnum.embedding, value: data.score, index }]
};
return result;
});
return { return {
embeddingRecallResults: formatResult, embeddingRecallResults: formatResult,
@@ -344,88 +348,224 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
}; };
} }
let searchResults = ( const searchResults = (
await Promise.all( await Promise.all(
datasetIds.map(async (id) => { datasetIds.map(async (id) => {
return MongoDatasetData.aggregate([ return MongoDatasetData.aggregate(
{ [
$match: { {
teamId: new Types.ObjectId(teamId), $match: {
datasetId: new Types.ObjectId(id), teamId: new Types.ObjectId(teamId),
$text: { $search: jiebaSplit({ text: query }) }, datasetId: new Types.ObjectId(id),
...(filterCollectionIdList $text: { $search: jiebaSplit({ text: query }) },
? { ...(filterCollectionIdList
collectionId: { ? {
$in: filterCollectionIdList.map((id) => new Types.ObjectId(id)) collectionId: {
$in: filterCollectionIdList.map((id) => new Types.ObjectId(id))
}
} }
} : {}),
: {}), ...(forbidCollectionIdList && forbidCollectionIdList.length > 0
...(forbidCollectionIdList && forbidCollectionIdList.length > 0 ? {
? { collectionId: {
collectionId: { $nin: forbidCollectionIdList.map((id) => new Types.ObjectId(id))
$nin: forbidCollectionIdList.map((id) => new Types.ObjectId(id)) }
} }
} : {})
: {}) }
},
{
$sort: {
score: { $meta: 'textScore' }
}
},
{
$limit: limit
},
{
$project: {
_id: 1,
datasetId: 1,
collectionId: 1,
updateTime: 1,
q: 1,
a: 1,
chunkIndex: 1,
score: { $meta: 'textScore' }
}
} }
}, ],
{ {
$addFields: { ...readFromSecondary
score: { $meta: 'textScore' }
}
},
{
$sort: {
score: { $meta: 'textScore' }
}
},
{
$limit: limit
},
{
$project: {
_id: 1,
datasetId: 1,
collectionId: 1,
updateTime: 1,
q: 1,
a: 1,
chunkIndex: 1,
score: 1
}
} }
]); );
}) })
) )
).flat() as (DatasetDataSchemaType & { score: number })[]; ).flat() as (DatasetDataSchemaType & { score: number })[];
// resort // Get data and collections
searchResults.sort((a, b) => b.score - a.score);
searchResults.slice(0, limit);
const collections = await MongoDatasetCollection.find( const collections = await MongoDatasetCollection.find(
{ {
_id: { $in: searchResults.map((item) => item.collectionId) } _id: { $in: searchResults.map((item) => item.collectionId) }
}, },
'_id name fileId rawLink' '_id name fileId rawLink externalFileId externalFileUrl',
); { ...readFromSecondary }
).lean();
return { return {
fullTextRecallResults: searchResults.map((item, index) => { fullTextRecallResults: searchResults
const collection = collections.find((col) => String(col._id) === String(item.collectionId)); .map((data, index) => {
return { const collection = collections.find(
id: String(item._id), (col) => String(col._id) === String(data.collectionId)
datasetId: String(item.datasetId), );
collectionId: String(item.collectionId), if (!collection) {
updateTime: item.updateTime, console.log('Collection is not found', data);
...getCollectionSourceData(collection), return;
q: item.q, }
a: item.a,
chunkIndex: item.chunkIndex, return {
indexes: item.indexes, id: String(data._id),
score: [{ type: SearchScoreTypeEnum.fullText, value: item.score, index }] datasetId: String(data.datasetId),
}; collectionId: String(data.collectionId),
}), updateTime: data.updateTime,
q: data.q,
a: data.a,
chunkIndex: data.chunkIndex,
indexes: data.indexes,
...getCollectionSourceData(collection),
score: [{ type: SearchScoreTypeEnum.fullText, value: data.score ?? 0, index }]
};
})
.filter(Boolean) as SearchDataResponseItemType[],
tokenLen: 0
};
};
const fullTextRecall2 = async ({
query,
limit,
filterCollectionIdList,
forbidCollectionIdList
}: {
query: string;
limit: number;
filterCollectionIdList?: string[];
forbidCollectionIdList: string[];
}): Promise<{
fullTextRecallResults: SearchDataResponseItemType[];
tokenLen: number;
}> => {
if (limit === 0) {
return {
fullTextRecallResults: [],
tokenLen: 0
};
}
const searchResults = (
await Promise.all(
datasetIds.map(async (id) => {
return MongoDatasetDataText.aggregate(
[
{
$match: {
teamId: new Types.ObjectId(teamId),
datasetId: new Types.ObjectId(id),
$text: { $search: jiebaSplit({ text: query }) },
...(filterCollectionIdList
? {
collectionId: {
$in: filterCollectionIdList.map((id) => new Types.ObjectId(id))
}
}
: {}),
...(forbidCollectionIdList && forbidCollectionIdList.length > 0
? {
collectionId: {
$nin: forbidCollectionIdList.map((id) => new Types.ObjectId(id))
}
}
: {})
}
},
{
$sort: {
score: { $meta: 'textScore' }
}
},
{
$limit: limit
},
{
$project: {
_id: 1,
collectionId: 1,
dataId: 1,
score: { $meta: 'textScore' }
}
}
],
{
...readFromSecondary
}
);
})
)
).flat() as (DatasetDataTextSchemaType & { score: number })[];
// Get data and collections
const [dataList, collections] = await Promise.all([
MongoDatasetData.find(
{
_id: { $in: searchResults.map((item) => item.dataId) }
},
'_id datasetId collectionId updateTime q a chunkIndex indexes',
{ ...readFromSecondary }
).lean(),
MongoDatasetCollection.find(
{
_id: { $in: searchResults.map((item) => item.collectionId) }
},
'_id name fileId rawLink externalFileId externalFileUrl',
{ ...readFromSecondary }
).lean()
]);
return {
fullTextRecallResults: searchResults
.map((item, index) => {
const collection = collections.find(
(col) => String(col._id) === String(item.collectionId)
);
if (!collection) {
console.log('Collection is not found', item);
return;
}
const data = dataList.find((data) => String(data._id) === String(item.dataId));
if (!data) {
console.log('Data is not found', item);
return;
}
return {
id: String(data._id),
datasetId: String(data.datasetId),
collectionId: String(data.collectionId),
updateTime: data.updateTime,
q: data.q,
a: data.a,
chunkIndex: data.chunkIndex,
indexes: data.indexes,
...getCollectionSourceData(collection),
score: [
{
type: SearchScoreTypeEnum.fullText,
value: item.score || 0,
index
}
]
};
})
.filter(Boolean) as SearchDataResponseItemType[],
tokenLen: 0 tokenLen: 0
}; };
}; };
@@ -496,7 +636,8 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
forbidCollectionIdList, forbidCollectionIdList,
filterCollectionIdList filterCollectionIdList
}), }),
fullTextRecall({ // FullText tmp
fullTextRecall2({
query, query,
limit: fullTextLimit, limit: fullTextLimit,
filterCollectionIdList, filterCollectionIdList,

View File

@@ -1,5 +1,8 @@
import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt'; import { chats2GPTMessages } from '@fastgpt/global/core/chat/adapt';
import { countMessagesTokens } from '../../../../common/string/tiktoken/index'; import {
countGptMessagesTokens,
countPromptTokens
} from '../../../../common/string/tiktoken/index';
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { createChatCompletion } from '../../../ai/config'; import { createChatCompletion } from '../../../ai/config';
@@ -49,7 +52,7 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
const chatHistories = getHistories(history, histories); const chatHistories = getHistories(history, histories);
const { arg, tokens } = await completions({ const { arg, inputTokens, outputTokens } = await completions({
...props, ...props,
histories: chatHistories, histories: chatHistories,
cqModel cqModel
@@ -59,7 +62,8 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
const { totalPoints, modelName } = formatModelChars2Points({ const { totalPoints, modelName } = formatModelChars2Points({
model: cqModel.model, model: cqModel.model,
tokens, inputTokens: inputTokens,
outputTokens: outputTokens,
modelType: ModelTypeEnum.llm modelType: ModelTypeEnum.llm
}); });
@@ -72,7 +76,8 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints, totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
model: modelName, model: modelName,
query: userChatInput, query: userChatInput,
tokens, inputTokens: inputTokens,
outputTokens: outputTokens,
cqList: agents, cqList: agents,
cqResult: result.value, cqResult: result.value,
contextTotalLen: chatHistories.length + 2 contextTotalLen: chatHistories.length + 2
@@ -82,7 +87,8 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
moduleName: name, moduleName: name,
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints, totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
model: modelName, model: modelName,
tokens inputTokens: inputTokens,
outputTokens: outputTokens
} }
] ]
}; };
@@ -148,7 +154,8 @@ const completions = async ({
} }
return { return {
tokens: await countMessagesTokens(messages), inputTokens: await countGptMessagesTokens(requestMessages),
outputTokens: await countPromptTokens(answer),
arg: { type: id } arg: { type: id }
}; };
}; };

View File

@@ -3,7 +3,8 @@ import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../chat/
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d'; import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
import { import {
countMessagesTokens, countMessagesTokens,
countGptMessagesTokens countGptMessagesTokens,
countPromptTokens
} from '../../../../common/string/tiktoken/index'; } from '../../../../common/string/tiktoken/index';
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants'; import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { createChatCompletion } from '../../../ai/config'; import { createChatCompletion } from '../../../ai/config';
@@ -59,7 +60,7 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
const extractModel = getLLMModel(model); const extractModel = getLLMModel(model);
const chatHistories = getHistories(history, histories); const chatHistories = getHistories(history, histories);
const { arg, tokens } = await (async () => { const { arg, inputTokens, outputTokens } = await (async () => {
if (extractModel.toolChoice) { if (extractModel.toolChoice) {
return toolChoice({ return toolChoice({
...props, ...props,
@@ -114,7 +115,8 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
const { totalPoints, modelName } = formatModelChars2Points({ const { totalPoints, modelName } = formatModelChars2Points({
model: extractModel.model, model: extractModel.model,
tokens, inputTokens: inputTokens,
outputTokens: outputTokens,
modelType: ModelTypeEnum.llm modelType: ModelTypeEnum.llm
}); });
@@ -126,7 +128,8 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints, totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
model: modelName, model: modelName,
query: content, query: content,
tokens, inputTokens,
outputTokens,
extractDescription: description, extractDescription: description,
extractResult: arg, extractResult: arg,
contextTotalLen: chatHistories.length + 2 contextTotalLen: chatHistories.length + 2
@@ -136,7 +139,8 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
moduleName: name, moduleName: name,
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints, totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
model: modelName, model: modelName,
tokens inputTokens,
outputTokens
} }
] ]
}; };
@@ -249,15 +253,18 @@ const toolChoice = async (props: ActionProps) => {
} }
})(); })();
const completeMessages: ChatCompletionMessageParam[] = [ const AIMessages: ChatCompletionMessageParam[] = [
...filterMessages,
{ {
role: ChatCompletionRequestMessageRoleEnum.Assistant, role: ChatCompletionRequestMessageRoleEnum.Assistant,
tool_calls: response.choices?.[0]?.message?.tool_calls tool_calls: response.choices?.[0]?.message?.tool_calls
} }
]; ];
const inputTokens = await countGptMessagesTokens(filterMessages, tools);
const outputTokens = await countGptMessagesTokens(AIMessages);
return { return {
tokens: await countGptMessagesTokens(completeMessages, tools), inputTokens,
outputTokens,
arg arg
}; };
}; };
@@ -286,17 +293,21 @@ const functionCall = async (props: ActionProps) => {
try { try {
const arg = JSON.parse(response?.choices?.[0]?.message?.function_call?.arguments || ''); const arg = JSON.parse(response?.choices?.[0]?.message?.function_call?.arguments || '');
const completeMessages: ChatCompletionMessageParam[] = [
...filterMessages, const AIMessages: ChatCompletionMessageParam[] = [
{ {
role: ChatCompletionRequestMessageRoleEnum.Assistant, role: ChatCompletionRequestMessageRoleEnum.Assistant,
function_call: response.choices?.[0]?.message?.function_call function_call: response.choices?.[0]?.message?.function_call
} }
]; ];
const inputTokens = await countGptMessagesTokens(filterMessages, undefined, functions);
const outputTokens = await countGptMessagesTokens(AIMessages);
return { return {
arg, arg,
tokens: await countGptMessagesTokens(completeMessages, undefined, functions) inputTokens,
outputTokens
}; };
} catch (error) { } catch (error) {
console.log(response.choices?.[0]?.message); console.log(response.choices?.[0]?.message);
@@ -305,7 +316,8 @@ const functionCall = async (props: ActionProps) => {
return { return {
arg: {}, arg: {},
tokens: 0 inputTokens: 0,
outputTokens: 0
}; };
} }
}; };
@@ -370,7 +382,8 @@ Human: ${content}`
if (!jsonStr) { if (!jsonStr) {
return { return {
rawResponse: answer, rawResponse: answer,
tokens: await countMessagesTokens(messages), inputTokens: await countMessagesTokens(messages),
outputTokens: await countPromptTokens(answer),
arg: {} arg: {}
}; };
} }
@@ -378,7 +391,8 @@ Human: ${content}`
try { try {
return { return {
rawResponse: answer, rawResponse: answer,
tokens: await countMessagesTokens(messages), inputTokens: await countMessagesTokens(messages),
outputTokens: await countPromptTokens(answer),
arg: json5.parse(jsonStr) as Record<string, any> arg: json5.parse(jsonStr) as Record<string, any>
}; };
} catch (error) { } catch (error) {
@@ -386,7 +400,8 @@ Human: ${content}`
console.log(error); console.log(error);
return { return {
rawResponse: answer, rawResponse: answer,
tokens: await countMessagesTokens(messages), inputTokens: await countMessagesTokens(messages),
outputTokens: await countPromptTokens(answer),
arg: {} arg: {}
}; };
} }

View File

@@ -109,7 +109,8 @@ export const runToolWithFunctionCall = async (
return { return {
dispatchFlowResponse: [toolRunResponse], dispatchFlowResponse: [toolRunResponse],
toolNodeTokens: 0, toolNodeInputTokens: 0,
toolNodeOutputTokens: 0,
completeMessages: requestMessages, completeMessages: requestMessages,
assistantResponses: toolRunResponse.assistantResponses, assistantResponses: toolRunResponse.assistantResponses,
runTimes: toolRunResponse.runTimes, runTimes: toolRunResponse.runTimes,
@@ -126,7 +127,8 @@ export const runToolWithFunctionCall = async (
}, },
{ {
dispatchFlowResponse: [toolRunResponse], dispatchFlowResponse: [toolRunResponse],
toolNodeTokens: 0, toolNodeInputTokens: 0,
toolNodeOutputTokens: 0,
assistantResponses: toolRunResponse.assistantResponses, assistantResponses: toolRunResponse.assistantResponses,
runTimes: toolRunResponse.runTimes runTimes: toolRunResponse.runTimes
} }
@@ -340,7 +342,9 @@ export const runToolWithFunctionCall = async (
assistantToolMsgParams assistantToolMsgParams
] as ChatCompletionMessageParam[]; ] as ChatCompletionMessageParam[];
// Only toolCall tokens are counted here, Tool response tokens count towards the next reply // Only toolCall tokens are counted here, Tool response tokens count towards the next reply
const tokens = await countGptMessagesTokens(concatToolMessages, undefined, functions); // const tokens = await countGptMessagesTokens(concatToolMessages, undefined, functions);
const inputTokens = await countGptMessagesTokens(requestMessages, undefined, functions);
const outputTokens = await countGptMessagesTokens([assistantToolMsgParams]);
/* /*
... ...
user user
@@ -375,7 +379,12 @@ export const runToolWithFunctionCall = async (
const runTimes = const runTimes =
(response?.runTimes || 0) + (response?.runTimes || 0) +
flatToolsResponseData.reduce((sum, item) => sum + item.runTimes, 0); flatToolsResponseData.reduce((sum, item) => sum + item.runTimes, 0);
const toolNodeTokens = response?.toolNodeTokens ? response.toolNodeTokens + tokens : tokens; const toolNodeInputTokens = response?.toolNodeInputTokens
? response.toolNodeInputTokens + inputTokens
: inputTokens;
const toolNodeOutputTokens = response?.toolNodeOutputTokens
? response.toolNodeOutputTokens + outputTokens
: outputTokens;
// Check stop signal // Check stop signal
const hasStopSignal = flatToolsResponseData.some( const hasStopSignal = flatToolsResponseData.some(
@@ -408,7 +417,8 @@ export const runToolWithFunctionCall = async (
return { return {
dispatchFlowResponse, dispatchFlowResponse,
toolNodeTokens, toolNodeInputTokens,
toolNodeOutputTokens,
completeMessages, completeMessages,
assistantResponses: toolNodeAssistants, assistantResponses: toolNodeAssistants,
runTimes, runTimes,
@@ -423,7 +433,8 @@ export const runToolWithFunctionCall = async (
}, },
{ {
dispatchFlowResponse, dispatchFlowResponse,
toolNodeTokens, toolNodeInputTokens,
toolNodeOutputTokens,
assistantResponses: toolNodeAssistants, assistantResponses: toolNodeAssistants,
runTimes runTimes
} }
@@ -435,7 +446,8 @@ export const runToolWithFunctionCall = async (
content: answer content: answer
}; };
const completeMessages = filterMessages.concat(gptAssistantResponse); const completeMessages = filterMessages.concat(gptAssistantResponse);
const tokens = await countGptMessagesTokens(completeMessages, undefined, functions); const inputTokens = await countGptMessagesTokens(requestMessages, undefined, functions);
const outputTokens = await countGptMessagesTokens([gptAssistantResponse]);
// console.log(tokens, 'response token'); // console.log(tokens, 'response token');
// concat tool assistant // concat tool assistant
@@ -443,7 +455,12 @@ export const runToolWithFunctionCall = async (
return { return {
dispatchFlowResponse: response?.dispatchFlowResponse || [], dispatchFlowResponse: response?.dispatchFlowResponse || [],
toolNodeTokens: response?.toolNodeTokens ? response.toolNodeTokens + tokens : tokens, toolNodeInputTokens: response?.toolNodeInputTokens
? response.toolNodeInputTokens + inputTokens
: inputTokens,
toolNodeOutputTokens: response?.toolNodeOutputTokens
? response.toolNodeOutputTokens + outputTokens
: outputTokens,
completeMessages, completeMessages,
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value], assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
runTimes: (response?.runTimes || 0) + 1 runTimes: (response?.runTimes || 0) + 1

View File

@@ -165,6 +165,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
toolWorkflowInteractiveResponse, toolWorkflowInteractiveResponse,
dispatchFlowResponse, // tool flow response dispatchFlowResponse, // tool flow response
toolNodeTokens, toolNodeTokens,
toolNodeInputTokens,
toolNodeOutputTokens,
completeMessages = [], // The actual message sent to AI(just save text) completeMessages = [], // The actual message sent to AI(just save text)
assistantResponses = [], // FastGPT system store assistant.value response assistantResponses = [], // FastGPT system store assistant.value response
runTimes runTimes
@@ -225,7 +227,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
const { totalPoints, modelName } = formatModelChars2Points({ const { totalPoints, modelName } = formatModelChars2Points({
model, model,
tokens: toolNodeTokens, inputTokens: toolNodeInputTokens,
outputTokens: toolNodeOutputTokens,
modelType: ModelTypeEnum.llm modelType: ModelTypeEnum.llm
}); });
const toolAIUsage = externalProvider.openaiAccount?.key ? 0 : totalPoints; const toolAIUsage = externalProvider.openaiAccount?.key ? 0 : totalPoints;
@@ -255,6 +258,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
// 展示的积分消耗 // 展示的积分消耗
totalPoints: totalPointsUsage, totalPoints: totalPointsUsage,
toolCallTokens: toolNodeTokens, toolCallTokens: toolNodeTokens,
toolCallInputTokens: toolNodeInputTokens,
toolCallOutputTokens: toolNodeOutputTokens,
childTotalPoints: flatUsages.reduce((sum, item) => sum + item.totalPoints, 0), childTotalPoints: flatUsages.reduce((sum, item) => sum + item.totalPoints, 0),
model: modelName, model: modelName,
query: userChatInput, query: userChatInput,
@@ -270,9 +275,10 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
// 工具调用本身的积分消耗 // 工具调用本身的积分消耗
{ {
moduleName: name, moduleName: name,
totalPoints: toolAIUsage,
model: modelName, model: modelName,
tokens: toolNodeTokens totalPoints: toolAIUsage,
inputTokens: toolNodeInputTokens,
outputTokens: toolNodeOutputTokens
}, },
// 工具的消耗 // 工具的消耗
...flatUsages ...flatUsages

View File

@@ -115,7 +115,8 @@ export const runToolWithPromptCall = async (
return { return {
dispatchFlowResponse: [toolRunResponse], dispatchFlowResponse: [toolRunResponse],
toolNodeTokens: 0, toolNodeInputTokens: 0,
toolNodeOutputTokens: 0,
completeMessages: concatMessages, completeMessages: concatMessages,
assistantResponses: toolRunResponse.assistantResponses, assistantResponses: toolRunResponse.assistantResponses,
runTimes: toolRunResponse.runTimes, runTimes: toolRunResponse.runTimes,
@@ -131,7 +132,8 @@ export const runToolWithPromptCall = async (
}, },
{ {
dispatchFlowResponse: [toolRunResponse], dispatchFlowResponse: [toolRunResponse],
toolNodeTokens: 0, toolNodeInputTokens: 0,
toolNodeOutputTokens: 0,
assistantResponses: toolRunResponse.assistantResponses, assistantResponses: toolRunResponse.assistantResponses,
runTimes: toolRunResponse.runTimes runTimes: toolRunResponse.runTimes
} }
@@ -286,15 +288,20 @@ export const runToolWithPromptCall = async (
content: replaceAnswer content: replaceAnswer
}; };
const completeMessages = filterMessages.concat(gptAssistantResponse); const completeMessages = filterMessages.concat(gptAssistantResponse);
const tokens = await countGptMessagesTokens(completeMessages, undefined); const inputTokens = await countGptMessagesTokens(requestMessages);
// console.log(tokens, 'response token'); const outputTokens = await countGptMessagesTokens([gptAssistantResponse]);
// concat tool assistant // concat tool assistant
const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType; const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType;
return { return {
dispatchFlowResponse: response?.dispatchFlowResponse || [], dispatchFlowResponse: response?.dispatchFlowResponse || [],
toolNodeTokens: response?.toolNodeTokens ? response.toolNodeTokens + tokens : tokens, toolNodeInputTokens: response?.toolNodeInputTokens
? response.toolNodeInputTokens + inputTokens
: inputTokens,
toolNodeOutputTokens: response?.toolNodeOutputTokens
? response.toolNodeOutputTokens + outputTokens
: outputTokens,
completeMessages, completeMessages,
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value], assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
runTimes: (response?.runTimes || 0) + 1 runTimes: (response?.runTimes || 0) + 1
@@ -366,17 +373,9 @@ export const runToolWithPromptCall = async (
function_call: toolJson function_call: toolJson
}; };
/*
...
user
assistant: tool data
*/
const concatToolMessages = [
...requestMessages,
assistantToolMsgParams
] as ChatCompletionMessageParam[];
// Only toolCall tokens are counted here, Tool response tokens count towards the next reply // Only toolCall tokens are counted here, Tool response tokens count towards the next reply
const tokens = await countGptMessagesTokens(concatToolMessages, undefined); const inputTokens = await countGptMessagesTokens(requestMessages);
const outputTokens = await countGptMessagesTokens([assistantToolMsgParams]);
/* /*
... ...
@@ -437,7 +436,12 @@ ANSWER: `;
} }
const runTimes = (response?.runTimes || 0) + toolsRunResponse.toolResponse.runTimes; const runTimes = (response?.runTimes || 0) + toolsRunResponse.toolResponse.runTimes;
const toolNodeTokens = response?.toolNodeTokens ? response.toolNodeTokens + tokens : tokens; const toolNodeInputTokens = response?.toolNodeInputTokens
? response.toolNodeInputTokens + inputTokens
: inputTokens;
const toolNodeOutputTokens = response?.toolNodeOutputTokens
? response.toolNodeOutputTokens + outputTokens
: outputTokens;
// Check stop signal // Check stop signal
const hasStopSignal = toolsRunResponse.toolResponse.flowResponses.some((item) => !!item.toolStop); const hasStopSignal = toolsRunResponse.toolResponse.flowResponses.some((item) => !!item.toolStop);
@@ -460,7 +464,8 @@ ANSWER: `;
return { return {
dispatchFlowResponse, dispatchFlowResponse,
toolNodeTokens, toolNodeInputTokens,
toolNodeOutputTokens,
completeMessages: filterMessages, completeMessages: filterMessages,
assistantResponses: toolNodeAssistants, assistantResponses: toolNodeAssistants,
runTimes, runTimes,
@@ -475,7 +480,8 @@ ANSWER: `;
}, },
{ {
dispatchFlowResponse, dispatchFlowResponse,
toolNodeTokens, toolNodeInputTokens,
toolNodeOutputTokens,
assistantResponses: toolNodeAssistants, assistantResponses: toolNodeAssistants,
runTimes runTimes
} }

View File

@@ -27,9 +27,10 @@ import { getNanoid, sliceStrStartEnd } from '@fastgpt/global/common/string/tools
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants'; import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type'; import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants'; import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
import { getErrText } from '@fastgpt/global/common/error/utils';
type ToolRunResponseType = { type ToolRunResponseType = {
toolRunResponse: DispatchFlowResponse; toolRunResponse?: DispatchFlowResponse;
toolMsgParams: ChatCompletionToolMessageParam; toolMsgParams: ChatCompletionToolMessageParam;
}[]; }[];
@@ -158,7 +159,8 @@ export const runToolWithToolChoice = async (
return { return {
dispatchFlowResponse: [toolRunResponse], dispatchFlowResponse: [toolRunResponse],
toolNodeTokens: 0, toolNodeInputTokens: 0,
toolNodeOutputTokens: 0,
completeMessages: requestMessages, completeMessages: requestMessages,
assistantResponses: toolRunResponse.assistantResponses, assistantResponses: toolRunResponse.assistantResponses,
runTimes: toolRunResponse.runTimes, runTimes: toolRunResponse.runTimes,
@@ -176,7 +178,8 @@ export const runToolWithToolChoice = async (
}, },
{ {
dispatchFlowResponse: [toolRunResponse], dispatchFlowResponse: [toolRunResponse],
toolNodeTokens: 0, toolNodeInputTokens: 0,
toolNodeOutputTokens: 0,
assistantResponses: toolRunResponse.assistantResponses, assistantResponses: toolRunResponse.assistantResponses,
runTimes: toolRunResponse.runTimes runTimes: toolRunResponse.runTimes
} }
@@ -342,59 +345,87 @@ export const runToolWithToolChoice = async (
return Promise.reject(getEmptyResponseTip()); return Promise.reject(getEmptyResponseTip());
} }
// Run the selected tool by LLM. /* Run the selected tool by LLM.
const toolsRunResponse = ( Since only reference parameters are passed, if the same tool is run in parallel, it will get the same run parameters
await Promise.all( */
toolCalls.map(async (tool) => { const toolsRunResponse: ToolRunResponseType = [];
const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name); for await (const tool of toolCalls) {
try {
const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name);
if (!toolNode) return; if (!toolNode) continue;
const startParams = (() => { const startParams = (() => {
try { try {
return json5.parse(tool.function.arguments); return json5.parse(tool.function.arguments);
} catch (error) { } catch (error) {
return {}; return {};
}
})();
initToolNodes(runtimeNodes, [toolNode.nodeId], startParams);
const toolRunResponse = await dispatchWorkFlow({
...workflowProps,
isToolCall: true
});
const stringToolResponse = formatToolResponse(toolRunResponse.toolResponses);
const toolMsgParams: ChatCompletionToolMessageParam = {
tool_call_id: tool.id,
role: ChatCompletionRequestMessageRoleEnum.Tool,
name: tool.function.name,
content: stringToolResponse
};
workflowStreamResponse?.({
event: SseResponseEventEnum.toolResponse,
data: {
tool: {
id: tool.id,
toolName: '',
toolAvatar: '',
params: '',
response: sliceStrStartEnd(stringToolResponse, 5000, 5000)
} }
})(); }
});
initToolNodes(runtimeNodes, [toolNode.nodeId], startParams); toolsRunResponse.push({
const toolRunResponse = await dispatchWorkFlow({ toolRunResponse,
...workflowProps, toolMsgParams
isToolCall: true });
}); } catch (error) {
const err = getErrText(error);
workflowStreamResponse?.({
event: SseResponseEventEnum.toolResponse,
data: {
tool: {
id: tool.id,
toolName: '',
toolAvatar: '',
params: '',
response: sliceStrStartEnd(err, 5000, 5000)
}
}
});
const stringToolResponse = formatToolResponse(toolRunResponse.toolResponses); toolsRunResponse.push({
toolRunResponse: undefined,
const toolMsgParams: ChatCompletionToolMessageParam = { toolMsgParams: {
tool_call_id: tool.id, tool_call_id: tool.id,
role: ChatCompletionRequestMessageRoleEnum.Tool, role: ChatCompletionRequestMessageRoleEnum.Tool,
name: tool.function.name, name: tool.function.name,
content: stringToolResponse content: sliceStrStartEnd(err, 5000, 5000)
}; }
});
}
}
workflowStreamResponse?.({ const flatToolsResponseData = toolsRunResponse
event: SseResponseEventEnum.toolResponse, .map((item) => item.toolRunResponse)
data: { .flat()
tool: { .filter(Boolean) as DispatchFlowResponse[];
id: tool.id,
toolName: '',
toolAvatar: '',
params: '',
response: sliceStrStartEnd(stringToolResponse, 5000, 5000)
}
}
});
return {
toolRunResponse,
toolMsgParams
};
})
)
).filter(Boolean) as ToolRunResponseType;
const flatToolsResponseData = toolsRunResponse.map((item) => item.toolRunResponse).flat();
// concat tool responses // concat tool responses
const dispatchFlowResponse = response const dispatchFlowResponse = response
? response.dispatchFlowResponse.concat(flatToolsResponseData) ? response.dispatchFlowResponse.concat(flatToolsResponseData)
@@ -428,24 +459,26 @@ export const runToolWithToolChoice = async (
] as ChatCompletionMessageParam[]; ] as ChatCompletionMessageParam[];
// Only toolCall tokens are counted here, Tool response tokens count towards the next reply // Only toolCall tokens are counted here, Tool response tokens count towards the next reply
const tokens = await countGptMessagesTokens(concatToolMessages, tools); const inputTokens = await countGptMessagesTokens(requestMessages, tools);
const outputTokens = await countGptMessagesTokens(assistantToolMsgParams);
/* /*
... ...
user user
assistant: tool data assistant: tool data
tool: tool response tool: tool response
*/ */
const completeMessages = [ const completeMessages = [
...concatToolMessages, ...concatToolMessages,
...toolsRunResponse.map((item) => item?.toolMsgParams) ...toolsRunResponse.map((item) => item?.toolMsgParams)
]; ];
/* /*
Get tool node assistant response Get tool node assistant response
history assistant history assistant
current tool assistant current tool assistant
tool child assistant tool child assistant
*/ */
const toolNodeAssistant = GPTMessages2Chats([ const toolNodeAssistant = GPTMessages2Chats([
...assistantToolMsgParams, ...assistantToolMsgParams,
...toolsRunResponse.map((item) => item?.toolMsgParams) ...toolsRunResponse.map((item) => item?.toolMsgParams)
@@ -463,7 +496,10 @@ export const runToolWithToolChoice = async (
const runTimes = const runTimes =
(response?.runTimes || 0) + (response?.runTimes || 0) +
flatToolsResponseData.reduce((sum, item) => sum + item.runTimes, 0); flatToolsResponseData.reduce((sum, item) => sum + item.runTimes, 0);
const toolNodeTokens = response ? response.toolNodeTokens + tokens : tokens; const toolNodeInputTokens = response ? response.toolNodeInputTokens + inputTokens : inputTokens;
const toolNodeOutputTokens = response
? response.toolNodeOutputTokens + outputTokens
: outputTokens;
// Check stop signal // Check stop signal
const hasStopSignal = flatToolsResponseData.some( const hasStopSignal = flatToolsResponseData.some(
@@ -471,12 +507,12 @@ export const runToolWithToolChoice = async (
); );
// Check interactive response(Only 1 interaction is reserved) // Check interactive response(Only 1 interaction is reserved)
const workflowInteractiveResponseItem = toolsRunResponse.find( const workflowInteractiveResponseItem = toolsRunResponse.find(
(item) => item.toolRunResponse.workflowInteractiveResponse (item) => item.toolRunResponse?.workflowInteractiveResponse
); );
if (hasStopSignal || workflowInteractiveResponseItem) { if (hasStopSignal || workflowInteractiveResponseItem) {
// Get interactive tool data // Get interactive tool data
const workflowInteractiveResponse = const workflowInteractiveResponse =
workflowInteractiveResponseItem?.toolRunResponse.workflowInteractiveResponse; workflowInteractiveResponseItem?.toolRunResponse?.workflowInteractiveResponse;
// Flashback traverses completeMessages, intercepting messages that know the first user // Flashback traverses completeMessages, intercepting messages that know the first user
const firstUserIndex = completeMessages.findLastIndex((item) => item.role === 'user'); const firstUserIndex = completeMessages.findLastIndex((item) => item.role === 'user');
@@ -496,7 +532,8 @@ export const runToolWithToolChoice = async (
return { return {
dispatchFlowResponse, dispatchFlowResponse,
toolNodeTokens, toolNodeInputTokens,
toolNodeOutputTokens,
completeMessages, completeMessages,
assistantResponses: toolNodeAssistants, assistantResponses: toolNodeAssistants,
runTimes, runTimes,
@@ -512,7 +549,8 @@ export const runToolWithToolChoice = async (
}, },
{ {
dispatchFlowResponse, dispatchFlowResponse,
toolNodeTokens, toolNodeInputTokens,
toolNodeOutputTokens,
assistantResponses: toolNodeAssistants, assistantResponses: toolNodeAssistants,
runTimes runTimes
} }
@@ -524,14 +562,17 @@ export const runToolWithToolChoice = async (
content: answer content: answer
}; };
const completeMessages = filterMessages.concat(gptAssistantResponse); const completeMessages = filterMessages.concat(gptAssistantResponse);
const tokens = await countGptMessagesTokens(completeMessages, tools); const inputTokens = await countGptMessagesTokens(requestMessages, tools);
const outputTokens = await countGptMessagesTokens([gptAssistantResponse]);
// concat tool assistant // concat tool assistant
const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType; const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType;
return { return {
dispatchFlowResponse: response?.dispatchFlowResponse || [], dispatchFlowResponse: response?.dispatchFlowResponse || [],
toolNodeTokens: response ? response.toolNodeTokens + tokens : tokens, toolNodeInputTokens: response ? response.toolNodeInputTokens + inputTokens : inputTokens,
toolNodeOutputTokens: response ? response.toolNodeOutputTokens + outputTokens : outputTokens,
completeMessages, completeMessages,
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value], assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
runTimes: (response?.runTimes || 0) + 1 runTimes: (response?.runTimes || 0) + 1
@@ -578,7 +619,8 @@ async function streamResponse({
text: content text: content
}) })
}); });
} else if (responseChoice?.tool_calls?.[0]) { }
if (responseChoice?.tool_calls?.[0]) {
const toolCall: ChatCompletionMessageToolCall = responseChoice.tool_calls[0]; const toolCall: ChatCompletionMessageToolCall = responseChoice.tool_calls[0];
// In a stream response, only one tool is returned at a time. If have id, description is executing a tool // In a stream response, only one tool is returned at a time. If have id, description is executing a tool
if (toolCall.id || callingTool) { if (toolCall.id || callingTool) {

View File

@@ -31,7 +31,9 @@ export type DispatchToolModuleProps = ModuleDispatchProps<{
export type RunToolResponse = { export type RunToolResponse = {
dispatchFlowResponse: DispatchFlowResponse[]; dispatchFlowResponse: DispatchFlowResponse[];
toolNodeTokens: number; toolNodeTokens?: number; // deprecated
toolNodeInputTokens: number;
toolNodeOutputTokens: number;
completeMessages?: ChatCompletionMessageParam[]; completeMessages?: ChatCompletionMessageParam[];
assistantResponses?: AIChatItemValueItemType[]; assistantResponses?: AIChatItemValueItemType[];
toolWorkflowInteractiveResponse?: WorkflowInteractiveResponseType; toolWorkflowInteractiveResponse?: WorkflowInteractiveResponseType;

View File

@@ -5,13 +5,17 @@ import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants'; import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils'; import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
import { createChatCompletion } from '../../../ai/config'; import { createChatCompletion } from '../../../ai/config';
import type { ChatCompletion, StreamChatType } from '@fastgpt/global/core/ai/type.d'; import type {
ChatCompletion,
ChatCompletionMessageParam,
StreamChatType
} from '@fastgpt/global/core/ai/type.d';
import { formatModelChars2Points } from '../../../../support/wallet/usage/utils'; import { formatModelChars2Points } from '../../../../support/wallet/usage/utils';
import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d'; import type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
import { postTextCensor } from '../../../../common/api/requestPlusApi'; import { postTextCensor } from '../../../../common/api/requestPlusApi';
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants'; import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
import type { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type'; import type { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { countMessagesTokens } from '../../../../common/string/tiktoken/index'; import { countGptMessagesTokens } from '../../../../common/string/tiktoken/index';
import { import {
chats2GPTMessages, chats2GPTMessages,
chatValue2RuntimePrompt, chatValue2RuntimePrompt,
@@ -214,16 +218,23 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
return Promise.reject(getEmptyResponseTip()); return Promise.reject(getEmptyResponseTip());
} }
const completeMessages = requestMessages.concat({ const AIMessages: ChatCompletionMessageParam[] = [
role: ChatCompletionRequestMessageRoleEnum.Assistant, {
content: answerText role: ChatCompletionRequestMessageRoleEnum.Assistant,
}); content: answerText
}
];
const completeMessages = [...requestMessages, ...AIMessages];
const chatCompleteMessages = GPTMessages2Chats(completeMessages); const chatCompleteMessages = GPTMessages2Chats(completeMessages);
const tokens = await countMessagesTokens(chatCompleteMessages); const inputTokens = await countGptMessagesTokens(requestMessages);
const outputTokens = await countGptMessagesTokens(AIMessages);
const { totalPoints, modelName } = formatModelChars2Points({ const { totalPoints, modelName } = formatModelChars2Points({
model, model,
tokens, inputTokens,
outputTokens,
modelType: ModelTypeEnum.llm modelType: ModelTypeEnum.llm
}); });
@@ -232,7 +243,9 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
[DispatchNodeResponseKeyEnum.nodeResponse]: { [DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints, totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
model: modelName, model: modelName,
tokens, tokens: inputTokens + outputTokens,
inputTokens: inputTokens,
outputTokens: outputTokens,
query: `${userChatInput}`, query: `${userChatInput}`,
maxToken: max_tokens, maxToken: max_tokens,
historyPreview: getHistoryPreview( historyPreview: getHistoryPreview(
@@ -247,7 +260,8 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
moduleName: name, moduleName: name,
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints, totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
model: modelName, model: modelName,
tokens inputTokens: inputTokens,
outputTokens: outputTokens
} }
], ],
[DispatchNodeResponseKeyEnum.toolResponses]: answerText, [DispatchNodeResponseKeyEnum.toolResponses]: answerText,

View File

@@ -120,14 +120,14 @@ export async function dispatchDatasetSearch(
// vector // vector
const { totalPoints, modelName } = formatModelChars2Points({ const { totalPoints, modelName } = formatModelChars2Points({
model: vectorModel.model, model: vectorModel.model,
tokens, inputTokens: tokens,
modelType: ModelTypeEnum.vector modelType: ModelTypeEnum.vector
}); });
const responseData: DispatchNodeResponseType & { totalPoints: number } = { const responseData: DispatchNodeResponseType & { totalPoints: number } = {
totalPoints, totalPoints,
query: concatQueries.join('\n'), query: concatQueries.join('\n'),
model: modelName, model: modelName,
tokens, inputTokens: tokens,
similarity: usingSimilarityFilter ? similarity : undefined, similarity: usingSimilarityFilter ? similarity : undefined,
limit, limit,
searchMode, searchMode,
@@ -139,19 +139,21 @@ export async function dispatchDatasetSearch(
totalPoints, totalPoints,
moduleName: node.name, moduleName: node.name,
model: modelName, model: modelName,
tokens inputTokens: tokens
} }
]; ];
if (aiExtensionResult) { if (aiExtensionResult) {
const { totalPoints, modelName } = formatModelChars2Points({ const { totalPoints, modelName } = formatModelChars2Points({
model: aiExtensionResult.model, model: aiExtensionResult.model,
tokens: aiExtensionResult.tokens, inputTokens: aiExtensionResult.inputTokens,
outputTokens: aiExtensionResult.outputTokens,
modelType: ModelTypeEnum.llm modelType: ModelTypeEnum.llm
}); });
responseData.totalPoints += totalPoints; responseData.totalPoints += totalPoints;
responseData.tokens = aiExtensionResult.tokens; responseData.inputTokens = aiExtensionResult.inputTokens;
responseData.outputTokens = aiExtensionResult.outputTokens;
responseData.extensionModel = modelName; responseData.extensionModel = modelName;
responseData.extensionResult = responseData.extensionResult =
aiExtensionResult.extensionQueries?.join('\n') || aiExtensionResult.extensionQueries?.join('\n') ||
@@ -161,7 +163,8 @@ export async function dispatchDatasetSearch(
totalPoints, totalPoints,
moduleName: 'core.module.template.Query extension', moduleName: 'core.module.template.Query extension',
model: modelName, model: modelName,
tokens: aiExtensionResult.tokens inputTokens: aiExtensionResult.inputTokens,
outputTokens: aiExtensionResult.outputTokens
}); });
} }

View File

@@ -72,6 +72,7 @@ import { dispatchLoopEnd } from './loop/runLoopEnd';
import { dispatchLoopStart } from './loop/runLoopStart'; import { dispatchLoopStart } from './loop/runLoopStart';
import { dispatchFormInput } from './interactive/formInput'; import { dispatchFormInput } from './interactive/formInput';
import { dispatchToolParams } from './agent/runTool/toolParams'; import { dispatchToolParams } from './agent/runTool/toolParams';
import { getErrText } from '@fastgpt/global/common/error/utils';
const callbackMap: Record<FlowNodeTypeEnum, Function> = { const callbackMap: Record<FlowNodeTypeEnum, Function> = {
[FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart, [FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart,
@@ -231,9 +232,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
if (toolResponses !== undefined) { if (toolResponses !== undefined) {
if (Array.isArray(toolResponses) && toolResponses.length === 0) return; if (Array.isArray(toolResponses) && toolResponses.length === 0) return;
if (typeof toolResponses === 'object' && Object.keys(toolResponses).length === 0) { if (typeof toolResponses === 'object' && Object.keys(toolResponses).length === 0) return;
return;
}
toolRunResponse = toolResponses; toolRunResponse = toolResponses;
} }
@@ -565,6 +564,8 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
const targetEdges = runtimeEdges.filter((item) => item.source === node.nodeId); const targetEdges = runtimeEdges.filter((item) => item.source === node.nodeId);
const skipHandleIds = targetEdges.map((item) => item.sourceHandle); const skipHandleIds = targetEdges.map((item) => item.sourceHandle);
toolRunResponse = getErrText(error);
// Skip all edges and return error // Skip all edges and return error
return { return {
[DispatchNodeResponseKeyEnum.nodeResponse]: { [DispatchNodeResponseKeyEnum.nodeResponse]: {

View File

@@ -130,8 +130,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [ [DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
{ {
moduleName: plugin.name, moduleName: plugin.name,
totalPoints: usagePoints, totalPoints: usagePoints
tokens: 0
} }
], ],
[DispatchNodeResponseKeyEnum.toolResponses]: output?.pluginOutput [DispatchNodeResponseKeyEnum.toolResponses]: output?.pluginOutput

View File

@@ -153,8 +153,7 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [ [DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
{ {
moduleName: appData.name, moduleName: appData.name,
totalPoints: usagePoints, totalPoints: usagePoints
tokens: 0
} }
], ],
[DispatchNodeResponseKeyEnum.toolResponses]: text, [DispatchNodeResponseKeyEnum.toolResponses]: text,

View File

@@ -177,17 +177,17 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
if (!httpJsonBody) return {}; if (!httpJsonBody) return {};
if (httpContentType === ContentTypes.json) { if (httpContentType === ContentTypes.json) {
httpJsonBody = replaceStringVariables(httpJsonBody); httpJsonBody = replaceStringVariables(httpJsonBody);
const replaceJsonBody = httpJsonBody.replace(/(".*?")\s*:\s*undefined\b/g, '$1: null');
// Json body, parse and return // Json body, parse and return
const jsonParse = json5.parse( const jsonParse = json5.parse(replaceJsonBody);
httpJsonBody.replace(/(".*?")\s*:\s*undefined\b/g, '$1: null')
);
const removeSignJson = removeUndefinedSign(jsonParse); const removeSignJson = removeUndefinedSign(jsonParse);
return removeSignJson; return removeSignJson;
} }
httpJsonBody = replaceStringVariables(httpJsonBody); httpJsonBody = replaceStringVariables(httpJsonBody);
return httpJsonBody.replaceAll(UNDEFINED_SIGN, 'null'); return httpJsonBody.replaceAll(UNDEFINED_SIGN, 'null');
} catch (error) { } catch (error) {
console.log(error);
return Promise.reject(`Invalid JSON body: ${httpJsonBody}`); return Promise.reject(`Invalid JSON body: ${httpJsonBody}`);
} }
})(); })();

View File

@@ -31,7 +31,7 @@ export const dispatchQueryExtension = async ({
const queryExtensionModel = getLLMModel(model); const queryExtensionModel = getLLMModel(model);
const chatHistories = getHistories(history, histories); const chatHistories = getHistories(history, histories);
const { extensionQueries, tokens } = await queryExtension({ const { extensionQueries, inputTokens, outputTokens } = await queryExtension({
chatBg: systemPrompt, chatBg: systemPrompt,
query: userChatInput, query: userChatInput,
histories: chatHistories, histories: chatHistories,
@@ -42,7 +42,8 @@ export const dispatchQueryExtension = async ({
const { totalPoints, modelName } = formatModelChars2Points({ const { totalPoints, modelName } = formatModelChars2Points({
model: queryExtensionModel.model, model: queryExtensionModel.model,
tokens, inputTokens,
outputTokens,
modelType: ModelTypeEnum.llm modelType: ModelTypeEnum.llm
}); });
@@ -59,7 +60,8 @@ export const dispatchQueryExtension = async ({
[DispatchNodeResponseKeyEnum.nodeResponse]: { [DispatchNodeResponseKeyEnum.nodeResponse]: {
totalPoints, totalPoints,
model: modelName, model: modelName,
tokens, inputTokens,
outputTokens,
query: userChatInput, query: userChatInput,
textOutput: JSON.stringify(filterSameQueries) textOutput: JSON.stringify(filterSameQueries)
}, },
@@ -68,7 +70,8 @@ export const dispatchQueryExtension = async ({
moduleName: node.name, moduleName: node.name,
totalPoints, totalPoints,
model: modelName, model: modelName,
tokens inputTokens,
outputTokens
} }
], ],
[NodeOutputKeyEnum.text]: JSON.stringify(filterSameQueries) [NodeOutputKeyEnum.text]: JSON.stringify(filterSameQueries)

View File

@@ -92,6 +92,7 @@ OutLinkSchema.virtual('associatedApp', {
try { try {
OutLinkSchema.index({ shareId: -1 }); OutLinkSchema.index({ shareId: -1 });
OutLinkSchema.index({ teamId: 1, tmbId: 1, appId: 1 });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }

View File

@@ -0,0 +1,43 @@
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
import { AuthModeType, AuthResponseType } from '../type';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
import { authUserPer } from '../user/auth';
import { ManagePermissionVal } from '@fastgpt/global/support/permission/constant';
/*
Team manager can control org
*/
export const authOrgMember = async ({
orgIds,
...props
}: {
orgIds: string | string[];
} & AuthModeType): Promise<AuthResponseType> => {
const result = await authUserPer({
...props,
per: ManagePermissionVal
});
const { teamId, tmbId, isRoot, tmb } = result;
if (isRoot) {
return {
teamId,
tmbId,
userId: result.userId,
appId: result.appId,
apikey: result.apikey,
isRoot,
authType: result.authType,
permission: new TeamPermission({ isOwner: true })
};
}
if (tmb.permission.hasManagePer) {
return {
...result,
permission: tmb.permission
};
}
return Promise.reject(TeamErrEnum.unAuthTeam);
};

View File

@@ -18,7 +18,7 @@ export async function getUserChatInfoAndAuthTeamPoints(tmbId: string) {
]) ])
.lean(); .lean();
if (!tmb) return Promise.reject(UserErrEnum.unAuthUser); if (!tmb) return Promise.reject(UserErrEnum.binVisitor);
await checkTeamAIPoints(tmb.team._id); await checkTeamAIPoints(tmb.team._id);

View File

@@ -8,10 +8,7 @@ import { authOpenApiKey } from '../openapi/auth';
import { FileTokenQuery } from '@fastgpt/global/common/file/type'; import { FileTokenQuery } from '@fastgpt/global/common/file/type';
import { MongoResourcePermission } from './schema'; import { MongoResourcePermission } from './schema';
import { ClientSession } from 'mongoose'; import { ClientSession } from 'mongoose';
import { import { PermissionValueType } from '@fastgpt/global/support/permission/type';
PermissionValueType,
ResourcePermissionType
} from '@fastgpt/global/support/permission/type';
import { bucketNameMap } from '@fastgpt/global/common/file/constants'; import { bucketNameMap } from '@fastgpt/global/common/file/constants';
import { addMinutes } from 'date-fns'; import { addMinutes } from 'date-fns';
import { getGroupsByTmbId } from './memberGroup/controllers'; import { getGroupsByTmbId } from './memberGroup/controllers';
@@ -21,6 +18,8 @@ import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type'; import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type';
import { TeamMemberSchema } from '@fastgpt/global/support/user/team/type'; import { TeamMemberSchema } from '@fastgpt/global/support/user/team/type';
import { UserModelSchema } from '@fastgpt/global/support/user/type'; import { UserModelSchema } from '@fastgpt/global/support/user/type';
import { OrgSchemaType } from '@fastgpt/global/support/user/team/org/type';
import { getOrgIdSetWithParentByTmbId } from './org/controllers';
/** get resource permission for a team member /** get resource permission for a team member
* If there is no permission for the team member, it will return undefined * If there is no permission for the team member, it will return undefined
@@ -67,67 +66,44 @@ export const getResourcePermission = async ({
} }
// If there is no personal permission, get the group permission // If there is no personal permission, get the group permission
const groupIdList = (await getGroupsByTmbId({ tmbId, teamId })).map((item) => item._id); const [groupPers, orgPers] = await Promise.all([
getGroupsByTmbId({ tmbId, teamId })
.then((res) => res.map((item) => item._id))
.then((groupIdList) =>
MongoResourcePermission.find(
{
teamId,
resourceType,
groupId: {
$in: groupIdList
},
resourceId
},
'permission'
).lean()
)
.then((perList) => perList.map((item) => item.permission)),
getOrgIdSetWithParentByTmbId({ tmbId, teamId })
.then((item) => Array.from(item))
.then((orgIds) =>
MongoResourcePermission.find(
{
teamId,
resourceType,
orgId: {
$in: Array.from(orgIds)
},
resourceId
},
'permission'
).lean()
)
.then((perList) => perList.map((item) => item.permission))
]);
if (groupIdList.length === 0) { return concatPer([...groupPers, ...orgPers]);
return undefined;
}
// get the maximum permission of the group
const pers = (
await MongoResourcePermission.find(
{
teamId,
resourceType,
groupId: {
$in: groupIdList
},
resourceId
},
'permission'
).lean()
).map((item) => item.permission);
const groupPer = getGroupPer(pers);
return groupPer;
}; };
/* 仅取 members 不取 groups */
export async function getResourceAllClbs({
resourceId,
teamId,
resourceType,
session
}: {
teamId: string;
session?: ClientSession;
} & (
| {
resourceType: 'team';
resourceId?: undefined;
}
| {
resourceType: Omit<PerResourceTypeEnum, 'team'>;
resourceId?: string | null;
}
)): Promise<ResourcePermissionType[]> {
return MongoResourcePermission.find(
{
resourceType: resourceType,
teamId: teamId,
resourceId,
groupId: {
$exists: false
}
},
null,
{
session
}
).lean();
}
export async function getResourceClbsAndGroups({ export async function getResourceClbsAndGroups({
resourceId, resourceId,
resourceType, resourceType,
@@ -155,10 +131,17 @@ export const getClbsAndGroupsWithInfo = async ({
resourceType, resourceType,
teamId teamId
}: { }: {
resourceId: ParentIdType;
resourceType: Omit<`${PerResourceTypeEnum}`, 'team'>;
teamId: string; teamId: string;
}) => } & (
| {
resourceId: ParentIdType;
resourceType: Omit<`${PerResourceTypeEnum}`, 'team'>;
}
| {
resourceType: 'team';
resourceId?: undefined;
}
)) =>
Promise.all([ Promise.all([
MongoResourcePermission.find({ MongoResourcePermission.find({
teamId, teamId,
@@ -170,7 +153,7 @@ export const getClbsAndGroupsWithInfo = async ({
}) })
.populate<{ tmb: TeamMemberSchema & { user: UserModelSchema } }>({ .populate<{ tmb: TeamMemberSchema & { user: UserModelSchema } }>({
path: 'tmb', path: 'tmb',
select: 'name userId', select: 'name userId role',
populate: { populate: {
path: 'user', path: 'user',
select: 'avatar' select: 'avatar'
@@ -186,6 +169,16 @@ export const getClbsAndGroupsWithInfo = async ({
} }
}) })
.populate<{ group: MemberGroupSchemaType }>('group', 'name avatar') .populate<{ group: MemberGroupSchemaType }>('group', 'name avatar')
.lean(),
MongoResourcePermission.find({
teamId,
resourceId,
resourceType,
orgId: {
$exists: true
}
})
.populate<{ org: OrgSchemaType }>({ path: 'org', select: 'name avatar' })
.lean() .lean()
]); ]);
@@ -196,6 +189,7 @@ export const delResourcePermission = ({
session, session,
tmbId, tmbId,
groupId, groupId,
orgId,
...props ...props
}: { }: {
resourceType: PerResourceTypeEnum; resourceType: PerResourceTypeEnum;
@@ -204,15 +198,18 @@ export const delResourcePermission = ({
session?: ClientSession; session?: ClientSession;
tmbId?: string; tmbId?: string;
groupId?: string; groupId?: string;
orgId?: string;
}) => { }) => {
// tmbId or groupId only one and not both // either tmbId or groupId or orgId must be provided
if (!!tmbId === !!groupId) { if (!tmbId && !groupId && !orgId) {
return Promise.reject(CommonErrEnum.missingParams); return Promise.reject(CommonErrEnum.missingParams);
} }
return MongoResourcePermission.deleteOne( return MongoResourcePermission.deleteOne(
{ {
...(tmbId ? { tmbId } : {}), ...(tmbId ? { tmbId } : {}),
...(groupId ? { groupId } : {}), ...(groupId ? { groupId } : {}),
...(orgId ? { orgId } : {}),
...props ...props
}, },
{ session } { session }
@@ -250,7 +247,7 @@ export function authJWT(token: string) {
}>((resolve, reject) => { }>((resolve, reject) => {
const key = process.env.TOKEN_KEY as string; const key = process.env.TOKEN_KEY as string;
jwt.verify(token, key, function (err, decoded: any) { jwt.verify(token, key, (err, decoded: any) => {
if (err || !decoded?.userId) { if (err || !decoded?.userId) {
reject(ERROR_ENUM.unAuthorization); reject(ERROR_ENUM.unAuthorization);
return; return;
@@ -436,7 +433,7 @@ export const authFileToken = (token?: string) =>
} }
const key = (process.env.FILE_TOKEN_KEY as string) ?? 'filetoken'; const key = (process.env.FILE_TOKEN_KEY as string) ?? 'filetoken';
jwt.verify(token, key, function (err, decoded: any) { jwt.verify(token, key, (err, decoded: any) => {
if (err || !decoded.bucketName || !decoded?.teamId || !decoded?.fileId) { if (err || !decoded.bucketName || !decoded?.teamId || !decoded?.fileId) {
reject(ERROR_ENUM.unAuthFile); reject(ERROR_ENUM.unAuthFile);
return; return;
@@ -450,10 +447,10 @@ export const authFileToken = (token?: string) =>
}); });
}); });
export const getGroupPer = (groups: PermissionValueType[] = []) => { export const concatPer = (perList: PermissionValueType[] = []) => {
if (groups.length === 0) { if (perList.length === 0) {
return undefined; return undefined;
} }
return new Permission().addPer(...groups).value; return new Permission().addPer(...perList).value;
}; };

View File

@@ -1,11 +1,11 @@
import { mongoSessionRun } from '../../common/mongo/sessionRun'; import { mongoSessionRun } from '../../common/mongo/sessionRun';
import { MongoResourcePermission } from './schema'; import { MongoResourcePermission } from './schema';
import { ClientSession, Model } from 'mongoose'; import type { ClientSession, Model } from 'mongoose';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant'; import type { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { PermissionValueType } from '@fastgpt/global/support/permission/type'; import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
import { getResourceClbsAndGroups } from './controller'; import { getResourceClbsAndGroups } from './controller';
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils'; import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type'; import type { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
export type SyncChildrenPermissionResourceType = { export type SyncChildrenPermissionResourceType = {
_id: string; _id: string;
@@ -18,6 +18,7 @@ export type UpdateCollaboratorItem = {
} & RequireOnlyOne<{ } & RequireOnlyOne<{
tmbId: string; tmbId: string;
groupId: string; groupId: string;
orgId: string;
}>; }>;
// sync the permission to all children folders. // sync the permission to all children folders.
@@ -161,7 +162,7 @@ export async function resumeInheritPermission({
} }
} }
/* /*
Delete all the collaborators and then insert the new collaborators. Delete all the collaborators and then insert the new collaborators.
*/ */
export async function syncCollaborators({ export async function syncCollaborators({

View File

@@ -1,9 +1,6 @@
import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type'; import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type';
import { MongoGroupMemberModel } from './groupMemberSchema'; import { MongoGroupMemberModel } from './groupMemberSchema';
import { TeamMemberSchema } from '@fastgpt/global/support/user/team/type'; import { parseHeaderCert } from '../controller';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { MongoResourcePermission } from '../schema';
import { getGroupPer, parseHeaderCert } from '../controller';
import { MongoMemberGroupModel } from './memberGroupSchema'; import { MongoMemberGroupModel } from './memberGroupSchema';
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
import { ClientSession } from 'mongoose'; import { ClientSession } from 'mongoose';
@@ -50,26 +47,32 @@ export const getTeamDefaultGroup = async ({
export const getGroupsByTmbId = async ({ export const getGroupsByTmbId = async ({
tmbId, tmbId,
teamId, teamId,
role role,
session
}: { }: {
tmbId: string; tmbId: string;
teamId: string; teamId: string;
role?: `${GroupMemberRole}`[]; role?: `${GroupMemberRole}`[];
session?: ClientSession;
}) => }) =>
( (
await Promise.all([ await Promise.all([
( (
await MongoGroupMemberModel.find({ await MongoGroupMemberModel.find(
tmbId, {
groupId: { tmbId,
$exists: true groupId: {
$exists: true
},
...(role ? { role: { $in: role } } : {})
}, },
...(role ? { role: { $in: role } } : {}) undefined,
}) { session }
)
.populate<{ group: MemberGroupSchemaType }>('group') .populate<{ group: MemberGroupSchemaType }>('group')
.lean() .lean()
).map((item) => item.group), ).map((item) => item.group),
role ? [] : getTeamDefaultGroup({ teamId }) role ? [] : getTeamDefaultGroup({ teamId, session })
]) ])
).flat(); ).flat();
@@ -79,46 +82,6 @@ export const getGroupMembersByGroupId = async (groupId: string) => {
}).lean(); }).lean();
}; };
/**
* Get tmb's group permission: the maximum permission of the group
* @param tmbId
* @param resourceId
* @param resourceType
* @returns the maximum permission of the group
*/
export const getGroupPermission = async ({
tmbId,
resourceId,
teamId,
resourceType
}: {
tmbId: string;
teamId: string;
} & (
| {
resourceId?: undefined;
resourceType: 'team';
}
| {
resourceId: string;
resourceType: Omit<PerResourceTypeEnum, 'team'>;
}
)) => {
const groupIds = (await getGroupsByTmbId({ tmbId, teamId })).map((item) => item._id);
const groupPermissions = (
await MongoResourcePermission.find({
groupId: {
$in: groupIds
},
resourceType,
resourceId,
teamId
})
).map((item) => item.permission);
return getGroupPer(groupPermissions);
};
// auth group member role // auth group member role
export const authGroupMemberRole = async ({ export const authGroupMemberRole = async ({
groupId, groupId,
@@ -140,8 +103,12 @@ export const authGroupMemberRole = async ({
tmbId tmbId
}; };
} }
const groupMember = await MongoGroupMemberModel.findOne({ groupId, tmbId }); const [groupMember, tmb] = await Promise.all([
const tmb = await getTmbInfoByTmbId({ tmbId }); MongoGroupMemberModel.findOne({ groupId, tmbId }),
getTmbInfoByTmbId({ tmbId })
]);
// Team admin or role check
if (tmb.permission.hasManagePer || (groupMember && role.includes(groupMember.role))) { if (tmb.permission.hasManagePer || (groupMember && role.includes(groupMember.role))) {
return { return {
...result, ...result,

View File

@@ -0,0 +1,95 @@
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
import type { OrgSchemaType } from '@fastgpt/global/support/user/team/org/type';
import type { ClientSession } from 'mongoose';
import { MongoOrgModel } from './orgSchema';
import { MongoOrgMemberModel } from './orgMemberSchema';
import { getOrgChildrenPath } from '@fastgpt/global/support/user/team/org/constant';
export const getOrgsByTmbId = async ({ teamId, tmbId }: { teamId: string; tmbId: string }) =>
MongoOrgMemberModel.find({ teamId, tmbId }, 'orgId').lean();
export const getOrgIdSetWithParentByTmbId = async ({
teamId,
tmbId
}: {
teamId: string;
tmbId: string;
}) => {
const orgMembers = await MongoOrgMemberModel.find({ teamId, tmbId }, 'orgId').lean();
const orgIds = Array.from(new Set(orgMembers.map((item) => String(item.orgId))));
const orgs = await MongoOrgModel.find({ _id: { $in: orgIds } }, 'path').lean();
const pathIdList = new Set<string>(
orgs
.map((org) => {
const pathIdList = org.path.split('/').filter(Boolean);
return pathIdList;
})
.flat()
);
const parentOrgs = await MongoOrgModel.find(
{
teamId,
pathId: { $in: Array.from(pathIdList) }
},
'_id'
).lean();
const parentOrgIds = parentOrgs.map((item) => String(item._id));
return new Set([...orgIds, ...parentOrgIds]);
};
export const getChildrenByOrg = async ({
org,
teamId,
session
}: {
org: OrgSchemaType;
teamId: string;
session?: ClientSession;
}) => {
return MongoOrgModel.find(
{ teamId, path: { $regex: `^${getOrgChildrenPath(org)}` } },
undefined,
{
session
}
).lean();
};
export const getOrgAndChildren = async ({
orgId,
teamId,
session
}: {
orgId: string;
teamId: string;
session?: ClientSession;
}) => {
const org = await MongoOrgModel.findOne({ _id: orgId, teamId }, undefined, { session }).lean();
if (!org) {
return Promise.reject(TeamErrEnum.orgNotExist);
}
const children = await getChildrenByOrg({ org, teamId, session });
return { org, children };
};
export async function createRootOrg({
teamId,
session
}: {
teamId: string;
session?: ClientSession;
}) {
return MongoOrgModel.create(
[
{
teamId,
name: 'ROOT',
path: ''
}
],
{ session }
);
}

View File

@@ -0,0 +1,65 @@
import { OrgCollectionName } from '@fastgpt/global/support/user/team/org/constant';
import { connectionMongo, getMongoModel } from '../../../common/mongo';
import {
TeamCollectionName,
TeamMemberCollectionName
} from '@fastgpt/global/support/user/team/constant';
import { OrgMemberSchemaType } from '@fastgpt/global/support/user/team/org/type';
const { Schema } = connectionMongo;
export const OrgMemberCollectionName = 'team_org_members';
export const OrgMemberSchema = new Schema({
teamId: {
type: Schema.Types.ObjectId,
ref: TeamCollectionName,
required: true
},
orgId: {
type: Schema.Types.ObjectId,
ref: OrgCollectionName,
required: true
},
tmbId: {
type: Schema.Types.ObjectId,
ref: TeamMemberCollectionName,
required: true
}
// role: {
// type: String,
// enum: Object.values(OrgMemberRole),
// required: true,
// default: OrgMemberRole.member
// }
});
OrgMemberSchema.virtual('org', {
ref: OrgCollectionName,
localField: 'orgId',
foreignField: '_id',
justOne: true
});
try {
OrgMemberSchema.index(
{
teamId: 1,
orgId: 1,
tmbId: 1
},
{
unique: true
}
);
OrgMemberSchema.index({
teamId: 1,
tmbId: 1
});
} catch (error) {
console.log(error);
}
export const MongoOrgMemberModel = getMongoModel<OrgMemberSchemaType>(
OrgMemberCollectionName,
OrgMemberSchema
);

View File

@@ -0,0 +1,77 @@
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
import { OrgCollectionName } from '@fastgpt/global/support/user/team/org/constant';
import type { OrgSchemaType } from '@fastgpt/global/support/user/team/org/type';
import { connectionMongo, getMongoModel } from '../../../common/mongo';
import { OrgMemberCollectionName } from './orgMemberSchema';
import { getNanoid } from '@fastgpt/global/common/string/tools';
const { Schema } = connectionMongo;
export const OrgSchema = new Schema(
{
teamId: {
type: Schema.Types.ObjectId,
ref: TeamCollectionName,
required: true
},
pathId: {
// path id, only used for path
type: String,
required: true,
default: () => getNanoid()
},
path: {
type: String,
required: function (this: OrgSchemaType) {
return typeof this.path !== 'string';
} // allow empty string, but not null
},
name: {
type: String,
required: true
},
avatar: String,
description: String,
updateTime: {
type: Date,
default: () => new Date()
}
},
{
// Auto update updateTime
timestamps: {
updatedAt: 'updateTime'
}
}
);
OrgSchema.virtual('members', {
ref: OrgMemberCollectionName,
localField: '_id',
foreignField: 'orgId'
});
// OrgSchema.virtual('permission', {
// ref: ResourcePermissionCollectionName,
// localField: '_id',
// foreignField: 'orgId',
// justOne: true
// });
try {
OrgSchema.index({
teamId: 1,
path: 1
});
OrgSchema.index(
{
teamId: 1,
pathId: 1
},
{
unique: true
}
);
} catch (error) {
console.log(error);
}
export const MongoOrgModel = getMongoModel<OrgSchemaType>(OrgCollectionName, OrgSchema);

View File

@@ -6,6 +6,7 @@ import { connectionMongo, getMongoModel } from '../../common/mongo';
import type { ResourcePermissionType } from '@fastgpt/global/support/permission/type'; import type { ResourcePermissionType } from '@fastgpt/global/support/permission/type';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant'; import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { MemberGroupCollectionName } from './memberGroup/memberGroupSchema'; import { MemberGroupCollectionName } from './memberGroup/memberGroupSchema';
import { OrgCollectionName } from '@fastgpt/global/support/user/team/org/constant';
const { Schema } = connectionMongo; const { Schema } = connectionMongo;
export const ResourcePermissionCollectionName = 'resource_permissions'; export const ResourcePermissionCollectionName = 'resource_permissions';
@@ -23,6 +24,10 @@ export const ResourcePermissionSchema = new Schema({
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: MemberGroupCollectionName ref: MemberGroupCollectionName
}, },
orgId: {
type: Schema.Types.ObjectId,
ref: OrgCollectionName
},
resourceType: { resourceType: {
type: String, type: String,
enum: Object.values(PerResourceTypeEnum), enum: Object.values(PerResourceTypeEnum),
@@ -51,6 +56,12 @@ ResourcePermissionSchema.virtual('group', {
foreignField: '_id', foreignField: '_id',
justOne: true justOne: true
}); });
ResourcePermissionSchema.virtual('org', {
ref: OrgCollectionName,
localField: 'orgId',
foreignField: '_id',
justOne: true
});
try { try {
ResourcePermissionSchema.index( ResourcePermissionSchema.index(
@@ -70,6 +81,23 @@ try {
} }
); );
ResourcePermissionSchema.index(
{
resourceType: 1,
teamId: 1,
resourceId: 1,
orgId: 1
},
{
unique: true,
partialFilterExpression: {
orgId: {
$exists: true
}
}
}
);
ResourcePermissionSchema.index( ResourcePermissionSchema.index(
{ {
resourceType: 1, resourceType: 1,
@@ -87,6 +115,7 @@ try {
} }
); );
// Delete tmb permission
ResourcePermissionSchema.index({ ResourcePermissionSchema.index({
resourceType: 1, resourceType: 1,
teamId: 1, teamId: 1,

View File

@@ -19,9 +19,7 @@ export const checkDatasetLimit = async ({
if (!standardConstants) return; if (!standardConstants) return;
if (usedDatasetSize + insertLen >= datasetMaxSize) { if (usedDatasetSize + insertLen >= datasetMaxSize) {
return Promise.reject( return Promise.reject(TeamErrEnum.datasetSizeNotEnough);
`您的知识库容量为: ${datasetMaxSize}组,已使用: ${usedDatasetSize}组,导入当前文件需要: ${insertLen}组,请增加知识库容量后导入。`
);
} }
if (usedPoints >= totalPoints) { if (usedPoints >= totalPoints) {

View File

@@ -3,22 +3,10 @@ const { Schema } = connectionMongo;
import { hashStr } from '@fastgpt/global/common/string/tools'; import { hashStr } from '@fastgpt/global/common/string/tools';
import type { UserModelSchema } from '@fastgpt/global/support/user/type'; import type { UserModelSchema } from '@fastgpt/global/support/user/type';
import { UserStatusEnum, userStatusMap } from '@fastgpt/global/support/user/constant'; import { UserStatusEnum, userStatusMap } from '@fastgpt/global/support/user/constant';
import { getRandomUserAvatar } from '@fastgpt/global/support/user/utils';
export const userCollectionName = 'users'; export const userCollectionName = 'users';
const defaultAvatars = [
'/imgs/avatar/RoyalBlueAvatar.svg',
'/imgs/avatar/PurpleAvatar.svg',
'/imgs/avatar/AdoraAvatar.svg',
'/imgs/avatar/OrangeAvatar.svg',
'/imgs/avatar/RedAvatar.svg',
'/imgs/avatar/GrayModernAvatar.svg',
'/imgs/avatar/TealAvatar.svg',
'/imgs/avatar/GreenAvatar.svg',
'/imgs/avatar/BrightBlueAvatar.svg',
'/imgs/avatar/BlueAvatar.svg'
];
const UserSchema = new Schema({ const UserSchema = new Schema({
status: { status: {
type: String, type: String,
@@ -47,7 +35,7 @@ const UserSchema = new Schema({
}, },
avatar: { avatar: {
type: String, type: String,
default: defaultAvatars[Math.floor(Math.random() * defaultAvatars.length)] default: () => getRandomUserAvatar()
}, },
promotionRate: { promotionRate: {
@@ -78,11 +66,8 @@ const UserSchema = new Schema({
}); });
try { try {
// login
UserSchema.index({ username: 1, password: 1 }, { background: true });
// Admin charts // Admin charts
UserSchema.index({ createTime: -1 }, { background: true }); UserSchema.index({ createTime: -1 });
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }

View File

@@ -16,6 +16,8 @@ import { MongoMemberGroupModel } from '../../permission/memberGroup/memberGroupS
import { mongoSessionRun } from '../../../common/mongo/sessionRun'; import { mongoSessionRun } from '../../../common/mongo/sessionRun';
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant'; import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
import { getAIApi, openaiBaseUrl } from '../../../core/ai/config'; import { getAIApi, openaiBaseUrl } from '../../../core/ai/config';
import { createRootOrg } from '../../permission/org/controllers';
import { refreshSourceAvatar } from '../../../common/file/image/controller';
async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemType> { async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemType> {
const tmb = await MongoTeamMember.findOne(match).populate<{ team: TeamSchema }>('team').lean(); const tmb = await MongoTeamMember.findOne(match).populate<{ team: TeamSchema }>('team').lean();
@@ -32,6 +34,7 @@ async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemTyp
return { return {
userId: String(tmb.userId), userId: String(tmb.userId),
teamId: String(tmb.teamId), teamId: String(tmb.teamId),
teamAvatar: tmb.team.avatar,
teamName: tmb.team.name, teamName: tmb.team.name,
memberName: tmb.name, memberName: tmb.name,
avatar: tmb.team.avatar, avatar: tmb.team.avatar,
@@ -77,13 +80,11 @@ export async function createDefaultTeam({
userId, userId,
teamName = 'My Team', teamName = 'My Team',
avatar = '/icon/logo.svg', avatar = '/icon/logo.svg',
balance,
session session
}: { }: {
userId: string; userId: string;
teamName?: string; teamName?: string;
avatar?: string; avatar?: string;
balance?: number;
session: ClientSession; session: ClientSession;
}) { }) {
// auth default team // auth default team
@@ -100,7 +101,6 @@ export async function createDefaultTeam({
ownerId: userId, ownerId: userId,
name: teamName, name: teamName,
avatar, avatar,
balance,
createTime: new Date() createTime: new Date()
} }
], ],
@@ -132,15 +132,11 @@ export async function createDefaultTeam({
], ],
{ session } { session }
); );
console.log('create default team and group', userId); await createRootOrg({ teamId: tmb.teamId, session });
console.log('create default team, group and root org', userId);
return tmb; return tmb;
} else { } else {
console.log('default team exist', userId); console.log('default team exist', userId);
await MongoTeam.findByIdAndUpdate(tmb.teamId, {
$set: {
...(balance !== undefined && { balance })
}
});
} }
} }
@@ -215,7 +211,8 @@ export async function updateTeam({
return obj; return obj;
})(); })();
await MongoTeam.findByIdAndUpdate( // This is where we get the old team
const team = await MongoTeam.findByIdAndUpdate(
teamId, teamId,
{ {
$set: { $set: {
@@ -241,6 +238,8 @@ export async function updateTeam({
}, },
{ session } { session }
); );
await refreshSourceAvatar(avatar, team?.avatar, session);
} }
}); });
} }

Some files were not shown because too many files have changed in this diff Show More