Compare commits
39 Commits
v4.8.17-al
...
v4.8.18-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47f7b1a7a3 | ||
|
|
fadb3e3ceb | ||
|
|
1c0b323b1b | ||
|
|
b26345dcaa | ||
|
|
cef8487ca1 | ||
|
|
ed619edd47 | ||
|
|
93cf5bb372 | ||
|
|
f556fbf0d5 | ||
|
|
07cc849877 | ||
|
|
2066094047 | ||
|
|
2bf1fce32a | ||
|
|
5465ca642f | ||
|
|
c0d0c629c5 | ||
|
|
20c6c202d2 | ||
|
|
c8f60b47d0 | ||
|
|
8b2be89350 | ||
|
|
a401d4d612 | ||
|
|
d24d29ac48 | ||
|
|
9abbc16036 | ||
|
|
28710ac05b | ||
|
|
3412d7009d | ||
|
|
c3480b0ffa | ||
|
|
f89212f35f | ||
|
|
03d1c6a651 | ||
|
|
5d1d4ff64f | ||
|
|
fd9600c6f8 | ||
|
|
efecfd44c3 | ||
|
|
1fc77a126a | ||
|
|
bb669ca3ff | ||
|
|
72ed72e595 | ||
|
|
e5735fddd1 | ||
|
|
fa92ef86b9 | ||
|
|
f39ea04178 | ||
|
|
bd4893ced9 | ||
|
|
b75e807f24 | ||
|
|
b2fdefdc0c | ||
|
|
896fec4b82 | ||
|
|
50bf7f9a3b | ||
|
|
da2831b948 |
4
.github/workflows/preview-fastgpt-image.yml
vendored
4
.github/workflows/preview-fastgpt-image.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
password: ${{ secrets.GH_PAT }}
|
||||
- name: Set DOCKER_REPO_TAGGED based on branch or tag
|
||||
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
|
||||
env:
|
||||
DOCKER_REPO_TAGGED: ${{ env.DOCKER_REPO_TAGGED }}
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
docker buildx build \
|
||||
-f projects/app/Dockerfile \
|
||||
--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" \
|
||||
--push \
|
||||
--cache-from=type=local,src=/tmp/.buildx-cache \
|
||||
|
||||
@@ -215,4 +215,4 @@ https://github.com/labring/FastGPT/assets/15308462/7d3a38df-eb0e-4388-9250-2409b
|
||||
1. 允许作为后台服务直接商用,但不允许提供 SaaS 服务。
|
||||
2. 未经商业授权,任何形式的商用服务均需保留相关版权信息。
|
||||
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/)
|
||||
|
||||
BIN
docSite/assets/imgs/dataset1.png
Normal file
BIN
docSite/assets/imgs/dataset1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
BIN
docSite/assets/imgs/dataset2.png
Normal file
BIN
docSite/assets/imgs/dataset2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
@@ -168,6 +168,7 @@ weight: 707
|
||||
"reRankModels": [],
|
||||
"audioSpeechModels": [
|
||||
{
|
||||
"provider": "OpenAI",
|
||||
"model": "tts-1",
|
||||
"name": "OpenAI TTS1",
|
||||
"charsPointsPrice": 0,
|
||||
@@ -182,6 +183,7 @@ weight: 707
|
||||
}
|
||||
],
|
||||
"whisperModel": {
|
||||
"provider": "OpenAI",
|
||||
"model": "whisper-1",
|
||||
"name": "Whisper1",
|
||||
"charsPointsPrice": 0
|
||||
@@ -201,7 +203,9 @@ weight: 707
|
||||
- OpenAI
|
||||
- Claude
|
||||
- Gemini
|
||||
- Meta
|
||||
- MistralAI
|
||||
- AliCloud - 阿里云
|
||||
- Qwen - 通义千问
|
||||
- Doubao - 豆包
|
||||
- ChatGLM - 智谱
|
||||
@@ -213,7 +217,10 @@ weight: 707
|
||||
- Baichuan - 百川
|
||||
- Yi - 零一万物
|
||||
- Ernie - 文心一言
|
||||
- StepFun - 阶跃星辰
|
||||
- Ollama
|
||||
- BAAI - 智源研究院
|
||||
- FishAudio
|
||||
- Other - 其他
|
||||
|
||||
|
||||
|
||||
@@ -19,6 +19,10 @@ images: []
|
||||
|
||||
## 二、通用问题
|
||||
|
||||
### 本地部署的限制
|
||||
|
||||
具体内容参考https://fael3z0zfze.feishu.cn/wiki/OFpAw8XzAi36Guk8dfucrCKUnjg。
|
||||
|
||||
### 能否纯本地运行
|
||||
|
||||
可以。需要准备好向量模型和LLM模型。
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.17(进行中)'
|
||||
title: 'V4.8.17(包含升级脚本)'
|
||||
description: 'FastGPT V4.8.17 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
@@ -7,7 +7,16 @@ toc: true
|
||||
weight: 807
|
||||
---
|
||||
|
||||
## 运行升级脚本
|
||||
## 更新指南
|
||||
|
||||
### 1. 更新镜像:
|
||||
|
||||
- 更新 fastgpt 镜像 tag: v4.8.17-fix-title
|
||||
- 更新 fastgpt-pro 商业版镜像 tag: v4.8.17
|
||||
- Sandbox 镜像无需更新
|
||||
|
||||
|
||||
### 2. 运行升级脚本
|
||||
|
||||
从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`;{{host}} 替换成**FastGPT 域名**。
|
||||
|
||||
@@ -19,6 +28,11 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4817' \
|
||||
|
||||
会将用户绑定的 OpenAI 账号移动到团队中。
|
||||
|
||||
|
||||
## 调整 completions 接口返回值
|
||||
|
||||
/api/v1/chat/completions 接口返回值调整,对话节点、工具节点等使用到模型的节点,将不再返回 `tokens` 字段,改为返回 `inputTokens` 和 `outputTokens` 字段,分别表示输入和输出的 Token 数量。
|
||||
|
||||
## 完整更新内容
|
||||
|
||||
1. 新增 - 简易模式工具调用支持数组类型插件。
|
||||
@@ -27,10 +41,12 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4817' \
|
||||
4. 新增 - 商业版支持后台配置模板市场。
|
||||
5. 新增 - 商业版支持后台配置自定义工作流变量,用于与业务系统鉴权打通。
|
||||
6. 新增 - 搜索测试接口支持问题优化。
|
||||
7. 优化 - Markdown 大小测试,超出 20 万字符不使用 Markdown 组件,避免崩溃。
|
||||
8. 优化 - 知识库搜索参数,滑动条支持输入模式,可以更精准的控制。
|
||||
9. 优化 - 可用模型展示
|
||||
10. 优化 - Mongo 查询语句,增加 virtual 字段。
|
||||
11. 修复 - 文件返回接口缺少 Content-Length 头,导致通过非同源文件上传时,阿里 vision 模型无法识别图片。
|
||||
12. 修复 - 去除判断器两端字符串隐藏换行符,避免判断器失效。
|
||||
13. 修复 - 变量更新节点,手动输入更新内容时候,非字符串类型数据类型无法自动转化。
|
||||
7. 新增 - 工作流中 Input Token 和 Output Token 分开记录展示。并修复部分请求未记录输出 Token 计费问题。
|
||||
8. 优化 - Markdown 大小测试,超出 20 万字符不使用 Markdown 组件,避免崩溃。
|
||||
9. 优化 - 知识库搜索参数,滑动条支持输入模式,可以更精准的控制。
|
||||
10. 优化 - 可用模型展示UI。
|
||||
11. 优化 - Mongo 查询语句,增加 virtual 字段。
|
||||
12. 修复 - 文件返回接口缺少 Content-Length 头,导致通过非同源文件上传时,阿里 vision 模型无法识别图片。
|
||||
13. 修复 - 去除判断器两端字符串隐藏换行符,避免判断器失效。
|
||||
14. 修复 - 变量更新节点,手动输入更新内容时候,非字符串类型数据类型无法自动转化。
|
||||
15. 修复 - 豆包模型无法工具调用。
|
||||
|
||||
37
docSite/content/zh-cn/docs/development/upgrading/4818.md
Normal file
37
docSite/content/zh-cn/docs/development/upgrading/4818.md
Normal 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. 修复 - 插件计费错误。
|
||||
@@ -13,4 +13,12 @@ weight: 908
|
||||
|
||||
但是,当连续问题之间的关联性较小,模型判断的准确度可能会受到限制。在这种情况下,我们可以引入全局变量的概念来记录分类结果。在后续的问题分类阶段,首先检查全局变量是否存有分类结果。如果有,那么直接沿用该结果;若没有,则让模型自行判断。
|
||||
|
||||
建议:构建批量运行脚本进行测试,评估问题分类的准确性。
|
||||
建议:构建批量运行脚本进行测试,评估问题分类的准确性。
|
||||
|
||||
## 系统编排配置中的定时执行,如果用户打开分享的连接,停留在那个页面,定时执行触发问题
|
||||
|
||||
发布后,后台生效。
|
||||
|
||||
## AI对话回答要求中的Markdown语法取消
|
||||
|
||||
在针对知识库的回答要求里有, 要给它配置提示词,不然他就是默认的,默认的里面就有该语法。
|
||||
@@ -14,4 +14,51 @@ weight: 910
|
||||
## 知识库配置里的文件处理模型是什么?与索引模型有什么区别?
|
||||
|
||||
* **文件处理模型**:用于数据处理的【增强处理】和【问答拆分】。在【增强处理】中,生成相关问题和摘要,在【问答拆分】中执行问答对生成。
|
||||
* **索引模型**:用于向量化,即通过对文本数据进行处理和组织,构建出一个能够快速查询的数据结构。
|
||||
* **索引模型**:用于向量化,即通过对文本数据进行处理和组织,构建出一个能够快速查询的数据结构。
|
||||
|
||||
## 基于知识库的查询,但是问题相关的答案过多。ai回答到一半就不继续回答。
|
||||
|
||||
FastGPT回复长度计算公式:
|
||||
|
||||
最大回复=min(配置的最大回复(内置的限制),最大上下文(输入和输出的总和)-历史记录)
|
||||
|
||||
18K模型->输入与输出的和
|
||||
|
||||
输出增多->输入减小
|
||||
|
||||
所以可以:
|
||||
|
||||
1. 检查配置的最大回复(回复上限)
|
||||
2. 减小输入来增大输出,即减小历史记录,在工作流其实也就是“聊天记录”
|
||||
|
||||
配置的最大回复:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
1. 私有化部署的时候,后台配模型参数,可以在配置最大上文时候,预留一些空间,比如 128000 的模型,可以只配置 120000, 剩余的空间后续会被安排给输出
|
||||
|
||||
|
||||
## 受到模型上下文的限制,有时候达不到聊天记录的轮次,连续对话字数过多就会报上下文不够的错误。
|
||||
|
||||
FastGPT回复长度计算公式:
|
||||
|
||||
最大回复=min(配置的最大回复(内置的限制),最大上下文(输入和输出的总和)-历史记录)
|
||||
|
||||
18K模型->输入与输出的和
|
||||
|
||||
输出增多->输入减小
|
||||
|
||||
所以可以:
|
||||
|
||||
1. 检查配置的最大回复(回复上限)
|
||||
2. 减小输入来增大输出,即减小历史记录,在工作流其实也就是“聊天记录”
|
||||
|
||||
配置的最大回复:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
1. 私有化部署的时候,后台配模型参数,可以在配置最大上文时候,预留一些空间,比如 128000 的模型,可以只配置 120000, 剩余的空间后续会被安排给输出
|
||||
@@ -160,6 +160,18 @@ default_doi_resolver: 'oadoi.org'
|
||||
}
|
||||
```
|
||||
|
||||
* 搜索结果为空时会返回友好提示:
|
||||
|
||||
```Bash
|
||||
{
|
||||
"result": "[]",
|
||||
"error": {
|
||||
"message": "No search results",
|
||||
"code": 500
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* 失败时通过 Promise.reject 可能返回错误信息:
|
||||
|
||||
```Bash
|
||||
|
||||
@@ -114,15 +114,15 @@ services:
|
||||
# fastgpt
|
||||
sandbox:
|
||||
container_name: sandbox
|
||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.16 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.16 # 阿里云
|
||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.17 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.17 # 阿里云
|
||||
networks:
|
||||
- fastgpt
|
||||
restart: always
|
||||
fastgpt:
|
||||
container_name: fastgpt
|
||||
image: ghcr.io/labring/fastgpt:v4.8.16 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.16 # 阿里云
|
||||
image: ghcr.io/labring/fastgpt:v4.8.17 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.17 # 阿里云
|
||||
ports:
|
||||
- 3000:3000
|
||||
networks:
|
||||
|
||||
@@ -72,15 +72,15 @@ services:
|
||||
# fastgpt
|
||||
sandbox:
|
||||
container_name: sandbox
|
||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.16 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.16 # 阿里云
|
||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.17 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.17 # 阿里云
|
||||
networks:
|
||||
- fastgpt
|
||||
restart: always
|
||||
fastgpt:
|
||||
container_name: fastgpt
|
||||
image: ghcr.io/labring/fastgpt:v4.8.16 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.16 # 阿里云
|
||||
image: ghcr.io/labring/fastgpt:v4.8.17 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.17 # 阿里云
|
||||
ports:
|
||||
- 3000:3000
|
||||
networks:
|
||||
|
||||
@@ -53,15 +53,15 @@ services:
|
||||
wait $$!
|
||||
sandbox:
|
||||
container_name: sandbox
|
||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.16 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.16 # 阿里云
|
||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.17 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.17 # 阿里云
|
||||
networks:
|
||||
- fastgpt
|
||||
restart: always
|
||||
fastgpt:
|
||||
container_name: fastgpt
|
||||
image: ghcr.io/labring/fastgpt:v4.8.16 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.16 # 阿里云
|
||||
image: ghcr.io/labring/fastgpt:v4.8.17 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.17 # 阿里云
|
||||
ports:
|
||||
- 3000:3000
|
||||
networks:
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import { i18nT } from '../../../../web/i18n/utils';
|
||||
import { ErrType } from '../errorCode';
|
||||
|
||||
/* dataset: 507000 */
|
||||
const startCode = 507000;
|
||||
export enum CommonErrEnum {
|
||||
invalidParams = 'invalidParams',
|
||||
fileNotFound = 'fileNotFound',
|
||||
unAuthFile = 'unAuthFile',
|
||||
missingParams = 'missingParams',
|
||||
inheritPermissionError = 'inheritPermissionError'
|
||||
}
|
||||
const datasetErr = [
|
||||
{
|
||||
statusText: CommonErrEnum.fileNotFound,
|
||||
message: i18nT('common:error.invalid_params')
|
||||
},
|
||||
{
|
||||
statusText: CommonErrEnum.fileNotFound,
|
||||
message: 'error.fileNotFound'
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { ErrType } from '../errorCode';
|
||||
import { i18nT } from '../../../../web/i18n/utils';
|
||||
import type { ErrType } from '../errorCode';
|
||||
/* team: 500000 */
|
||||
export enum TeamErrEnum {
|
||||
notUser = 'notUser',
|
||||
teamOverSize = 'teamOverSize',
|
||||
unAuthTeam = 'unAuthTeam',
|
||||
teamMemberOverSize = 'teamMemberOverSize',
|
||||
aiPointsNotEnough = 'aiPointsNotEnough',
|
||||
datasetSizeNotEnough = 'datasetSizeNotEnough',
|
||||
datasetAmountNotEnough = 'datasetAmountNotEnough',
|
||||
@@ -14,11 +16,22 @@ export enum TeamErrEnum {
|
||||
groupNameEmpty = 'groupNameEmpty',
|
||||
groupNameDuplicate = 'groupNameDuplicate',
|
||||
groupNotExist = 'groupNotExist',
|
||||
orgMemberNotExist = 'orgMemberNotExist',
|
||||
orgMemberDuplicated = 'orgMemberDuplicated',
|
||||
orgNotExist = 'orgNotExist',
|
||||
orgParentNotExist = 'orgParentNotExist',
|
||||
cannotMoveToSubPath = 'cannotMoveToSubPath',
|
||||
cannotModifyRootOrg = 'cannotModifyRootOrg',
|
||||
cannotDeleteNonEmptyOrg = 'cannotDeleteNonEmptyOrg',
|
||||
cannotDeleteDefaultGroup = 'cannotDeleteDefaultGroup',
|
||||
userNotActive = 'userNotActive'
|
||||
}
|
||||
|
||||
const teamErr = [
|
||||
{
|
||||
statusText: TeamErrEnum.notUser,
|
||||
message: i18nT('common:code_error.team_error.not_user')
|
||||
},
|
||||
{
|
||||
statusText: TeamErrEnum.teamOverSize,
|
||||
message: i18nT('common:code_error.team_error.over_size')
|
||||
@@ -71,6 +84,34 @@ const teamErr = [
|
||||
{
|
||||
statusText: TeamErrEnum.userNotActive,
|
||||
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')
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -2,25 +2,16 @@ import { ErrType } from '../errorCode';
|
||||
import { i18nT } from '../../../../web/i18n/utils';
|
||||
/* team: 503000 */
|
||||
export enum UserErrEnum {
|
||||
unAuthUser = 'unAuthUser',
|
||||
unAuthRole = 'unAuthRole',
|
||||
binVisitor = 'binVisitor',
|
||||
balanceNotEnough = 'balanceNotEnough',
|
||||
unAuthSso = 'unAuthSso'
|
||||
}
|
||||
const errList = [
|
||||
{
|
||||
statusText: UserErrEnum.unAuthUser,
|
||||
message: i18nT('common:code_error.user_error.un_auth_user')
|
||||
},
|
||||
{
|
||||
statusText: UserErrEnum.binVisitor,
|
||||
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,
|
||||
message: i18nT('common:code_error.user_error.balance_not_enough')
|
||||
|
||||
5
packages/global/common/file/api.d.ts
vendored
5
packages/global/common/file/api.d.ts
vendored
@@ -1,10 +1,7 @@
|
||||
import { MongoImageTypeEnum } from './image/constants';
|
||||
import { OutLinkChatAuthProps } from '../../support/permission/chat.d';
|
||||
|
||||
export type preUploadImgProps = OutLinkChatAuthProps & {
|
||||
type: `${MongoImageTypeEnum}`;
|
||||
|
||||
expiredTime?: Date;
|
||||
// expiredTime?: Date;
|
||||
metadata?: Record<string, any>;
|
||||
};
|
||||
export type UploadImgProps = preUploadImgProps & {
|
||||
|
||||
@@ -1,61 +1,5 @@
|
||||
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 FolderImgUrl = '/imgs/files/folder.svg';
|
||||
export const HttpPluginImgUrl = '/imgs/app/httpPluginFill.svg';
|
||||
|
||||
4
packages/global/common/file/image/type.d.ts
vendored
4
packages/global/common/file/image/type.d.ts
vendored
@@ -1,12 +1,8 @@
|
||||
import { MongoImageTypeEnum } from './constants';
|
||||
|
||||
export type MongoImageSchemaType = {
|
||||
_id: string;
|
||||
teamId: string;
|
||||
binary: Buffer;
|
||||
createTime: Date;
|
||||
expiredTime?: Date;
|
||||
type: `${MongoImageTypeEnum}`;
|
||||
|
||||
metadata?: {
|
||||
mime?: string; // image mime type.
|
||||
|
||||
@@ -2,6 +2,7 @@ import { detect } from 'jschardet';
|
||||
import { documentFileType, imageFileType } from './constants';
|
||||
import { ChatFileTypeEnum } from '../../core/chat/constants';
|
||||
import { UserChatItemValueItemType } from '../../core/chat/type';
|
||||
import * as fs from 'fs';
|
||||
|
||||
export const formatFileSize = (bytes: number): string => {
|
||||
if (bytes === 0) return '0 B';
|
||||
@@ -16,6 +17,22 @@ export const formatFileSize = (bytes: number): string => {
|
||||
export const detectFileEncoding = (buffer: Buffer) => {
|
||||
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
|
||||
export const parseUrlToFileType = (url: string): UserChatItemValueItemType['file'] | undefined => {
|
||||
|
||||
@@ -25,17 +25,22 @@ export const simpleText = (text = '') => {
|
||||
return text;
|
||||
};
|
||||
|
||||
/*
|
||||
replace {{variable}} to value
|
||||
*/
|
||||
export const valToStr = (val: any) => {
|
||||
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>) {
|
||||
if (typeof text !== 'string') return text;
|
||||
|
||||
for (const key in obj) {
|
||||
const val = obj[key];
|
||||
const formatVal = typeof val === 'object' ? JSON.stringify(val) : String(val);
|
||||
|
||||
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), formatVal);
|
||||
const formatVal = valToStr(val);
|
||||
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), () => formatVal);
|
||||
}
|
||||
return text || '';
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
export const HUMAN_ICON = `/icon/human.svg`;
|
||||
export const LOGO_ICON = `/icon/logo.svg`;
|
||||
export const HUGGING_FACE_ICON = `/imgs/model/huggingface.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';
|
||||
|
||||
@@ -73,6 +73,11 @@ export type FastGPTFeConfigsType = {
|
||||
google?: string;
|
||||
wechat?: string;
|
||||
dingtalk?: string;
|
||||
wecom?: {
|
||||
corpid?: string;
|
||||
agentid?: string;
|
||||
secret?: string;
|
||||
};
|
||||
microsoft?: {
|
||||
clientId?: string;
|
||||
tenantId?: string;
|
||||
|
||||
24
packages/global/core/ai/model.d.ts
vendored
24
packages/global/core/ai/model.d.ts
vendored
@@ -1,6 +1,13 @@
|
||||
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;
|
||||
model: string;
|
||||
name: string;
|
||||
@@ -10,8 +17,6 @@ export type LLMModelItemType = {
|
||||
quoteMaxToken: number;
|
||||
maxTemperature: number;
|
||||
|
||||
charsPointsPrice: number; // 1k chars=n points
|
||||
|
||||
censor?: boolean;
|
||||
vision?: boolean;
|
||||
|
||||
@@ -33,13 +38,12 @@ export type LLMModelItemType = {
|
||||
fieldMap?: Record<string, string>;
|
||||
};
|
||||
|
||||
export type VectorModelItemType = {
|
||||
export type VectorModelItemType = PriceType & {
|
||||
provider: ModelProviderIdType;
|
||||
model: string; // model name
|
||||
name: string; // show name
|
||||
avatar?: string;
|
||||
defaultToken: number; // split text default token
|
||||
charsPointsPrice: number; // 1k tokens=n points
|
||||
maxToken: number; // model max token
|
||||
weight: number; // training weight
|
||||
hidden?: boolean; // Disallow creation
|
||||
@@ -48,25 +52,23 @@ export type VectorModelItemType = {
|
||||
queryConfig?: Record<string, any>; // Custom parameters for query
|
||||
};
|
||||
|
||||
export type ReRankModelItemType = {
|
||||
export type ReRankModelItemType = PriceType & {
|
||||
provider: ModelProviderIdType;
|
||||
model: string;
|
||||
name: string;
|
||||
charsPointsPrice: number;
|
||||
requestUrl: string;
|
||||
requestAuth: string;
|
||||
};
|
||||
|
||||
export type AudioSpeechModelType = {
|
||||
export type AudioSpeechModelType = PriceType & {
|
||||
provider: ModelProviderIdType;
|
||||
model: string;
|
||||
name: string;
|
||||
charsPointsPrice: number;
|
||||
voices: { label: string; value: string; bufferId: string }[];
|
||||
};
|
||||
|
||||
export type STTModelType = {
|
||||
export type STTModelType = PriceType & {
|
||||
provider: ModelProviderIdType;
|
||||
model: string;
|
||||
name: string;
|
||||
charsPointsPrice: number; // 60s = n points
|
||||
};
|
||||
|
||||
@@ -4,20 +4,25 @@ export type ModelProviderIdType =
|
||||
| 'OpenAI'
|
||||
| 'Claude'
|
||||
| 'Gemini'
|
||||
| 'Meta'
|
||||
| 'MistralAI'
|
||||
| 'Groq'
|
||||
| 'AliCloud'
|
||||
| 'Qwen'
|
||||
| 'Doubao'
|
||||
| 'ChatGLM'
|
||||
| 'DeepSeek'
|
||||
| 'Ernie'
|
||||
| 'Moonshot'
|
||||
| 'MiniMax'
|
||||
| 'SparkDesk'
|
||||
| 'Hunyuan'
|
||||
| 'Baichuan'
|
||||
| 'StepFun'
|
||||
| 'Yi'
|
||||
| 'Ernie'
|
||||
| 'Ollama'
|
||||
| 'BAAI'
|
||||
| 'FishAudio'
|
||||
| 'Other';
|
||||
|
||||
export type ModelProviderType = {
|
||||
@@ -42,6 +47,11 @@ export const ModelProviderList: ModelProviderType[] = [
|
||||
name: 'Gemini',
|
||||
avatar: 'model/gemini'
|
||||
},
|
||||
{
|
||||
id: 'Meta',
|
||||
name: 'Meta',
|
||||
avatar: 'model/meta'
|
||||
},
|
||||
{
|
||||
id: 'MistralAI',
|
||||
name: 'MistralAI',
|
||||
@@ -52,6 +62,11 @@ export const ModelProviderList: ModelProviderType[] = [
|
||||
name: 'Groq',
|
||||
avatar: 'model/groq'
|
||||
},
|
||||
{
|
||||
id: 'AliCloud',
|
||||
name: i18nT('common:model_alicloud'),
|
||||
avatar: 'model/alicloud'
|
||||
},
|
||||
{
|
||||
id: 'Qwen',
|
||||
name: i18nT('common:model_qwen'),
|
||||
@@ -67,6 +82,11 @@ export const ModelProviderList: ModelProviderType[] = [
|
||||
name: i18nT('common:model_chatglm'),
|
||||
avatar: 'model/chatglm'
|
||||
},
|
||||
{
|
||||
id: 'Ernie',
|
||||
name: i18nT('common:model_ernie'),
|
||||
avatar: 'model/ernie'
|
||||
},
|
||||
{
|
||||
id: 'DeepSeek',
|
||||
name: 'DeepSeek',
|
||||
@@ -97,21 +117,32 @@ export const ModelProviderList: ModelProviderType[] = [
|
||||
name: i18nT('common:model_baichuan'),
|
||||
avatar: 'model/baichuan'
|
||||
},
|
||||
{
|
||||
id: 'StepFun',
|
||||
name: i18nT('common:model_stepfun'),
|
||||
avatar: 'model/stepfun'
|
||||
},
|
||||
{
|
||||
id: 'Yi',
|
||||
name: i18nT('common:model_yi'),
|
||||
avatar: 'model/yi'
|
||||
},
|
||||
{
|
||||
id: 'Ernie',
|
||||
name: i18nT('common:model_ernie'),
|
||||
avatar: 'model/ernie'
|
||||
},
|
||||
|
||||
{
|
||||
id: 'Ollama',
|
||||
name: 'Ollama',
|
||||
avatar: 'model/ollama'
|
||||
},
|
||||
{
|
||||
id: 'BAAI',
|
||||
name: i18nT('common:model_baai'),
|
||||
avatar: 'model/BAAI'
|
||||
},
|
||||
{
|
||||
id: 'FishAudio',
|
||||
name: 'FishAudio',
|
||||
avatar: 'model/fishaudio'
|
||||
},
|
||||
{
|
||||
id: 'Other',
|
||||
name: i18nT('common:model_other'),
|
||||
|
||||
5
packages/global/core/app/collaborator.d.ts
vendored
5
packages/global/core/app/collaborator.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
import { RequireOnlyOne } from '../../common/type/utils';
|
||||
import type { RequireOnlyOne } from '../../common/type/utils';
|
||||
import {
|
||||
UpdateClbPermissionProps,
|
||||
type UpdateClbPermissionProps,
|
||||
UpdatePermissionBody
|
||||
} from '../../support/permission/collaborator';
|
||||
import { PermissionValueType } from '../../support/permission/type';
|
||||
@@ -14,4 +14,5 @@ export type AppCollaboratorDeleteParams = {
|
||||
} & RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
@@ -11,4 +11,5 @@ export type DatasetCollaboratorDeleteParams = {
|
||||
} & RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
9
packages/global/core/dataset/type.d.ts
vendored
9
packages/global/core/dataset/type.d.ts
vendored
@@ -112,6 +112,15 @@ export type DatasetDataSchemaType = {
|
||||
rebuilding?: boolean;
|
||||
};
|
||||
|
||||
export type DatasetDataTextSchemaType = {
|
||||
_id: string;
|
||||
teamId: string;
|
||||
datasetId: string;
|
||||
collectionId: string;
|
||||
dataId: string;
|
||||
fullTextToken: string;
|
||||
};
|
||||
|
||||
export type DatasetTrainingSchemaType = {
|
||||
_id: string;
|
||||
userId: string;
|
||||
|
||||
@@ -107,7 +107,9 @@ export type DispatchNodeResponseType = {
|
||||
mergeSignId?: string;
|
||||
|
||||
// bill
|
||||
tokens?: number;
|
||||
tokens?: number; // deprecated
|
||||
inputTokens?: number;
|
||||
outputTokens?: number;
|
||||
model?: string;
|
||||
contextTotalLen?: number;
|
||||
totalPoints?: number;
|
||||
@@ -157,6 +159,8 @@ export type DispatchNodeResponseType = {
|
||||
|
||||
// tool
|
||||
toolCallTokens?: number;
|
||||
toolCallInputTokens?: number;
|
||||
toolCallOutputTokens?: number;
|
||||
toolDetail?: ChatHistoryItemResType[];
|
||||
toolStop?: boolean;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { isValidReferenceValueFormat } from '../utils';
|
||||
import { FlowNodeOutputItemType, ReferenceValueType } from '../type/io';
|
||||
import { ChatItemType, NodeOutputItemType } from '../../../core/chat/type';
|
||||
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 => {
|
||||
let limit = 10;
|
||||
@@ -343,11 +343,7 @@ export function replaceEditorVariable({
|
||||
if (input) return getReferenceVariableValue({ value: input.value, nodes, variables });
|
||||
})();
|
||||
|
||||
const formatVal = (() => {
|
||||
if (variableVal === undefined) return 'undefined';
|
||||
if (variableVal === null) return 'null';
|
||||
return typeof variableVal === 'object' ? JSON.stringify(variableVal) : String(variableVal);
|
||||
})();
|
||||
const formatVal = valToStr(variableVal);
|
||||
|
||||
const regex = new RegExp(`\\{\\{\\$(${nodeId}\\.${id})\\$\\}\\}`, 'g');
|
||||
text = text.replace(regex, () => formatVal);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"next": "14.2.5",
|
||||
"openai": "4.61.0",
|
||||
"openapi-types": "^12.1.3",
|
||||
"json5": "^2.2.3",
|
||||
"timezones-list": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -10,22 +10,18 @@ export type CollaboratorItemType = {
|
||||
} & RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
export type UpdateClbPermissionProps = {
|
||||
members?: string[];
|
||||
groups?: string[];
|
||||
orgs?: string[];
|
||||
permission: PermissionValueType;
|
||||
};
|
||||
|
||||
export type DeleteClbPermissionProps = RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
}>;
|
||||
|
||||
export type UpdatePermissionBody = {
|
||||
permission: PermissionValueType;
|
||||
} & RequireOnlyOne<{
|
||||
memberId: string;
|
||||
groupId: string;
|
||||
export type DeletePermissionQuery = RequireOnlyOne<{
|
||||
tmbId?: string;
|
||||
groupId?: string;
|
||||
orgId?: string;
|
||||
}>;
|
||||
|
||||
4
packages/global/support/permission/type.d.ts
vendored
4
packages/global/support/permission/type.d.ts
vendored
@@ -1,8 +1,9 @@
|
||||
import { UserModelSchema } from '../user/type';
|
||||
import { RequireOnlyOne } from '../../common/type/utils';
|
||||
import { TeamMemberSchema } from '../user/team/type';
|
||||
import { AuthUserTypeEnum, PermissionKeyEnum, PerResourceTypeEnum } from './constant';
|
||||
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.
|
||||
// It is spired by the permission system in Linux.
|
||||
@@ -29,6 +30,7 @@ export type ResourcePermissionType = {
|
||||
} & RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
export type ResourcePerWithTmbWithUser = Omit<ResourcePermissionType, 'tmbId'> & {
|
||||
|
||||
@@ -17,5 +17,6 @@ export enum OAuthEnum {
|
||||
wechat = 'wechat',
|
||||
microsoft = 'microsoft',
|
||||
dingtalk = 'dingtalk',
|
||||
wecom = 'wecom',
|
||||
sso = 'sso'
|
||||
}
|
||||
|
||||
3
packages/global/support/user/login/constants.ts
Normal file
3
packages/global/support/user/login/constants.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function checkIsWecomTerminal() {
|
||||
return /wxwork/i.test(navigator.userAgent);
|
||||
}
|
||||
32
packages/global/support/user/team/org/api.d.ts
vendored
Normal file
32
packages/global/support/user/team/org/api.d.ts
vendored
Normal 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;
|
||||
// };
|
||||
12
packages/global/support/user/team/org/constant.ts
Normal file
12
packages/global/support/user/team/org/constant.ts
Normal 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'
|
||||
// }
|
||||
25
packages/global/support/user/team/org/type.d.ts
vendored
Normal file
25
packages/global/support/user/team/org/type.d.ts
vendored
Normal 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;
|
||||
};
|
||||
3
packages/global/support/user/team/type.d.ts
vendored
3
packages/global/support/user/team/type.d.ts
vendored
@@ -55,10 +55,11 @@ export type TeamMemberWithTeamAndUserSchema = TeamMemberSchema & {
|
||||
export type TeamTmbItemType = {
|
||||
userId: string;
|
||||
teamId: string;
|
||||
teamAvatar?: string;
|
||||
teamName: string;
|
||||
memberName: string;
|
||||
avatar: string;
|
||||
balance: number;
|
||||
balance?: number;
|
||||
tmbId: string;
|
||||
teamDomain: string;
|
||||
defaultTeam: boolean;
|
||||
|
||||
16
packages/global/support/user/utils.ts
Normal file
16
packages/global/support/user/utils.ts
Normal 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)];
|
||||
};
|
||||
@@ -23,7 +23,8 @@ export type BillSchemaType = {
|
||||
};
|
||||
|
||||
export type ChatNodeUsageType = {
|
||||
tokens?: number;
|
||||
inputTokens?: number;
|
||||
outputTokens?: number;
|
||||
totalPoints: number;
|
||||
moduleName: string;
|
||||
model?: string;
|
||||
|
||||
@@ -2,9 +2,13 @@ import { CreateUsageProps } from './api';
|
||||
import { UsageSourceEnum } from './constants';
|
||||
|
||||
export type UsageListItemCountType = {
|
||||
tokens?: number;
|
||||
inputTokens?: number;
|
||||
outputTokens?: number;
|
||||
charsLength?: number;
|
||||
duration?: number;
|
||||
|
||||
// deprecated
|
||||
tokens?: number;
|
||||
};
|
||||
export type UsageListItemType = UsageListItemCountType & {
|
||||
moduleName: string;
|
||||
|
||||
@@ -35,24 +35,26 @@ export const list = [...staticPluginList, ...packagePluginList];
|
||||
|
||||
/* Get plugins */
|
||||
export const getCommunityPlugins = () => {
|
||||
return list.map<SystemPluginTemplateItemType>((name) => {
|
||||
const config = require(`./src/${name}/template.json`);
|
||||
return Promise.all(
|
||||
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 parentId =
|
||||
parentIdList.length > 0 ? `${PluginSourceEnum.community}-${parentIdList.join('/')}` : null;
|
||||
const parentIdList = name.split('/').slice(0, -1);
|
||||
const parentId =
|
||||
parentIdList.length > 0 ? `${PluginSourceEnum.community}-${parentIdList.join('/')}` : null;
|
||||
|
||||
return {
|
||||
...config,
|
||||
id: `${PluginSourceEnum.community}-${name}`,
|
||||
isFolder,
|
||||
parentId,
|
||||
isActive: true,
|
||||
isOfficial: true
|
||||
};
|
||||
});
|
||||
return {
|
||||
...config,
|
||||
id: `${PluginSourceEnum.community}-${name}`,
|
||||
isFolder,
|
||||
parentId,
|
||||
isActive: true,
|
||||
isOfficial: true
|
||||
};
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const getSystemPluginTemplates = () => {
|
||||
|
||||
@@ -4,8 +4,8 @@ import { SystemPluginSpecialResponse } from '../../../type.d';
|
||||
|
||||
type Props = {
|
||||
title: string;
|
||||
xAxis: string;
|
||||
yAxis: string;
|
||||
xAxis: string[];
|
||||
yAxis: string[];
|
||||
chartType: string;
|
||||
};
|
||||
|
||||
@@ -27,7 +27,12 @@ type Option = {
|
||||
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会出现生成图片无法正常展示,有高手可以帮忙解决
|
||||
const chart = echarts.init(undefined, undefined, {
|
||||
renderer: 'svg', // 必须使用 SVG 模式
|
||||
@@ -36,21 +41,11 @@ const generateChart = async (title: string, xAxis: string, yAxis: string, chartT
|
||||
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 = {
|
||||
backgroundColor: '#f5f5f5',
|
||||
title: { text: title },
|
||||
tooltip: {},
|
||||
xAxis: { data: parsedXAxis },
|
||||
xAxis: { data: xAxis },
|
||||
yAxis: {},
|
||||
series: [] // 初始化为空数组
|
||||
};
|
||||
@@ -58,18 +53,18 @@ const generateChart = async (title: string, xAxis: string, yAxis: string, chartT
|
||||
// 根据 chartType 生成不同的图表
|
||||
switch (chartType) {
|
||||
case '柱状图':
|
||||
option.series.push({ name: 'Sample', type: 'bar', data: parsedYAxis });
|
||||
option.series.push({ name: 'Sample', type: 'bar', data: yAxis.map(Number) });
|
||||
break;
|
||||
case '折线图':
|
||||
option.series.push({ name: 'Sample', type: 'line', data: parsedYAxis });
|
||||
option.series.push({ name: 'Sample', type: 'line', data: yAxis.map(Number) });
|
||||
break;
|
||||
case '饼图':
|
||||
option.series.push({
|
||||
name: 'Sample',
|
||||
type: 'pie',
|
||||
data: parsedYAxis.map((value, index) => ({
|
||||
value,
|
||||
name: parsedXAxis[index] // 使用 xAxis 作为饼图的名称
|
||||
data: yAxis.map((value, index) => ({
|
||||
value: Number(value),
|
||||
name: xAxis[index] // 使用 xAxis 作为饼图的名称
|
||||
}))
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"author": "silencezhang",
|
||||
"version": "4812",
|
||||
"version": "4817",
|
||||
"name": "基础图表",
|
||||
"avatar": "core/workflow/template/baseChart",
|
||||
"intro": "根据数据生成图表,可根据chartType生成柱状图,折线图,饼图",
|
||||
@@ -68,7 +68,7 @@
|
||||
"canEdit": true,
|
||||
"key": "yAxis",
|
||||
"label": "yAxis",
|
||||
"description": "y轴数据,例如:['1', '2', '3']",
|
||||
"description": "y轴数据,例如:[1,2,3]",
|
||||
"defaultValue": "",
|
||||
"list": [
|
||||
{
|
||||
@@ -77,7 +77,7 @@
|
||||
}
|
||||
],
|
||||
"required": true,
|
||||
"toolDescription": "y轴数据,例如:['1', '2', '3']"
|
||||
"toolDescription": "y轴数据,例如:[1,2,3]"
|
||||
},
|
||||
{
|
||||
"renderTypeList": ["select", "reference"],
|
||||
@@ -145,8 +145,8 @@
|
||||
"flowNodeType": "pluginOutput",
|
||||
"showStatus": false,
|
||||
"position": {
|
||||
"x": 2122.252754006148,
|
||||
"y": -63.5218674613718
|
||||
"x": 2128.8138851197145,
|
||||
"y": -63.52186746137181
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
@@ -154,10 +154,12 @@
|
||||
"renderTypeList": ["reference"],
|
||||
"valueType": "string",
|
||||
"canEdit": true,
|
||||
"key": "相对路径URL",
|
||||
"label": "相对路径URL",
|
||||
"key": "图表 url",
|
||||
"label": "图表 url",
|
||||
"description": "可用使用markdown格式展示图片,如:",
|
||||
"value": ["ws0DFKJnCPhk", "bzaYjKyQFOw2"]
|
||||
"value": ["ws0DFKJnCPhk", "bzaYjKyQFOw2"],
|
||||
"isToolOutput": true,
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"outputs": []
|
||||
@@ -170,8 +172,8 @@
|
||||
"flowNodeType": "httpRequest468",
|
||||
"showStatus": true,
|
||||
"position": {
|
||||
"x": 1216.5166647574395,
|
||||
"y": -206.30162946606856
|
||||
"x": 1264.2009472531117,
|
||||
"y": -455.0773486762623
|
||||
},
|
||||
"version": "481",
|
||||
"inputs": [
|
||||
@@ -275,7 +277,7 @@
|
||||
"key": "system_httpJsonBody",
|
||||
"renderTypeList": ["hidden"],
|
||||
"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": "",
|
||||
"required": false,
|
||||
"valueDesc": "",
|
||||
@@ -306,126 +308,6 @@
|
||||
"description": "",
|
||||
"debugLabel": "",
|
||||
"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": [
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "",
|
||||
"version": "488",
|
||||
"name": "飞书 webhook",
|
||||
"avatar": "/appMarketTemplates/plugin-feishu/avatar.svg",
|
||||
"avatar": "core/app/templates/plugin-feishu",
|
||||
"intro": "向飞书机器人发起 webhook 请求。",
|
||||
"courseUrl": "https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot#f62e72d5",
|
||||
"showStatus": false,
|
||||
|
||||
@@ -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 {
|
||||
result: JSON.stringify(results.slice(0, 10))
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import fsp from 'fs/promises';
|
||||
import fs from 'fs';
|
||||
import { DatasetFileSchema } from '@fastgpt/global/core/dataset/type';
|
||||
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 { MongoRawTextBuffer } from '../../buffer/rawText/schema';
|
||||
import { readRawContentByFileBuffer } from '../read/utils';
|
||||
@@ -36,7 +36,6 @@ export async function uploadFile({
|
||||
path,
|
||||
filename,
|
||||
contentType,
|
||||
encoding,
|
||||
metadata = {}
|
||||
}: {
|
||||
bucketName: `${BucketNameEnum}`;
|
||||
@@ -45,7 +44,6 @@ export async function uploadFile({
|
||||
path: string;
|
||||
filename: string;
|
||||
contentType?: string;
|
||||
encoding: string;
|
||||
metadata?: Record<string, any>;
|
||||
}) {
|
||||
if (!path) return Promise.reject(`filePath is empty`);
|
||||
@@ -59,7 +57,7 @@ export async function uploadFile({
|
||||
// Add default metadata
|
||||
metadata.teamId = teamId;
|
||||
metadata.uid = uid;
|
||||
metadata.encoding = encoding;
|
||||
metadata.encoding = await detectFileEncodingByPath(path);
|
||||
|
||||
// create a gridfs bucket
|
||||
const bucket = getGridBucket(bucketName);
|
||||
|
||||
@@ -1,43 +1,92 @@
|
||||
import { UploadImgProps } from '@fastgpt/global/common/file/api';
|
||||
import { imageBaseUrl } from '@fastgpt/global/common/file/image/constants';
|
||||
import { MongoImage } from './schema';
|
||||
import { ClientSession } from '../../../common/mongo';
|
||||
import { ClientSession, Types } from '../../../common/mongo';
|
||||
import { guessBase64ImageType } from '../utils';
|
||||
import { readFromSecondary } from '../../mongo/utils';
|
||||
import { addHours } from 'date-fns';
|
||||
|
||||
export const maxImgSize = 1024 * 1024 * 12;
|
||||
const base64MimeRegex = /data:image\/([^\)]+);base64/;
|
||||
export async function uploadMongoImg({
|
||||
type,
|
||||
base64Img,
|
||||
teamId,
|
||||
expiredTime,
|
||||
metadata,
|
||||
shareId
|
||||
shareId,
|
||||
forever = false
|
||||
}: UploadImgProps & {
|
||||
teamId: string;
|
||||
forever?: Boolean;
|
||||
}) {
|
||||
if (base64Img.length > maxImgSize) {
|
||||
return Promise.reject('Image too large');
|
||||
}
|
||||
|
||||
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 binary = Buffer.from(base64Data, 'base64');
|
||||
const extension = mime.split('/')[1];
|
||||
|
||||
const { _id } = await MongoImage.create({
|
||||
type,
|
||||
teamId,
|
||||
binary,
|
||||
expiredTime,
|
||||
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}`;
|
||||
}
|
||||
|
||||
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 }) {
|
||||
const formatId = id.replace(/\.[^/.]+$/, '');
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
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 { mongoImageTypeMap } from '@fastgpt/global/common/file/image/constants';
|
||||
const { Schema, model, models } = connectionMongo;
|
||||
const { Schema } = connectionMongo;
|
||||
|
||||
const ImageSchema = new Schema({
|
||||
teamId: {
|
||||
@@ -14,27 +13,15 @@ const ImageSchema = new Schema({
|
||||
type: Date,
|
||||
default: () => new Date()
|
||||
},
|
||||
expiredTime: {
|
||||
type: Date
|
||||
},
|
||||
binary: {
|
||||
type: Buffer
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
enum: Object.keys(mongoImageTypeMap),
|
||||
required: true
|
||||
},
|
||||
metadata: {
|
||||
type: Object
|
||||
}
|
||||
expiredTime: Date,
|
||||
binary: Buffer,
|
||||
metadata: Object
|
||||
});
|
||||
|
||||
try {
|
||||
// tts expired(60 Minutes)
|
||||
ImageSchema.index({ expiredTime: 1 }, { expireAfterSeconds: 60 * 60 });
|
||||
ImageSchema.index({ type: 1 });
|
||||
ImageSchema.index({ createTime: 1 });
|
||||
// delete related img
|
||||
ImageSchema.index({ teamId: 1, 'metadata.relatedId': 1 });
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { uploadMongoImg } from '../image/controller';
|
||||
import { MongoImageTypeEnum } from '@fastgpt/global/common/file/image/constants';
|
||||
import FormData from 'form-data';
|
||||
|
||||
import { WorkerNameEnum, runWorker } from '../../../worker/utils';
|
||||
@@ -8,7 +7,6 @@ import type { ReadFileResponse } from '../../../worker/readFile/type';
|
||||
import axios from 'axios';
|
||||
import { addLog } from '../../system/log';
|
||||
import { batchRun } from '@fastgpt/global/common/fn/utils';
|
||||
import { addHours } from 'date-fns';
|
||||
import { matchMdImgTextAndUpload } from '@fastgpt/global/common/string/markdown';
|
||||
|
||||
export type readRawTextByLocalFileParams = {
|
||||
@@ -22,7 +20,7 @@ export const readRawTextByLocalFile = async (params: readRawTextByLocalFileParam
|
||||
|
||||
const extension = path?.split('.')?.pop()?.toLowerCase() || '';
|
||||
|
||||
const buffer = fs.readFileSync(path);
|
||||
const buffer = await fs.promises.readFile(path);
|
||||
|
||||
const { rawText } = await readRawContentByFileBuffer({
|
||||
extension,
|
||||
@@ -114,10 +112,9 @@ export const readRawContentByFileBuffer = async ({
|
||||
if (imageList) {
|
||||
await batchRun(imageList, async (item) => {
|
||||
const src = await uploadMongoImg({
|
||||
type: MongoImageTypeEnum.collectionImage,
|
||||
base64Img: `data:${item.mime};base64,${item.base64}`,
|
||||
teamId,
|
||||
expiredTime: addHours(new Date(), 1),
|
||||
// expiredTime: addHours(new Date(), 1),
|
||||
metadata: {
|
||||
...metadata,
|
||||
mime: item.mime
|
||||
|
||||
@@ -3,10 +3,13 @@ import NextCors from 'nextjs-cors';
|
||||
|
||||
export async function withNextCors(req: NextApiRequest, res: NextApiResponse) {
|
||||
const methods = ['GET', 'eHEAD', 'PUT', 'PATCH', 'POST', 'DELETE'];
|
||||
|
||||
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',');
|
||||
const origin = req.headers.origin;
|
||||
|
||||
await NextCors(req, res, {
|
||||
methods,
|
||||
origin: origin,
|
||||
origin: allowedOrigins || origin,
|
||||
optionsSuccessStatus: 200
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ import { jsonRes } from '../response';
|
||||
// unit: times/s
|
||||
// how to use?
|
||||
// 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) => {
|
||||
const ip = requestIp.getClientIp(req);
|
||||
if (!ip || process.env.USE_IP_LIMIT !== 'true') {
|
||||
if (!ip || (process.env.USE_IP_LIMIT !== 'true' && !force)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -22,10 +22,9 @@ export function useReqFrequencyLimit(seconds: number, limit: number) {
|
||||
expiredTime: addSeconds(new Date(), seconds)
|
||||
});
|
||||
} catch (_) {
|
||||
res.status(429);
|
||||
jsonRes(res, {
|
||||
code: 429,
|
||||
message: ERROR_ENUM.tooManyRequest
|
||||
error: ERROR_ENUM.tooManyRequest
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ export const jsonRes = <T = any>(
|
||||
|
||||
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
|
||||
|
||||
@@ -15,6 +15,9 @@ export const initFastGPTConfig = (config?: FastGPTConfigFileType) => {
|
||||
global.subPlans = config.subPlans;
|
||||
|
||||
global.llmModels = config.llmModels;
|
||||
global.llmModelPriceType = global.llmModels.some((item) => typeof item.inputPrice === 'number')
|
||||
? 'IO'
|
||||
: 'Tokens';
|
||||
global.vectorModels = config.vectorModels;
|
||||
global.audioSpeechModels = config.audioSpeechModels;
|
||||
global.whisperModel = config.whisperModel;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
33
packages/service/core/ai/config/llm/gpt-4o-mini.json
Normal file
33
packages/service/core/ai/config/llm/gpt-4o-mini.json
Normal 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
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"provider": "BAAI",
|
||||
"model": "bge-reranker-v2-m3",
|
||||
"name": "bge-reranker-v2-m3",
|
||||
"charsPointsPrice": 0
|
||||
}
|
||||
6
packages/service/core/ai/config/stt/whisper-1.json
Normal file
6
packages/service/core/ai/config/stt/whisper-1.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"provider": "OpenAI",
|
||||
"model": "whisper-1",
|
||||
"name": "whisper-1",
|
||||
"charsPointsPrice": 0
|
||||
}
|
||||
32
packages/service/core/ai/config/tts/tts-1.json
Normal file
32
packages/service/core/ai/config/tts/tts-1.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ChatCompletionMessageParam } from '@fastgpt/global/core/ai/type.d';
|
||||
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 { llmCompletionsBodyFormat } from '../utils';
|
||||
import {
|
||||
@@ -20,7 +20,8 @@ export async function createQuestionGuide({
|
||||
customPrompt?: string;
|
||||
}): Promise<{
|
||||
result: string[];
|
||||
tokens: number;
|
||||
inputTokens: number;
|
||||
outputTokens: number;
|
||||
}> {
|
||||
const concatMessages: ChatCompletionMessageParam[] = [
|
||||
...messages,
|
||||
@@ -29,6 +30,10 @@ export async function createQuestionGuide({
|
||||
content: `${customPrompt || PROMPT_QUESTION_GUIDE}\n${PROMPT_QUESTION_GUIDE_FOOTER}`
|
||||
}
|
||||
];
|
||||
const requestMessages = await loadRequestMessages({
|
||||
messages: concatMessages,
|
||||
useVision: false
|
||||
});
|
||||
|
||||
const { response: data } = await createChatCompletion({
|
||||
body: llmCompletionsBodyFormat(
|
||||
@@ -36,10 +41,7 @@ export async function createQuestionGuide({
|
||||
model,
|
||||
temperature: 0.1,
|
||||
max_tokens: 200,
|
||||
messages: await loadRequestMessages({
|
||||
messages: concatMessages,
|
||||
useVision: false
|
||||
}),
|
||||
messages: requestMessages,
|
||||
stream: false
|
||||
},
|
||||
model
|
||||
@@ -51,13 +53,15 @@ export async function createQuestionGuide({
|
||||
const start = answer.indexOf('[');
|
||||
const end = answer.lastIndexOf(']');
|
||||
|
||||
const tokens = await countGptMessagesTokens(concatMessages);
|
||||
const inputTokens = await countGptMessagesTokens(requestMessages);
|
||||
const outputTokens = await countPromptTokens(answer);
|
||||
|
||||
if (start === -1 || end === -1) {
|
||||
addLog.warn('Create question guide error', { answer });
|
||||
return {
|
||||
result: [],
|
||||
tokens: 0
|
||||
inputTokens: 0,
|
||||
outputTokens: 0
|
||||
};
|
||||
}
|
||||
|
||||
@@ -69,14 +73,16 @@ export async function createQuestionGuide({
|
||||
try {
|
||||
return {
|
||||
result: json5.parse(jsonStr),
|
||||
tokens
|
||||
inputTokens,
|
||||
outputTokens
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
return {
|
||||
result: [],
|
||||
tokens: 0
|
||||
inputTokens: 0,
|
||||
outputTokens: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { replaceVariable } from '@fastgpt/global/common/string/tools';
|
||||
import { createChatCompletion } from '../config';
|
||||
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 { getLLMModel } from '../model';
|
||||
import { llmCompletionsBodyFormat } from '../utils';
|
||||
@@ -121,7 +121,8 @@ export const queryExtension = async ({
|
||||
rawQuery: string;
|
||||
extensionQueries: string[];
|
||||
model: string;
|
||||
tokens: number;
|
||||
inputTokens: number;
|
||||
outputTokens: number;
|
||||
}> => {
|
||||
const systemFewShot = chatBg
|
||||
? `Q: 对话背景。
|
||||
@@ -166,7 +167,8 @@ A: ${chatBg}
|
||||
rawQuery: query,
|
||||
extensionQueries: [],
|
||||
model,
|
||||
tokens: 0
|
||||
inputTokens: 0,
|
||||
outputTokens: 0
|
||||
};
|
||||
}
|
||||
|
||||
@@ -181,7 +183,8 @@ A: ${chatBg}
|
||||
rawQuery: query,
|
||||
extensionQueries: Array.isArray(queries) ? queries : [],
|
||||
model,
|
||||
tokens: await countGptMessagesTokens(messages)
|
||||
inputTokens: await countGptMessagesTokens(messages),
|
||||
outputTokens: await countPromptTokens(answer)
|
||||
};
|
||||
} catch (error) {
|
||||
addLog.error(`Query extension error`, error);
|
||||
@@ -189,7 +192,8 @@ A: ${chatBg}
|
||||
rawQuery: query,
|
||||
extensionQueries: [],
|
||||
model,
|
||||
tokens: 0
|
||||
inputTokens: 0,
|
||||
outputTokens: 0
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ export const getLLMModel = (model?: string) => {
|
||||
global.llmModels[0]
|
||||
);
|
||||
};
|
||||
|
||||
export const getDatasetModel = (model?: string) => {
|
||||
return (
|
||||
global.llmModels
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
|
||||
import { PluginRuntimeType } from '@fastgpt/global/core/plugin/type';
|
||||
import { splitCombinePluginId } from './controller';
|
||||
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
|
||||
|
||||
/*
|
||||
Plugin points calculation:
|
||||
1. Return 0 if error
|
||||
2. Add configured points if commercial plugin
|
||||
3. Add sum of child nodes points
|
||||
1. 系统插件/商业版插件:
|
||||
- 有错误:返回 0
|
||||
- 无错误:返回 单次积分 + 子流程积分(可配置)
|
||||
2. 个人插件
|
||||
- 返回 子流程积分
|
||||
*/
|
||||
export const computedPluginUsage = async ({
|
||||
plugin,
|
||||
@@ -16,13 +20,16 @@ export const computedPluginUsage = async ({
|
||||
childrenUsage: ChatNodeUsageType[];
|
||||
error?: boolean;
|
||||
}) => {
|
||||
if (error) {
|
||||
return 0;
|
||||
const { source } = await splitCombinePluginId(plugin.id);
|
||||
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);
|
||||
|
||||
const pluginCurrentCose = plugin.currentCost ?? 0;
|
||||
|
||||
return plugin.hasTokenFee ? pluginCurrentCose + childrenIUsages : pluginCurrentCose;
|
||||
return childrenUsages;
|
||||
};
|
||||
|
||||
@@ -86,24 +86,21 @@ const ChatItemSchema = new Schema({
|
||||
});
|
||||
|
||||
try {
|
||||
ChatItemSchema.index({ dataId: 1 }, { background: true });
|
||||
ChatItemSchema.index({ dataId: 1 });
|
||||
/* delete by app;
|
||||
delete by chat id;
|
||||
get chat list;
|
||||
get chat logs;
|
||||
close custom feedback;
|
||||
*/
|
||||
ChatItemSchema.index({ appId: 1, chatId: 1, dataId: 1 }, { background: true });
|
||||
ChatItemSchema.index({ appId: 1, chatId: 1, dataId: 1 });
|
||||
// admin charts
|
||||
ChatItemSchema.index({ time: -1, obj: 1 }, { background: true });
|
||||
ChatItemSchema.index({ time: -1, obj: 1 });
|
||||
// timer, clear history
|
||||
ChatItemSchema.index({ teamId: 1, time: -1 }, { background: true });
|
||||
ChatItemSchema.index({ teamId: 1, time: -1 });
|
||||
|
||||
// Admin charts
|
||||
ChatItemSchema.index(
|
||||
{ obj: 1, time: -1 },
|
||||
{ background: true, partialFilterExpression: { obj: 'Human' } }
|
||||
);
|
||||
ChatItemSchema.index({ obj: 1, time: -1 }, { partialFilterExpression: { obj: 'Human' } });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
@@ -81,19 +81,19 @@ const ChatSchema = new Schema({
|
||||
});
|
||||
|
||||
try {
|
||||
ChatSchema.index({ chatId: 1 }, { background: true });
|
||||
ChatSchema.index({ chatId: 1 });
|
||||
// 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;
|
||||
ChatSchema.index({ appId: 1, chatId: 1 }, { background: true });
|
||||
ChatSchema.index({ appId: 1, chatId: 1 });
|
||||
|
||||
// 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
|
||||
ChatSchema.index({ shareId: 1, outLinkUid: 1, updateTime: -1 }, { background: true });
|
||||
ChatSchema.index({ shareId: 1, outLinkUid: 1, updateTime: -1 });
|
||||
|
||||
// timer, clear history
|
||||
ChatSchema.index({ teamId: 1, updateTime: -1 }, { background: true });
|
||||
ChatSchema.index({ teamId: 1, updateTime: -1 });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import { pushDataListToTrainingQueue } from '../training/controller';
|
||||
import { MongoImage } from '../../../common/file/image/schema';
|
||||
import { hashStr } from '@fastgpt/global/common/string/tools';
|
||||
import { addDays } from 'date-fns';
|
||||
import { MongoDatasetDataText } from '../data/dataTextSchema';
|
||||
|
||||
export const createCollectionAndInsertData = async ({
|
||||
dataset,
|
||||
@@ -240,12 +241,12 @@ export const delCollectionRelatedSource = async ({
|
||||
.map((item) => item?.metadata?.relatedImgId || '')
|
||||
.filter(Boolean);
|
||||
|
||||
// delete files
|
||||
// Delete files
|
||||
await delFileByFileIdList({
|
||||
bucketName: BucketNameEnum.dataset,
|
||||
fileIdList
|
||||
});
|
||||
// delete images
|
||||
// Delete images
|
||||
await delImgByRelatedId({
|
||||
teamId,
|
||||
relateIds: relatedImageIds,
|
||||
@@ -273,7 +274,7 @@ export async function delCollection({
|
||||
const datasetIds = Array.from(new Set(collections.map((item) => String(item.datasetId))));
|
||||
const collectionIds = collections.map((item) => String(item._id));
|
||||
|
||||
// delete training data
|
||||
// Delete training data
|
||||
await MongoDatasetTraining.deleteMany({
|
||||
teamId,
|
||||
datasetIds: { $in: datasetIds },
|
||||
@@ -285,11 +286,16 @@ export async function delCollection({
|
||||
await delCollectionRelatedSource({ collections, session });
|
||||
}
|
||||
|
||||
// delete dataset.datas
|
||||
// Delete dataset_datas
|
||||
await MongoDatasetData.deleteMany(
|
||||
{ teamId, datasetIds: { $in: datasetIds }, collectionId: { $in: collectionIds } },
|
||||
{ session }
|
||||
);
|
||||
// Delete dataset_data_texts
|
||||
await MongoDatasetDataText.deleteMany(
|
||||
{ teamId, datasetIds: { $in: datasetIds }, collectionId: { $in: collectionIds } },
|
||||
{ session }
|
||||
);
|
||||
|
||||
// delete collections
|
||||
await MongoDatasetCollection.deleteMany(
|
||||
|
||||
@@ -6,6 +6,7 @@ import { ClientSession } from '../../common/mongo';
|
||||
import { MongoDatasetTraining } from './training/schema';
|
||||
import { MongoDatasetData } from './data/schema';
|
||||
import { deleteDatasetDataVector } from '../../common/vectorStore/controller';
|
||||
import { MongoDatasetDataText } from './data/dataTextSchema';
|
||||
|
||||
/* ============= dataset ========== */
|
||||
/* find all datasetId by top datasetId */
|
||||
@@ -92,7 +93,7 @@ export async function delDatasetRelevantData({
|
||||
{ session }
|
||||
).lean();
|
||||
|
||||
// image and file
|
||||
// Delete Image and file
|
||||
await delCollectionRelatedSource({ collections, session });
|
||||
|
||||
// delete collections
|
||||
@@ -101,9 +102,15 @@ export async function delDatasetRelevantData({
|
||||
datasetId: { $in: datasetIds }
|
||||
}).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 } });
|
||||
|
||||
// no session delete: delete files, vector data
|
||||
// Delete vector data
|
||||
await deleteDatasetDataVector({ teamId, datasetIds });
|
||||
}
|
||||
|
||||
45
packages/service/core/dataset/data/dataTextSchema.ts
Normal file
45
packages/service/core/dataset/data/dataTextSchema.ts
Normal 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
|
||||
);
|
||||
@@ -1,4 +1,4 @@
|
||||
import { connectionMongo, getMongoModel, type Model } from '../../../common/mongo';
|
||||
import { connectionMongo, getMongoModel } from '../../../common/mongo';
|
||||
const { Schema, model, models } = connectionMongo;
|
||||
import { DatasetDataSchemaType } from '@fastgpt/global/core/dataset/type.d';
|
||||
import {
|
||||
@@ -39,10 +39,6 @@ const DatasetDataSchema = new Schema({
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
fullTextToken: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
indexes: {
|
||||
type: [
|
||||
{
|
||||
@@ -71,17 +67,11 @@ const DatasetDataSchema = new Schema({
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
inited: {
|
||||
type: Boolean
|
||||
},
|
||||
rebuilding: Boolean
|
||||
});
|
||||
rebuilding: Boolean,
|
||||
|
||||
DatasetDataSchema.virtual('collection', {
|
||||
ref: DatasetColCollectionName,
|
||||
localField: 'collectionId',
|
||||
foreignField: '_id',
|
||||
justOne: true
|
||||
// Abandon
|
||||
fullTextToken: String,
|
||||
initFullText: Boolean
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -93,13 +83,15 @@ try {
|
||||
chunkIndex: 1,
|
||||
updateTime: -1
|
||||
});
|
||||
// full text index
|
||||
DatasetDataSchema.index({ teamId: 1, datasetId: 1, fullTextToken: 'text' });
|
||||
// FullText tmp full text index
|
||||
// DatasetDataSchema.index({ teamId: 1, datasetId: 1, fullTextToken: 'text' });
|
||||
// Recall vectors after data matching
|
||||
DatasetDataSchema.index({ teamId: 1, datasetId: 1, collectionId: 1, 'indexes.dataId': 1 });
|
||||
DatasetDataSchema.index({ updateTime: 1 });
|
||||
// rebuild data
|
||||
DatasetDataSchema.index({ rebuilding: 1, teamId: 1, datasetId: 1 });
|
||||
|
||||
DatasetDataSchema.index({ initFullText: 1 });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import { getVectorsByText } from '../../ai/embedding';
|
||||
import { getVectorModel } from '../../ai/model';
|
||||
import { MongoDatasetData } from '../data/schema';
|
||||
import {
|
||||
DatasetCollectionSchemaType,
|
||||
DatasetDataSchemaType,
|
||||
DatasetDataTextSchemaType,
|
||||
SearchDataResponseItemType
|
||||
} from '@fastgpt/global/core/dataset/type';
|
||||
import { MongoDatasetCollection } from '../collection/schema';
|
||||
@@ -23,6 +23,7 @@ import { Types } from '../../../common/mongo';
|
||||
import json5 from 'json5';
|
||||
import { MongoDatasetCollectionTags } from '../tag/schema';
|
||||
import { readFromSecondary } from '../../../common/mongo/utils';
|
||||
import { MongoDatasetDataText } from '../data/dataTextSchema';
|
||||
|
||||
type SearchDatasetDataProps = {
|
||||
teamId: string;
|
||||
@@ -266,57 +267,60 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
|
||||
filterCollectionIdList
|
||||
});
|
||||
|
||||
// get q and a
|
||||
const dataList = await MongoDatasetData.find(
|
||||
{
|
||||
teamId,
|
||||
datasetId: { $in: datasetIds },
|
||||
collectionId: { $in: Array.from(new Set(results.map((item) => item.collectionId))) },
|
||||
'indexes.dataId': { $in: results.map((item) => item.id?.trim()) }
|
||||
},
|
||||
'datasetId collectionId updateTime q a chunkIndex indexes'
|
||||
)
|
||||
.populate<{ collection: DatasetCollectionSchemaType }>(
|
||||
'collection',
|
||||
'name fileId rawLink externalFileId externalFileUrl'
|
||||
)
|
||||
.lean();
|
||||
// Get data and collections
|
||||
const collectionIdList = Array.from(new Set(results.map((item) => item.collectionId)));
|
||||
const [dataList, collections] = await Promise.all([
|
||||
MongoDatasetData.find(
|
||||
{
|
||||
teamId,
|
||||
datasetId: { $in: datasetIds },
|
||||
collectionId: { $in: collectionIdList },
|
||||
'indexes.dataId': { $in: results.map((item) => item.id?.trim()) }
|
||||
},
|
||||
'_id datasetId collectionId updateTime q a chunkIndex indexes',
|
||||
{ ...readFromSecondary }
|
||||
).lean(),
|
||||
MongoDatasetCollection.find(
|
||||
{
|
||||
_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 concatResults = dataList.map((data) => {
|
||||
const dataIdList = data.indexes.map((item) => item.dataId);
|
||||
const formatResult = results
|
||||
.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) =>
|
||||
data.indexes.some((index) => index.dataId === item.id)
|
||||
);
|
||||
if (!data) {
|
||||
console.log('Data is not found', item);
|
||||
return;
|
||||
}
|
||||
|
||||
const maxScoreResult = results.find((item) => {
|
||||
return dataIdList.includes(item.id);
|
||||
});
|
||||
const score = item?.score || 0;
|
||||
|
||||
return {
|
||||
...data,
|
||||
score: maxScoreResult?.score || 0
|
||||
};
|
||||
});
|
||||
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(collection),
|
||||
score: [{ type: SearchScoreTypeEnum.embedding, value: score, index }]
|
||||
};
|
||||
|
||||
concatResults.sort((a, b) => b.score - a.score);
|
||||
|
||||
const formatResult = concatResults.map((data, index) => {
|
||||
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 result;
|
||||
})
|
||||
.filter(Boolean) as SearchDataResponseItemType[];
|
||||
|
||||
return {
|
||||
embeddingRecallResults: formatResult,
|
||||
@@ -344,88 +348,224 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
|
||||
};
|
||||
}
|
||||
|
||||
let searchResults = (
|
||||
const searchResults = (
|
||||
await Promise.all(
|
||||
datasetIds.map(async (id) => {
|
||||
return MongoDatasetData.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))
|
||||
return MongoDatasetData.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))
|
||||
: {}),
|
||||
...(forbidCollectionIdList && forbidCollectionIdList.length > 0
|
||||
? {
|
||||
collectionId: {
|
||||
$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: {
|
||||
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
|
||||
}
|
||||
...readFromSecondary
|
||||
}
|
||||
]);
|
||||
);
|
||||
})
|
||||
)
|
||||
).flat() as (DatasetDataSchemaType & { score: number })[];
|
||||
|
||||
// resort
|
||||
searchResults.sort((a, b) => b.score - a.score);
|
||||
searchResults.slice(0, limit);
|
||||
|
||||
// Get data and collections
|
||||
const collections = await MongoDatasetCollection.find(
|
||||
{
|
||||
_id: { $in: searchResults.map((item) => item.collectionId) }
|
||||
},
|
||||
'_id name fileId rawLink'
|
||||
);
|
||||
'_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));
|
||||
return {
|
||||
id: String(item._id),
|
||||
datasetId: String(item.datasetId),
|
||||
collectionId: String(item.collectionId),
|
||||
updateTime: item.updateTime,
|
||||
...getCollectionSourceData(collection),
|
||||
q: item.q,
|
||||
a: item.a,
|
||||
chunkIndex: item.chunkIndex,
|
||||
indexes: item.indexes,
|
||||
score: [{ type: SearchScoreTypeEnum.fullText, value: item.score, index }]
|
||||
};
|
||||
}),
|
||||
fullTextRecallResults: searchResults
|
||||
.map((data, index) => {
|
||||
const collection = collections.find(
|
||||
(col) => String(col._id) === String(data.collectionId)
|
||||
);
|
||||
if (!collection) {
|
||||
console.log('Collection is not found', data);
|
||||
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: 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
|
||||
};
|
||||
};
|
||||
@@ -496,7 +636,8 @@ export async function searchDatasetData(props: SearchDatasetDataProps) {
|
||||
forbidCollectionIdList,
|
||||
filterCollectionIdList
|
||||
}),
|
||||
fullTextRecall({
|
||||
// FullText tmp
|
||||
fullTextRecall2({
|
||||
query,
|
||||
limit: fullTextLimit,
|
||||
filterCollectionIdList,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
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 { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { createChatCompletion } from '../../../ai/config';
|
||||
@@ -49,7 +52,7 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
|
||||
|
||||
const chatHistories = getHistories(history, histories);
|
||||
|
||||
const { arg, tokens } = await completions({
|
||||
const { arg, inputTokens, outputTokens } = await completions({
|
||||
...props,
|
||||
histories: chatHistories,
|
||||
cqModel
|
||||
@@ -59,7 +62,8 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
|
||||
|
||||
const { totalPoints, modelName } = formatModelChars2Points({
|
||||
model: cqModel.model,
|
||||
tokens,
|
||||
inputTokens: inputTokens,
|
||||
outputTokens: outputTokens,
|
||||
modelType: ModelTypeEnum.llm
|
||||
});
|
||||
|
||||
@@ -72,7 +76,8 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
|
||||
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
|
||||
model: modelName,
|
||||
query: userChatInput,
|
||||
tokens,
|
||||
inputTokens: inputTokens,
|
||||
outputTokens: outputTokens,
|
||||
cqList: agents,
|
||||
cqResult: result.value,
|
||||
contextTotalLen: chatHistories.length + 2
|
||||
@@ -82,7 +87,8 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
|
||||
moduleName: name,
|
||||
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
|
||||
model: modelName,
|
||||
tokens
|
||||
inputTokens: inputTokens,
|
||||
outputTokens: outputTokens
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -148,7 +154,8 @@ const completions = async ({
|
||||
}
|
||||
|
||||
return {
|
||||
tokens: await countMessagesTokens(messages),
|
||||
inputTokens: await countGptMessagesTokens(requestMessages),
|
||||
outputTokens: await countPromptTokens(answer),
|
||||
arg: { type: id }
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,7 +3,8 @@ import { filterGPTMessageByMaxTokens, loadRequestMessages } from '../../../chat/
|
||||
import type { ChatItemType } from '@fastgpt/global/core/chat/type.d';
|
||||
import {
|
||||
countMessagesTokens,
|
||||
countGptMessagesTokens
|
||||
countGptMessagesTokens,
|
||||
countPromptTokens
|
||||
} from '../../../../common/string/tiktoken/index';
|
||||
import { ChatItemValueTypeEnum, ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { createChatCompletion } from '../../../ai/config';
|
||||
@@ -59,7 +60,7 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
|
||||
const extractModel = getLLMModel(model);
|
||||
const chatHistories = getHistories(history, histories);
|
||||
|
||||
const { arg, tokens } = await (async () => {
|
||||
const { arg, inputTokens, outputTokens } = await (async () => {
|
||||
if (extractModel.toolChoice) {
|
||||
return toolChoice({
|
||||
...props,
|
||||
@@ -114,7 +115,8 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
|
||||
|
||||
const { totalPoints, modelName } = formatModelChars2Points({
|
||||
model: extractModel.model,
|
||||
tokens,
|
||||
inputTokens: inputTokens,
|
||||
outputTokens: outputTokens,
|
||||
modelType: ModelTypeEnum.llm
|
||||
});
|
||||
|
||||
@@ -126,7 +128,8 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
|
||||
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
|
||||
model: modelName,
|
||||
query: content,
|
||||
tokens,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
extractDescription: description,
|
||||
extractResult: arg,
|
||||
contextTotalLen: chatHistories.length + 2
|
||||
@@ -136,7 +139,8 @@ export async function dispatchContentExtract(props: Props): Promise<Response> {
|
||||
moduleName: name,
|
||||
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
|
||||
model: modelName,
|
||||
tokens
|
||||
inputTokens,
|
||||
outputTokens
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -249,15 +253,18 @@ const toolChoice = async (props: ActionProps) => {
|
||||
}
|
||||
})();
|
||||
|
||||
const completeMessages: ChatCompletionMessageParam[] = [
|
||||
...filterMessages,
|
||||
const AIMessages: ChatCompletionMessageParam[] = [
|
||||
{
|
||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
tool_calls: response.choices?.[0]?.message?.tool_calls
|
||||
}
|
||||
];
|
||||
|
||||
const inputTokens = await countGptMessagesTokens(filterMessages, tools);
|
||||
const outputTokens = await countGptMessagesTokens(AIMessages);
|
||||
return {
|
||||
tokens: await countGptMessagesTokens(completeMessages, tools),
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
arg
|
||||
};
|
||||
};
|
||||
@@ -286,17 +293,21 @@ const functionCall = async (props: ActionProps) => {
|
||||
|
||||
try {
|
||||
const arg = JSON.parse(response?.choices?.[0]?.message?.function_call?.arguments || '');
|
||||
const completeMessages: ChatCompletionMessageParam[] = [
|
||||
...filterMessages,
|
||||
|
||||
const AIMessages: ChatCompletionMessageParam[] = [
|
||||
{
|
||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
function_call: response.choices?.[0]?.message?.function_call
|
||||
}
|
||||
];
|
||||
|
||||
const inputTokens = await countGptMessagesTokens(filterMessages, undefined, functions);
|
||||
const outputTokens = await countGptMessagesTokens(AIMessages);
|
||||
|
||||
return {
|
||||
arg,
|
||||
tokens: await countGptMessagesTokens(completeMessages, undefined, functions)
|
||||
inputTokens,
|
||||
outputTokens
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(response.choices?.[0]?.message);
|
||||
@@ -305,7 +316,8 @@ const functionCall = async (props: ActionProps) => {
|
||||
|
||||
return {
|
||||
arg: {},
|
||||
tokens: 0
|
||||
inputTokens: 0,
|
||||
outputTokens: 0
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -370,7 +382,8 @@ Human: ${content}`
|
||||
if (!jsonStr) {
|
||||
return {
|
||||
rawResponse: answer,
|
||||
tokens: await countMessagesTokens(messages),
|
||||
inputTokens: await countMessagesTokens(messages),
|
||||
outputTokens: await countPromptTokens(answer),
|
||||
arg: {}
|
||||
};
|
||||
}
|
||||
@@ -378,7 +391,8 @@ Human: ${content}`
|
||||
try {
|
||||
return {
|
||||
rawResponse: answer,
|
||||
tokens: await countMessagesTokens(messages),
|
||||
inputTokens: await countMessagesTokens(messages),
|
||||
outputTokens: await countPromptTokens(answer),
|
||||
arg: json5.parse(jsonStr) as Record<string, any>
|
||||
};
|
||||
} catch (error) {
|
||||
@@ -386,7 +400,8 @@ Human: ${content}`
|
||||
console.log(error);
|
||||
return {
|
||||
rawResponse: answer,
|
||||
tokens: await countMessagesTokens(messages),
|
||||
inputTokens: await countMessagesTokens(messages),
|
||||
outputTokens: await countPromptTokens(answer),
|
||||
arg: {}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -109,7 +109,8 @@ export const runToolWithFunctionCall = async (
|
||||
|
||||
return {
|
||||
dispatchFlowResponse: [toolRunResponse],
|
||||
toolNodeTokens: 0,
|
||||
toolNodeInputTokens: 0,
|
||||
toolNodeOutputTokens: 0,
|
||||
completeMessages: requestMessages,
|
||||
assistantResponses: toolRunResponse.assistantResponses,
|
||||
runTimes: toolRunResponse.runTimes,
|
||||
@@ -126,7 +127,8 @@ export const runToolWithFunctionCall = async (
|
||||
},
|
||||
{
|
||||
dispatchFlowResponse: [toolRunResponse],
|
||||
toolNodeTokens: 0,
|
||||
toolNodeInputTokens: 0,
|
||||
toolNodeOutputTokens: 0,
|
||||
assistantResponses: toolRunResponse.assistantResponses,
|
||||
runTimes: toolRunResponse.runTimes
|
||||
}
|
||||
@@ -340,7 +342,9 @@ export const runToolWithFunctionCall = async (
|
||||
assistantToolMsgParams
|
||||
] as ChatCompletionMessageParam[];
|
||||
// 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
|
||||
@@ -375,7 +379,12 @@ export const runToolWithFunctionCall = async (
|
||||
const runTimes =
|
||||
(response?.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
|
||||
const hasStopSignal = flatToolsResponseData.some(
|
||||
@@ -408,7 +417,8 @@ export const runToolWithFunctionCall = async (
|
||||
|
||||
return {
|
||||
dispatchFlowResponse,
|
||||
toolNodeTokens,
|
||||
toolNodeInputTokens,
|
||||
toolNodeOutputTokens,
|
||||
completeMessages,
|
||||
assistantResponses: toolNodeAssistants,
|
||||
runTimes,
|
||||
@@ -423,7 +433,8 @@ export const runToolWithFunctionCall = async (
|
||||
},
|
||||
{
|
||||
dispatchFlowResponse,
|
||||
toolNodeTokens,
|
||||
toolNodeInputTokens,
|
||||
toolNodeOutputTokens,
|
||||
assistantResponses: toolNodeAssistants,
|
||||
runTimes
|
||||
}
|
||||
@@ -435,7 +446,8 @@ export const runToolWithFunctionCall = async (
|
||||
content: answer
|
||||
};
|
||||
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');
|
||||
|
||||
// concat tool assistant
|
||||
@@ -443,7 +455,12 @@ export const runToolWithFunctionCall = async (
|
||||
|
||||
return {
|
||||
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,
|
||||
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
|
||||
runTimes: (response?.runTimes || 0) + 1
|
||||
|
||||
@@ -165,6 +165,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
||||
toolWorkflowInteractiveResponse,
|
||||
dispatchFlowResponse, // tool flow response
|
||||
toolNodeTokens,
|
||||
toolNodeInputTokens,
|
||||
toolNodeOutputTokens,
|
||||
completeMessages = [], // The actual message sent to AI(just save text)
|
||||
assistantResponses = [], // FastGPT system store assistant.value response
|
||||
runTimes
|
||||
@@ -225,7 +227,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
||||
|
||||
const { totalPoints, modelName } = formatModelChars2Points({
|
||||
model,
|
||||
tokens: toolNodeTokens,
|
||||
inputTokens: toolNodeInputTokens,
|
||||
outputTokens: toolNodeOutputTokens,
|
||||
modelType: ModelTypeEnum.llm
|
||||
});
|
||||
const toolAIUsage = externalProvider.openaiAccount?.key ? 0 : totalPoints;
|
||||
@@ -255,6 +258,8 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
||||
// 展示的积分消耗
|
||||
totalPoints: totalPointsUsage,
|
||||
toolCallTokens: toolNodeTokens,
|
||||
toolCallInputTokens: toolNodeInputTokens,
|
||||
toolCallOutputTokens: toolNodeOutputTokens,
|
||||
childTotalPoints: flatUsages.reduce((sum, item) => sum + item.totalPoints, 0),
|
||||
model: modelName,
|
||||
query: userChatInput,
|
||||
@@ -270,9 +275,10 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
||||
// 工具调用本身的积分消耗
|
||||
{
|
||||
moduleName: name,
|
||||
totalPoints: toolAIUsage,
|
||||
model: modelName,
|
||||
tokens: toolNodeTokens
|
||||
totalPoints: toolAIUsage,
|
||||
inputTokens: toolNodeInputTokens,
|
||||
outputTokens: toolNodeOutputTokens
|
||||
},
|
||||
// 工具的消耗
|
||||
...flatUsages
|
||||
|
||||
@@ -115,7 +115,8 @@ export const runToolWithPromptCall = async (
|
||||
|
||||
return {
|
||||
dispatchFlowResponse: [toolRunResponse],
|
||||
toolNodeTokens: 0,
|
||||
toolNodeInputTokens: 0,
|
||||
toolNodeOutputTokens: 0,
|
||||
completeMessages: concatMessages,
|
||||
assistantResponses: toolRunResponse.assistantResponses,
|
||||
runTimes: toolRunResponse.runTimes,
|
||||
@@ -131,7 +132,8 @@ export const runToolWithPromptCall = async (
|
||||
},
|
||||
{
|
||||
dispatchFlowResponse: [toolRunResponse],
|
||||
toolNodeTokens: 0,
|
||||
toolNodeInputTokens: 0,
|
||||
toolNodeOutputTokens: 0,
|
||||
assistantResponses: toolRunResponse.assistantResponses,
|
||||
runTimes: toolRunResponse.runTimes
|
||||
}
|
||||
@@ -286,15 +288,20 @@ export const runToolWithPromptCall = async (
|
||||
content: replaceAnswer
|
||||
};
|
||||
const completeMessages = filterMessages.concat(gptAssistantResponse);
|
||||
const tokens = await countGptMessagesTokens(completeMessages, undefined);
|
||||
// console.log(tokens, 'response token');
|
||||
const inputTokens = await countGptMessagesTokens(requestMessages);
|
||||
const outputTokens = await countGptMessagesTokens([gptAssistantResponse]);
|
||||
|
||||
// concat tool assistant
|
||||
const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType;
|
||||
|
||||
return {
|
||||
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,
|
||||
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
|
||||
runTimes: (response?.runTimes || 0) + 1
|
||||
@@ -366,17 +373,9 @@ export const runToolWithPromptCall = async (
|
||||
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
|
||||
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 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
|
||||
const hasStopSignal = toolsRunResponse.toolResponse.flowResponses.some((item) => !!item.toolStop);
|
||||
@@ -460,7 +464,8 @@ ANSWER: `;
|
||||
|
||||
return {
|
||||
dispatchFlowResponse,
|
||||
toolNodeTokens,
|
||||
toolNodeInputTokens,
|
||||
toolNodeOutputTokens,
|
||||
completeMessages: filterMessages,
|
||||
assistantResponses: toolNodeAssistants,
|
||||
runTimes,
|
||||
@@ -475,7 +480,8 @@ ANSWER: `;
|
||||
},
|
||||
{
|
||||
dispatchFlowResponse,
|
||||
toolNodeTokens,
|
||||
toolNodeInputTokens,
|
||||
toolNodeOutputTokens,
|
||||
assistantResponses: toolNodeAssistants,
|
||||
runTimes
|
||||
}
|
||||
|
||||
@@ -27,9 +27,10 @@ import { getNanoid, sliceStrStartEnd } from '@fastgpt/global/common/string/tools
|
||||
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
|
||||
import { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||
import { ChatItemValueTypeEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
|
||||
type ToolRunResponseType = {
|
||||
toolRunResponse: DispatchFlowResponse;
|
||||
toolRunResponse?: DispatchFlowResponse;
|
||||
toolMsgParams: ChatCompletionToolMessageParam;
|
||||
}[];
|
||||
|
||||
@@ -158,7 +159,8 @@ export const runToolWithToolChoice = async (
|
||||
|
||||
return {
|
||||
dispatchFlowResponse: [toolRunResponse],
|
||||
toolNodeTokens: 0,
|
||||
toolNodeInputTokens: 0,
|
||||
toolNodeOutputTokens: 0,
|
||||
completeMessages: requestMessages,
|
||||
assistantResponses: toolRunResponse.assistantResponses,
|
||||
runTimes: toolRunResponse.runTimes,
|
||||
@@ -176,7 +178,8 @@ export const runToolWithToolChoice = async (
|
||||
},
|
||||
{
|
||||
dispatchFlowResponse: [toolRunResponse],
|
||||
toolNodeTokens: 0,
|
||||
toolNodeInputTokens: 0,
|
||||
toolNodeOutputTokens: 0,
|
||||
assistantResponses: toolRunResponse.assistantResponses,
|
||||
runTimes: toolRunResponse.runTimes
|
||||
}
|
||||
@@ -342,59 +345,87 @@ export const runToolWithToolChoice = async (
|
||||
return Promise.reject(getEmptyResponseTip());
|
||||
}
|
||||
|
||||
// Run the selected tool by LLM.
|
||||
const toolsRunResponse = (
|
||||
await Promise.all(
|
||||
toolCalls.map(async (tool) => {
|
||||
const toolNode = toolNodes.find((item) => item.nodeId === tool.function?.name);
|
||||
/* Run the selected tool by LLM.
|
||||
Since only reference parameters are passed, if the same tool is run in parallel, it will get the same run parameters
|
||||
*/
|
||||
const toolsRunResponse: ToolRunResponseType = [];
|
||||
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 = (() => {
|
||||
try {
|
||||
return json5.parse(tool.function.arguments);
|
||||
} catch (error) {
|
||||
return {};
|
||||
const startParams = (() => {
|
||||
try {
|
||||
return json5.parse(tool.function.arguments);
|
||||
} catch (error) {
|
||||
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);
|
||||
const toolRunResponse = await dispatchWorkFlow({
|
||||
...workflowProps,
|
||||
isToolCall: true
|
||||
});
|
||||
toolsRunResponse.push({
|
||||
toolRunResponse,
|
||||
toolMsgParams
|
||||
});
|
||||
} 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);
|
||||
|
||||
const toolMsgParams: ChatCompletionToolMessageParam = {
|
||||
toolsRunResponse.push({
|
||||
toolRunResponse: undefined,
|
||||
toolMsgParams: {
|
||||
tool_call_id: tool.id,
|
||||
role: ChatCompletionRequestMessageRoleEnum.Tool,
|
||||
name: tool.function.name,
|
||||
content: stringToolResponse
|
||||
};
|
||||
content: sliceStrStartEnd(err, 5000, 5000)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
workflowStreamResponse?.({
|
||||
event: SseResponseEventEnum.toolResponse,
|
||||
data: {
|
||||
tool: {
|
||||
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();
|
||||
const flatToolsResponseData = toolsRunResponse
|
||||
.map((item) => item.toolRunResponse)
|
||||
.flat()
|
||||
.filter(Boolean) as DispatchFlowResponse[];
|
||||
// concat tool responses
|
||||
const dispatchFlowResponse = response
|
||||
? response.dispatchFlowResponse.concat(flatToolsResponseData)
|
||||
@@ -428,24 +459,26 @@ export const runToolWithToolChoice = async (
|
||||
] as ChatCompletionMessageParam[];
|
||||
|
||||
// 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
|
||||
assistant: tool data
|
||||
tool: tool response
|
||||
*/
|
||||
...
|
||||
user
|
||||
assistant: tool data
|
||||
tool: tool response
|
||||
*/
|
||||
const completeMessages = [
|
||||
...concatToolMessages,
|
||||
...toolsRunResponse.map((item) => item?.toolMsgParams)
|
||||
];
|
||||
|
||||
/*
|
||||
Get tool node assistant response
|
||||
history assistant
|
||||
current tool assistant
|
||||
tool child assistant
|
||||
*/
|
||||
Get tool node assistant response
|
||||
history assistant
|
||||
current tool assistant
|
||||
tool child assistant
|
||||
*/
|
||||
const toolNodeAssistant = GPTMessages2Chats([
|
||||
...assistantToolMsgParams,
|
||||
...toolsRunResponse.map((item) => item?.toolMsgParams)
|
||||
@@ -463,7 +496,10 @@ export const runToolWithToolChoice = async (
|
||||
const runTimes =
|
||||
(response?.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
|
||||
const hasStopSignal = flatToolsResponseData.some(
|
||||
@@ -471,12 +507,12 @@ export const runToolWithToolChoice = async (
|
||||
);
|
||||
// Check interactive response(Only 1 interaction is reserved)
|
||||
const workflowInteractiveResponseItem = toolsRunResponse.find(
|
||||
(item) => item.toolRunResponse.workflowInteractiveResponse
|
||||
(item) => item.toolRunResponse?.workflowInteractiveResponse
|
||||
);
|
||||
if (hasStopSignal || workflowInteractiveResponseItem) {
|
||||
// Get interactive tool data
|
||||
const workflowInteractiveResponse =
|
||||
workflowInteractiveResponseItem?.toolRunResponse.workflowInteractiveResponse;
|
||||
workflowInteractiveResponseItem?.toolRunResponse?.workflowInteractiveResponse;
|
||||
|
||||
// Flashback traverses completeMessages, intercepting messages that know the first user
|
||||
const firstUserIndex = completeMessages.findLastIndex((item) => item.role === 'user');
|
||||
@@ -496,7 +532,8 @@ export const runToolWithToolChoice = async (
|
||||
|
||||
return {
|
||||
dispatchFlowResponse,
|
||||
toolNodeTokens,
|
||||
toolNodeInputTokens,
|
||||
toolNodeOutputTokens,
|
||||
completeMessages,
|
||||
assistantResponses: toolNodeAssistants,
|
||||
runTimes,
|
||||
@@ -512,7 +549,8 @@ export const runToolWithToolChoice = async (
|
||||
},
|
||||
{
|
||||
dispatchFlowResponse,
|
||||
toolNodeTokens,
|
||||
toolNodeInputTokens,
|
||||
toolNodeOutputTokens,
|
||||
assistantResponses: toolNodeAssistants,
|
||||
runTimes
|
||||
}
|
||||
@@ -524,14 +562,17 @@ export const runToolWithToolChoice = async (
|
||||
content: answer
|
||||
};
|
||||
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
|
||||
const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType;
|
||||
|
||||
return {
|
||||
dispatchFlowResponse: response?.dispatchFlowResponse || [],
|
||||
toolNodeTokens: response ? response.toolNodeTokens + tokens : tokens,
|
||||
toolNodeInputTokens: response ? response.toolNodeInputTokens + inputTokens : inputTokens,
|
||||
toolNodeOutputTokens: response ? response.toolNodeOutputTokens + outputTokens : outputTokens,
|
||||
|
||||
completeMessages,
|
||||
assistantResponses: [...assistantResponses, ...toolNodeAssistant.value],
|
||||
runTimes: (response?.runTimes || 0) + 1
|
||||
@@ -578,7 +619,8 @@ async function streamResponse({
|
||||
text: content
|
||||
})
|
||||
});
|
||||
} else if (responseChoice?.tool_calls?.[0]) {
|
||||
}
|
||||
if (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
|
||||
if (toolCall.id || callingTool) {
|
||||
|
||||
@@ -31,7 +31,9 @@ export type DispatchToolModuleProps = ModuleDispatchProps<{
|
||||
|
||||
export type RunToolResponse = {
|
||||
dispatchFlowResponse: DispatchFlowResponse[];
|
||||
toolNodeTokens: number;
|
||||
toolNodeTokens?: number; // deprecated
|
||||
toolNodeInputTokens: number;
|
||||
toolNodeOutputTokens: number;
|
||||
completeMessages?: ChatCompletionMessageParam[];
|
||||
assistantResponses?: AIChatItemValueItemType[];
|
||||
toolWorkflowInteractiveResponse?: WorkflowInteractiveResponseType;
|
||||
|
||||
@@ -5,13 +5,17 @@ import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
||||
import { SseResponseEventEnum } from '@fastgpt/global/core/workflow/runtime/constants';
|
||||
import { textAdaptGptResponse } from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
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 type { LLMModelItemType } from '@fastgpt/global/core/ai/model.d';
|
||||
import { postTextCensor } from '../../../../common/api/requestPlusApi';
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '@fastgpt/global/core/ai/constants';
|
||||
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 {
|
||||
chats2GPTMessages,
|
||||
chatValue2RuntimePrompt,
|
||||
@@ -214,16 +218,23 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
return Promise.reject(getEmptyResponseTip());
|
||||
}
|
||||
|
||||
const completeMessages = requestMessages.concat({
|
||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
content: answerText
|
||||
});
|
||||
const AIMessages: ChatCompletionMessageParam[] = [
|
||||
{
|
||||
role: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
content: answerText
|
||||
}
|
||||
];
|
||||
|
||||
const completeMessages = [...requestMessages, ...AIMessages];
|
||||
const chatCompleteMessages = GPTMessages2Chats(completeMessages);
|
||||
|
||||
const tokens = await countMessagesTokens(chatCompleteMessages);
|
||||
const inputTokens = await countGptMessagesTokens(requestMessages);
|
||||
const outputTokens = await countGptMessagesTokens(AIMessages);
|
||||
|
||||
const { totalPoints, modelName } = formatModelChars2Points({
|
||||
model,
|
||||
tokens,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
modelType: ModelTypeEnum.llm
|
||||
});
|
||||
|
||||
@@ -232,7 +243,9 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
|
||||
model: modelName,
|
||||
tokens,
|
||||
tokens: inputTokens + outputTokens,
|
||||
inputTokens: inputTokens,
|
||||
outputTokens: outputTokens,
|
||||
query: `${userChatInput}`,
|
||||
maxToken: max_tokens,
|
||||
historyPreview: getHistoryPreview(
|
||||
@@ -247,7 +260,8 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
|
||||
moduleName: name,
|
||||
totalPoints: externalProvider.openaiAccount?.key ? 0 : totalPoints,
|
||||
model: modelName,
|
||||
tokens
|
||||
inputTokens: inputTokens,
|
||||
outputTokens: outputTokens
|
||||
}
|
||||
],
|
||||
[DispatchNodeResponseKeyEnum.toolResponses]: answerText,
|
||||
|
||||
@@ -120,14 +120,14 @@ export async function dispatchDatasetSearch(
|
||||
// vector
|
||||
const { totalPoints, modelName } = formatModelChars2Points({
|
||||
model: vectorModel.model,
|
||||
tokens,
|
||||
inputTokens: tokens,
|
||||
modelType: ModelTypeEnum.vector
|
||||
});
|
||||
const responseData: DispatchNodeResponseType & { totalPoints: number } = {
|
||||
totalPoints,
|
||||
query: concatQueries.join('\n'),
|
||||
model: modelName,
|
||||
tokens,
|
||||
inputTokens: tokens,
|
||||
similarity: usingSimilarityFilter ? similarity : undefined,
|
||||
limit,
|
||||
searchMode,
|
||||
@@ -139,19 +139,21 @@ export async function dispatchDatasetSearch(
|
||||
totalPoints,
|
||||
moduleName: node.name,
|
||||
model: modelName,
|
||||
tokens
|
||||
inputTokens: tokens
|
||||
}
|
||||
];
|
||||
|
||||
if (aiExtensionResult) {
|
||||
const { totalPoints, modelName } = formatModelChars2Points({
|
||||
model: aiExtensionResult.model,
|
||||
tokens: aiExtensionResult.tokens,
|
||||
inputTokens: aiExtensionResult.inputTokens,
|
||||
outputTokens: aiExtensionResult.outputTokens,
|
||||
modelType: ModelTypeEnum.llm
|
||||
});
|
||||
|
||||
responseData.totalPoints += totalPoints;
|
||||
responseData.tokens = aiExtensionResult.tokens;
|
||||
responseData.inputTokens = aiExtensionResult.inputTokens;
|
||||
responseData.outputTokens = aiExtensionResult.outputTokens;
|
||||
responseData.extensionModel = modelName;
|
||||
responseData.extensionResult =
|
||||
aiExtensionResult.extensionQueries?.join('\n') ||
|
||||
@@ -161,7 +163,8 @@ export async function dispatchDatasetSearch(
|
||||
totalPoints,
|
||||
moduleName: 'core.module.template.Query extension',
|
||||
model: modelName,
|
||||
tokens: aiExtensionResult.tokens
|
||||
inputTokens: aiExtensionResult.inputTokens,
|
||||
outputTokens: aiExtensionResult.outputTokens
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ import { dispatchLoopEnd } from './loop/runLoopEnd';
|
||||
import { dispatchLoopStart } from './loop/runLoopStart';
|
||||
import { dispatchFormInput } from './interactive/formInput';
|
||||
import { dispatchToolParams } from './agent/runTool/toolParams';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
|
||||
const callbackMap: Record<FlowNodeTypeEnum, Function> = {
|
||||
[FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart,
|
||||
@@ -231,9 +232,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
||||
|
||||
if (toolResponses !== undefined) {
|
||||
if (Array.isArray(toolResponses) && toolResponses.length === 0) return;
|
||||
if (typeof toolResponses === 'object' && Object.keys(toolResponses).length === 0) {
|
||||
return;
|
||||
}
|
||||
if (typeof toolResponses === 'object' && Object.keys(toolResponses).length === 0) return;
|
||||
toolRunResponse = toolResponses;
|
||||
}
|
||||
|
||||
@@ -565,6 +564,8 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
||||
const targetEdges = runtimeEdges.filter((item) => item.source === node.nodeId);
|
||||
const skipHandleIds = targetEdges.map((item) => item.sourceHandle);
|
||||
|
||||
toolRunResponse = getErrText(error);
|
||||
|
||||
// Skip all edges and return error
|
||||
return {
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
|
||||
@@ -130,8 +130,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
|
||||
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
|
||||
{
|
||||
moduleName: plugin.name,
|
||||
totalPoints: usagePoints,
|
||||
tokens: 0
|
||||
totalPoints: usagePoints
|
||||
}
|
||||
],
|
||||
[DispatchNodeResponseKeyEnum.toolResponses]: output?.pluginOutput
|
||||
|
||||
@@ -153,8 +153,7 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
|
||||
[DispatchNodeResponseKeyEnum.nodeDispatchUsages]: [
|
||||
{
|
||||
moduleName: appData.name,
|
||||
totalPoints: usagePoints,
|
||||
tokens: 0
|
||||
totalPoints: usagePoints
|
||||
}
|
||||
],
|
||||
[DispatchNodeResponseKeyEnum.toolResponses]: text,
|
||||
|
||||
@@ -177,17 +177,17 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
|
||||
if (!httpJsonBody) return {};
|
||||
if (httpContentType === ContentTypes.json) {
|
||||
httpJsonBody = replaceStringVariables(httpJsonBody);
|
||||
|
||||
const replaceJsonBody = httpJsonBody.replace(/(".*?")\s*:\s*undefined\b/g, '$1: null');
|
||||
|
||||
// Json body, parse and return
|
||||
const jsonParse = json5.parse(
|
||||
httpJsonBody.replace(/(".*?")\s*:\s*undefined\b/g, '$1: null')
|
||||
);
|
||||
const jsonParse = json5.parse(replaceJsonBody);
|
||||
const removeSignJson = removeUndefinedSign(jsonParse);
|
||||
return removeSignJson;
|
||||
}
|
||||
httpJsonBody = replaceStringVariables(httpJsonBody);
|
||||
return httpJsonBody.replaceAll(UNDEFINED_SIGN, 'null');
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return Promise.reject(`Invalid JSON body: ${httpJsonBody}`);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -31,7 +31,7 @@ export const dispatchQueryExtension = async ({
|
||||
const queryExtensionModel = getLLMModel(model);
|
||||
const chatHistories = getHistories(history, histories);
|
||||
|
||||
const { extensionQueries, tokens } = await queryExtension({
|
||||
const { extensionQueries, inputTokens, outputTokens } = await queryExtension({
|
||||
chatBg: systemPrompt,
|
||||
query: userChatInput,
|
||||
histories: chatHistories,
|
||||
@@ -42,7 +42,8 @@ export const dispatchQueryExtension = async ({
|
||||
|
||||
const { totalPoints, modelName } = formatModelChars2Points({
|
||||
model: queryExtensionModel.model,
|
||||
tokens,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
modelType: ModelTypeEnum.llm
|
||||
});
|
||||
|
||||
@@ -59,7 +60,8 @@ export const dispatchQueryExtension = async ({
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
totalPoints,
|
||||
model: modelName,
|
||||
tokens,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
query: userChatInput,
|
||||
textOutput: JSON.stringify(filterSameQueries)
|
||||
},
|
||||
@@ -68,7 +70,8 @@ export const dispatchQueryExtension = async ({
|
||||
moduleName: node.name,
|
||||
totalPoints,
|
||||
model: modelName,
|
||||
tokens
|
||||
inputTokens,
|
||||
outputTokens
|
||||
}
|
||||
],
|
||||
[NodeOutputKeyEnum.text]: JSON.stringify(filterSameQueries)
|
||||
|
||||
@@ -92,6 +92,7 @@ OutLinkSchema.virtual('associatedApp', {
|
||||
|
||||
try {
|
||||
OutLinkSchema.index({ shareId: -1 });
|
||||
OutLinkSchema.index({ teamId: 1, tmbId: 1, appId: 1 });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
43
packages/service/support/permission/auth/org.ts
Normal file
43
packages/service/support/permission/auth/org.ts
Normal 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);
|
||||
};
|
||||
@@ -18,7 +18,7 @@ export async function getUserChatInfoAndAuthTeamPoints(tmbId: string) {
|
||||
])
|
||||
.lean();
|
||||
|
||||
if (!tmb) return Promise.reject(UserErrEnum.unAuthUser);
|
||||
if (!tmb) return Promise.reject(UserErrEnum.binVisitor);
|
||||
|
||||
await checkTeamAIPoints(tmb.team._id);
|
||||
|
||||
|
||||
@@ -8,10 +8,7 @@ import { authOpenApiKey } from '../openapi/auth';
|
||||
import { FileTokenQuery } from '@fastgpt/global/common/file/type';
|
||||
import { MongoResourcePermission } from './schema';
|
||||
import { ClientSession } from 'mongoose';
|
||||
import {
|
||||
PermissionValueType,
|
||||
ResourcePermissionType
|
||||
} from '@fastgpt/global/support/permission/type';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { bucketNameMap } from '@fastgpt/global/common/file/constants';
|
||||
import { addMinutes } from 'date-fns';
|
||||
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 { TeamMemberSchema } from '@fastgpt/global/support/user/team/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
|
||||
* 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
|
||||
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 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;
|
||||
return concatPer([...groupPers, ...orgPers]);
|
||||
};
|
||||
|
||||
/* 仅取 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({
|
||||
resourceId,
|
||||
resourceType,
|
||||
@@ -155,10 +131,17 @@ export const getClbsAndGroupsWithInfo = async ({
|
||||
resourceType,
|
||||
teamId
|
||||
}: {
|
||||
resourceId: ParentIdType;
|
||||
resourceType: Omit<`${PerResourceTypeEnum}`, 'team'>;
|
||||
teamId: string;
|
||||
}) =>
|
||||
} & (
|
||||
| {
|
||||
resourceId: ParentIdType;
|
||||
resourceType: Omit<`${PerResourceTypeEnum}`, 'team'>;
|
||||
}
|
||||
| {
|
||||
resourceType: 'team';
|
||||
resourceId?: undefined;
|
||||
}
|
||||
)) =>
|
||||
Promise.all([
|
||||
MongoResourcePermission.find({
|
||||
teamId,
|
||||
@@ -170,7 +153,7 @@ export const getClbsAndGroupsWithInfo = async ({
|
||||
})
|
||||
.populate<{ tmb: TeamMemberSchema & { user: UserModelSchema } }>({
|
||||
path: 'tmb',
|
||||
select: 'name userId',
|
||||
select: 'name userId role',
|
||||
populate: {
|
||||
path: 'user',
|
||||
select: 'avatar'
|
||||
@@ -186,6 +169,16 @@ export const getClbsAndGroupsWithInfo = async ({
|
||||
}
|
||||
})
|
||||
.populate<{ group: MemberGroupSchemaType }>('group', 'name avatar')
|
||||
.lean(),
|
||||
MongoResourcePermission.find({
|
||||
teamId,
|
||||
resourceId,
|
||||
resourceType,
|
||||
orgId: {
|
||||
$exists: true
|
||||
}
|
||||
})
|
||||
.populate<{ org: OrgSchemaType }>({ path: 'org', select: 'name avatar' })
|
||||
.lean()
|
||||
]);
|
||||
|
||||
@@ -196,6 +189,7 @@ export const delResourcePermission = ({
|
||||
session,
|
||||
tmbId,
|
||||
groupId,
|
||||
orgId,
|
||||
...props
|
||||
}: {
|
||||
resourceType: PerResourceTypeEnum;
|
||||
@@ -204,15 +198,18 @@ export const delResourcePermission = ({
|
||||
session?: ClientSession;
|
||||
tmbId?: string;
|
||||
groupId?: string;
|
||||
orgId?: string;
|
||||
}) => {
|
||||
// tmbId or groupId only one and not both
|
||||
if (!!tmbId === !!groupId) {
|
||||
// either tmbId or groupId or orgId must be provided
|
||||
if (!tmbId && !groupId && !orgId) {
|
||||
return Promise.reject(CommonErrEnum.missingParams);
|
||||
}
|
||||
|
||||
return MongoResourcePermission.deleteOne(
|
||||
{
|
||||
...(tmbId ? { tmbId } : {}),
|
||||
...(groupId ? { groupId } : {}),
|
||||
...(orgId ? { orgId } : {}),
|
||||
...props
|
||||
},
|
||||
{ session }
|
||||
@@ -250,7 +247,7 @@ export function authJWT(token: string) {
|
||||
}>((resolve, reject) => {
|
||||
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) {
|
||||
reject(ERROR_ENUM.unAuthorization);
|
||||
return;
|
||||
@@ -436,7 +433,7 @@ export const authFileToken = (token?: string) =>
|
||||
}
|
||||
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) {
|
||||
reject(ERROR_ENUM.unAuthFile);
|
||||
return;
|
||||
@@ -450,10 +447,10 @@ export const authFileToken = (token?: string) =>
|
||||
});
|
||||
});
|
||||
|
||||
export const getGroupPer = (groups: PermissionValueType[] = []) => {
|
||||
if (groups.length === 0) {
|
||||
export const concatPer = (perList: PermissionValueType[] = []) => {
|
||||
if (perList.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return new Permission().addPer(...groups).value;
|
||||
return new Permission().addPer(...perList).value;
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { mongoSessionRun } from '../../common/mongo/sessionRun';
|
||||
import { MongoResourcePermission } from './schema';
|
||||
import { ClientSession, Model } from 'mongoose';
|
||||
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import type { ClientSession, Model } from 'mongoose';
|
||||
import type { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||
import { getResourceClbsAndGroups } from './controller';
|
||||
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
|
||||
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
import type { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
|
||||
import type { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||
|
||||
export type SyncChildrenPermissionResourceType = {
|
||||
_id: string;
|
||||
@@ -18,6 +18,7 @@ export type UpdateCollaboratorItem = {
|
||||
} & RequireOnlyOne<{
|
||||
tmbId: string;
|
||||
groupId: string;
|
||||
orgId: string;
|
||||
}>;
|
||||
|
||||
// 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.
|
||||
*/
|
||||
export async function syncCollaborators({
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type';
|
||||
import { MongoGroupMemberModel } from './groupMemberSchema';
|
||||
import { TeamMemberSchema } from '@fastgpt/global/support/user/team/type';
|
||||
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { MongoResourcePermission } from '../schema';
|
||||
import { getGroupPer, parseHeaderCert } from '../controller';
|
||||
import { parseHeaderCert } from '../controller';
|
||||
import { MongoMemberGroupModel } from './memberGroupSchema';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
import { ClientSession } from 'mongoose';
|
||||
@@ -50,26 +47,32 @@ export const getTeamDefaultGroup = async ({
|
||||
export const getGroupsByTmbId = async ({
|
||||
tmbId,
|
||||
teamId,
|
||||
role
|
||||
role,
|
||||
session
|
||||
}: {
|
||||
tmbId: string;
|
||||
teamId: string;
|
||||
role?: `${GroupMemberRole}`[];
|
||||
session?: ClientSession;
|
||||
}) =>
|
||||
(
|
||||
await Promise.all([
|
||||
(
|
||||
await MongoGroupMemberModel.find({
|
||||
tmbId,
|
||||
groupId: {
|
||||
$exists: true
|
||||
await MongoGroupMemberModel.find(
|
||||
{
|
||||
tmbId,
|
||||
groupId: {
|
||||
$exists: true
|
||||
},
|
||||
...(role ? { role: { $in: role } } : {})
|
||||
},
|
||||
...(role ? { role: { $in: role } } : {})
|
||||
})
|
||||
undefined,
|
||||
{ session }
|
||||
)
|
||||
.populate<{ group: MemberGroupSchemaType }>('group')
|
||||
.lean()
|
||||
).map((item) => item.group),
|
||||
role ? [] : getTeamDefaultGroup({ teamId })
|
||||
role ? [] : getTeamDefaultGroup({ teamId, session })
|
||||
])
|
||||
).flat();
|
||||
|
||||
@@ -79,46 +82,6 @@ export const getGroupMembersByGroupId = async (groupId: string) => {
|
||||
}).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
|
||||
export const authGroupMemberRole = async ({
|
||||
groupId,
|
||||
@@ -140,8 +103,12 @@ export const authGroupMemberRole = async ({
|
||||
tmbId
|
||||
};
|
||||
}
|
||||
const groupMember = await MongoGroupMemberModel.findOne({ groupId, tmbId });
|
||||
const tmb = await getTmbInfoByTmbId({ tmbId });
|
||||
const [groupMember, tmb] = await Promise.all([
|
||||
MongoGroupMemberModel.findOne({ groupId, tmbId }),
|
||||
getTmbInfoByTmbId({ tmbId })
|
||||
]);
|
||||
|
||||
// Team admin or role check
|
||||
if (tmb.permission.hasManagePer || (groupMember && role.includes(groupMember.role))) {
|
||||
return {
|
||||
...result,
|
||||
|
||||
95
packages/service/support/permission/org/controllers.ts
Normal file
95
packages/service/support/permission/org/controllers.ts
Normal 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 }
|
||||
);
|
||||
}
|
||||
65
packages/service/support/permission/org/orgMemberSchema.ts
Normal file
65
packages/service/support/permission/org/orgMemberSchema.ts
Normal 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
|
||||
);
|
||||
77
packages/service/support/permission/org/orgSchema.ts
Normal file
77
packages/service/support/permission/org/orgSchema.ts
Normal 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);
|
||||
@@ -6,6 +6,7 @@ import { connectionMongo, getMongoModel } from '../../common/mongo';
|
||||
import type { ResourcePermissionType } from '@fastgpt/global/support/permission/type';
|
||||
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
|
||||
import { MemberGroupCollectionName } from './memberGroup/memberGroupSchema';
|
||||
import { OrgCollectionName } from '@fastgpt/global/support/user/team/org/constant';
|
||||
const { Schema } = connectionMongo;
|
||||
|
||||
export const ResourcePermissionCollectionName = 'resource_permissions';
|
||||
@@ -23,6 +24,10 @@ export const ResourcePermissionSchema = new Schema({
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: MemberGroupCollectionName
|
||||
},
|
||||
orgId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: OrgCollectionName
|
||||
},
|
||||
resourceType: {
|
||||
type: String,
|
||||
enum: Object.values(PerResourceTypeEnum),
|
||||
@@ -51,6 +56,12 @@ ResourcePermissionSchema.virtual('group', {
|
||||
foreignField: '_id',
|
||||
justOne: true
|
||||
});
|
||||
ResourcePermissionSchema.virtual('org', {
|
||||
ref: OrgCollectionName,
|
||||
localField: 'orgId',
|
||||
foreignField: '_id',
|
||||
justOne: true
|
||||
});
|
||||
|
||||
try {
|
||||
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(
|
||||
{
|
||||
resourceType: 1,
|
||||
@@ -87,6 +115,7 @@ try {
|
||||
}
|
||||
);
|
||||
|
||||
// Delete tmb permission
|
||||
ResourcePermissionSchema.index({
|
||||
resourceType: 1,
|
||||
teamId: 1,
|
||||
|
||||
@@ -19,9 +19,7 @@ export const checkDatasetLimit = async ({
|
||||
if (!standardConstants) return;
|
||||
|
||||
if (usedDatasetSize + insertLen >= datasetMaxSize) {
|
||||
return Promise.reject(
|
||||
`您的知识库容量为: ${datasetMaxSize}组,已使用: ${usedDatasetSize}组,导入当前文件需要: ${insertLen}组,请增加知识库容量后导入。`
|
||||
);
|
||||
return Promise.reject(TeamErrEnum.datasetSizeNotEnough);
|
||||
}
|
||||
|
||||
if (usedPoints >= totalPoints) {
|
||||
|
||||
@@ -3,22 +3,10 @@ const { Schema } = connectionMongo;
|
||||
import { hashStr } from '@fastgpt/global/common/string/tools';
|
||||
import type { UserModelSchema } from '@fastgpt/global/support/user/type';
|
||||
import { UserStatusEnum, userStatusMap } from '@fastgpt/global/support/user/constant';
|
||||
import { getRandomUserAvatar } from '@fastgpt/global/support/user/utils';
|
||||
|
||||
export const userCollectionName = 'users';
|
||||
|
||||
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({
|
||||
status: {
|
||||
type: String,
|
||||
@@ -47,7 +35,7 @@ const UserSchema = new Schema({
|
||||
},
|
||||
avatar: {
|
||||
type: String,
|
||||
default: defaultAvatars[Math.floor(Math.random() * defaultAvatars.length)]
|
||||
default: () => getRandomUserAvatar()
|
||||
},
|
||||
|
||||
promotionRate: {
|
||||
@@ -78,11 +66,8 @@ const UserSchema = new Schema({
|
||||
});
|
||||
|
||||
try {
|
||||
// login
|
||||
UserSchema.index({ username: 1, password: 1 }, { background: true });
|
||||
|
||||
// Admin charts
|
||||
UserSchema.index({ createTime: -1 }, { background: true });
|
||||
UserSchema.index({ createTime: -1 });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import { MongoMemberGroupModel } from '../../permission/memberGroup/memberGroupS
|
||||
import { mongoSessionRun } from '../../../common/mongo/sessionRun';
|
||||
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
|
||||
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> {
|
||||
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 {
|
||||
userId: String(tmb.userId),
|
||||
teamId: String(tmb.teamId),
|
||||
teamAvatar: tmb.team.avatar,
|
||||
teamName: tmb.team.name,
|
||||
memberName: tmb.name,
|
||||
avatar: tmb.team.avatar,
|
||||
@@ -77,13 +80,11 @@ export async function createDefaultTeam({
|
||||
userId,
|
||||
teamName = 'My Team',
|
||||
avatar = '/icon/logo.svg',
|
||||
balance,
|
||||
session
|
||||
}: {
|
||||
userId: string;
|
||||
teamName?: string;
|
||||
avatar?: string;
|
||||
balance?: number;
|
||||
session: ClientSession;
|
||||
}) {
|
||||
// auth default team
|
||||
@@ -100,7 +101,6 @@ export async function createDefaultTeam({
|
||||
ownerId: userId,
|
||||
name: teamName,
|
||||
avatar,
|
||||
balance,
|
||||
createTime: new Date()
|
||||
}
|
||||
],
|
||||
@@ -132,15 +132,11 @@ export async function createDefaultTeam({
|
||||
],
|
||||
{ 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;
|
||||
} else {
|
||||
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;
|
||||
})();
|
||||
|
||||
await MongoTeam.findByIdAndUpdate(
|
||||
// This is where we get the old team
|
||||
const team = await MongoTeam.findByIdAndUpdate(
|
||||
teamId,
|
||||
{
|
||||
$set: {
|
||||
@@ -241,6 +238,8 @@ export async function updateTeam({
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
|
||||
await refreshSourceAvatar(avatar, team?.avatar, session);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user