Compare commits

...

24 Commits

Author SHA1 Message Date
Archer
3a4b4a866b Team group (#2864)
* feat(member-group): Team (#2616)

* feat: member-group schema define

* feat(fe): create group

* feat: add group edit modal

* feat(fe): add avatar group component

* feat: edit group
fix: permission select menu style

* feat: bio-mode support for select-member component

* fix: avatar group key unique

* feat: group manage

* feat: divide member into group and clbs

* feat: finish team permission

* chore: adjust

* fix: get clbs

* perf: groups code

* pref: member group for team (#2706)

* chore: fe adjust
fix: remove the member from groups when removing from team
feat: change the groups avatar when updating the team's avatar

* chore: DefaultGroupName as a constant string ''

* fix: create default group when create team for root

* feat: comment

* feat: 4811 init

* pref: member group for team (#2732)

* chore: default group name

* feat: get default group when get by tmbid

* feat(fe): adjust

* member ui

* fix: delete group (#2736)

* perf: init4811

* pref: member group (#2818)

* fix: update clb per then refetch clb list

* fix: calculate group permission

* feat(fe): group tag

* refactor(fe): team and group manage

* feat: manage group member

* feat: add group transfer owner modal

* feat: group manage member

* chore: adjust the file structure

* pref: member group

* chore: adjust fe style

* fix: ts error

* chore: fe adjust

* chore: fe adjust

* chore: adjust

* chore: adjust the code

* perf: i18n and schema name

* pref: member-group (#2862)

* feat: group list ordered by updateTime

* fix: transfer ownership of group when deleting member

* fix: i18n fix

* feat: can not set member as admin/owner when user is not active

* fix: GroupInfoModal hover input do not change color

* fix(fe): searchinput do not scroll

* perf: team group ui

* doc

* remove enum

---------

Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
2024-10-09 18:32:10 +08:00
Finley Ge
7afa8f00b8 pref: trim the input of outlink (#2860) 2024-10-09 14:05:23 +08:00
Archer
f6c5695df4 Optimize base64 storage in files to support concurrent storage (#2856)
* fix: variables check

* remove log

* perf: file img saved

* update doc
2024-10-08 12:58:33 +08:00
papapatrick
dd3a1b910b docs: add FAQ (#2833) 2024-10-08 12:36:23 +08:00
Archer
a66d9d2e98 4.8.11 test (#2850)
* fix: variables check

* remove log

* fix: variables refresh

* perf: team select button

* perf: remove change fn
2024-10-04 10:25:20 +08:00
Archer
fc6f28f26e fix: variables check (#2849)
* fix: variables check

* remove log
2024-10-01 11:11:19 +08:00
Archer
d4b99ddcab fix: login ip check (#2848) 2024-09-30 18:24:03 +08:00
Archer
df328b2a73 fix: comment node cannot save (#2847) 2024-09-30 18:16:20 +08:00
Archer
fadb389294 fix: loop node cannot save parentId (#2845) 2024-09-30 17:58:17 +08:00
Archer
7c38d1da9a 4.8.11 test (#2843)
* feat: app version test

* update doc

* fix: paging num error

* fix: doc api domain

* rename variable

* perf: memment node min size
2024-09-30 17:28:03 +08:00
heheer
7c829febec feat: add context menu & comment node (#2834)
* feat: add comment node

* useMemo
2024-09-29 10:08:19 +08:00
heheer
7bdff9ce9c perf: change tool params type label & enum input conditional rendering (#2835) 2024-09-28 18:00:22 +08:00
heheer
1599d144ce feat: add tool params node & tool params add array type (#2824)
* add tool params node

* add tool params enum

* node response

* tool add array type params

* fix tool params

* fix

* fix

* fix
2024-09-28 15:58:55 +08:00
Archer
f2749cbb00 4.8.11 perf (#2832)
* save toast

* perf: surya ocr

* perf: remove same model name

* fix: indexes

* perf: ip check

* feat: Fixed the version number of the subapplication

* feat: simple app get latest child version

* perf: update child dispatch variables

* feat: variables update doc
2024-09-28 15:31:25 +08:00
heheer
f7a8203454 fix:variable not update in nested workflow runs (#2830) 2024-09-28 13:59:35 +08:00
Archer
d95f71e9e3 Update dataset_engine.md (#2829) 2024-09-28 10:08:54 +08:00
Deepturn
a43d845298 Update configuration.md (#2828)
修改 claude
2024-09-28 09:57:16 +08:00
papapatrick
3e64f46d92 fix: change ip detect url (#2827) 2024-09-27 22:19:48 +08:00
yiming-alicloud
0335f16742 submit ocr module (#2815) 2024-09-27 16:07:28 +08:00
Archer
98dbec2cf7 4.8.11 fix (#2822)
* fix: tool choice hostiry error

* fix: chat page auth error redirect

* perf: ip redirect tip

* feat: fedomain env

* fix: tool desc empty

* feat: 4811 doc
2024-09-27 15:52:33 +08:00
silencezhang7
d259eda6b4 新增BI圖標功能-柱狀圖 (#2779)
* 新增BI圖標功能-柱狀圖

* 新增BI图表功能-柱狀圖,优化代码,删除无用代码

* 优化生成逻辑,支持插件手动选择图表类型:目前支持柱状图,折线图,饼图

* 修改包名称,完成基础图表
2024-09-27 13:50:42 +08:00
heheer
7c8f2ab6f5 perf: support prompt editor dynamic height increase & modify aichat placeholder (#2817) 2024-09-27 13:45:44 +08:00
papapatrick
691476c821 feat: add login page ip detect (#2819)
* feat: add login page ip detect

* code perf
2024-09-27 13:39:10 +08:00
Archer
efcb53cd6d Rename node(#2814) 2024-09-26 18:25:42 +08:00
225 changed files with 5733 additions and 1419 deletions

View File

@@ -24,5 +24,10 @@
"i18n-ally.translate.engines": ["google"],
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
},
"markdown.copyFiles.destination": {
"/docSite/content/**/*": "${documentWorkspaceFolder}/docSite/assets/imgs/"
},
"markdown.copyFiles.overwriteBehavior": "nameIncrementally",
"markdown.copyFiles.transformPath": "const filename = uri.path.split('/').pop(); return `/imgs/${filename}`;"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,7 +1,7 @@
---
weight: 1100
title: '商业版介绍'
description: 'FastGPT 商业版介绍'
title: '收费说明'
description: 'FastGPT 收费说明'
icon: 'shopping_cart'
draft: false
images: []

View File

@@ -4,7 +4,7 @@ description: 'FastGPT 线上版定价'
icon: 'currency_yen'
draft: false
toc: true
weight: 1200
weight: 1002
type: redirect
target: https://cloud.tryfastgpt.ai/price
---

View File

@@ -42,7 +42,7 @@ FastGPT 采用了 RAG 中的 Embedding 方案构建知识库,要使用好 Fast
FastGPT 采用了`PostgresSQL``PG Vector`插件作为向量检索器,索引为`HNSW`。且`PostgresSQL`仅用于向量检索(该引擎可以替换成其它数据库),`MongoDB`用于其他数据的存取。
`MongoDB``dataset.datas`表中,会存储向量原数据的信息,同时有一个`indexes`字段会记录其对应的向量ID这是一个数组也就是说一组向量可以对应多组数据
`MongoDB``dataset.datas`表中,会存储向量原数据的信息,同时有一个`indexes`字段会记录其对应的向量ID这是一个数组也就是说一组数据可以对应多个向量
`PostgresSQL`的表中,设置一个`vector`字段用于存储向量。在检索时会先召回向量再根据向量的ID`MongoDB`中寻找原数据内容,如果对应了同一组原数据,则进行合并,向量得分取最高得分。

View File

@@ -27,7 +27,7 @@ Tips: 安全起见,你可以设置一个额度或者过期时间,放置 key
## 替换三方应用的变量
```bash
OPENAI_API_BASE_URL: https://api.tryfastgpt.ai/api (改成自己部署的域名)
OPENAI_API_BASE_URL: https://api.fastgpt.in/api (改成自己部署的域名)
OPENAI_API_KEY = 上一步获取到的密钥
```

View File

@@ -192,7 +192,7 @@ weight: 708
- /imgs/model/baichuan.svg - 百川
- /imgs/model/chatglm.svg - 智谱
- /imgs/model/calude.svg - calude
- /imgs/model/claude.svg - claude
- /imgs/model/ernie.svg - 文心一言
- /imgs/model/moonshot.svg - 月之暗面
- /imgs/model/openai.svg - OpenAI GPT

View File

@@ -32,7 +32,7 @@ FastGPT 的 API Key **有 2 类**,一类是全局通用的 key (无法直接
OpenAPI 中,所有的接口都通过 Header.Authorization 进行鉴权。
```
baseUrl: "https://api.tryfastgpt.ai/api"
baseUrl: "https://api.fastgpt.in/api"
headers: {
Authorization: "Bearer {{apikey}}"
}
@@ -41,7 +41,7 @@ headers: {
**发起应用对话示例**
```sh
curl --location --request POST 'https://api.tryfastgpt.ai/api/v1/chat/completions' \
curl --location --request POST 'https://api.fastgpt.in/api/v1/chat/completions' \
--header 'Authorization: Bearer fastgpt-xxxxxx' \
--header 'Content-Type: application/json' \
--data-raw '{

View File

@@ -29,7 +29,7 @@ weight: 852
{{< markdownify >}}
```bash
curl --location --request POST 'https://api.tryfastgpt.ai/api/v1/chat/completions' \
curl --location --request POST 'https://api.fastgpt.in/api/v1/chat/completions' \
--header 'Authorization: Bearer fastgpt-xxxxxx' \
--header 'Content-Type: application/json' \
--data-raw '{

View File

@@ -22,7 +22,7 @@ weight: 853
**新例子**
```bash
curl --location --request POST 'https://api.tryfastgpt.ai/api/support/wallet/usage/createTrainingUsage' \
curl --location --request POST 'https://api.fastgpt.in/api/support/wallet/usage/createTrainingUsage' \
--header 'Authorization: Bearer {{apikey}}' \
--header 'Content-Type: application/json' \
--data-raw '{
@@ -34,7 +34,7 @@ curl --location --request POST 'https://api.tryfastgpt.ai/api/support/wallet/usa
**x例子**
```bash
curl --location --request POST 'https://api.tryfastgpt.ai/api/support/wallet/bill/createTrainingBill' \
curl --location --request POST 'https://api.fastgpt.in/api/support/wallet/bill/createTrainingBill' \
--header 'Authorization: Bearer {{apikey}}' \
--header 'Content-Type: application/json' \
--data-raw '{
@@ -991,7 +991,7 @@ curl --location --request DELETE 'http://localhost:3000/api/core/dataset/collect
{{< markdownify >}}
```bash
curl --location --request POST 'https://api.tryfastgpt.ai/api/core/dataset/data/pushData' \
curl --location --request POST 'https://api.fastgpt.in/api/core/dataset/data/pushData' \
--header 'Authorization: Bearer apikey' \
--header 'Content-Type: application/json' \
--data-raw '{
@@ -1328,7 +1328,7 @@ curl --location --request DELETE 'http://localhost:3000/api/core/dataset/data/de
{{< markdownify >}}
```bash
curl --location --request POST 'https://api.tryfastgpt.ai/api/core/dataset/searchTest' \
curl --location --request POST 'https://api.fastgpt.in/api/core/dataset/searchTest' \
--header 'Authorization: Bearer fastgpt-xxxxx' \
--header 'Content-Type: application/json' \
--data-raw '{

View File

@@ -80,29 +80,36 @@ weight: 813
### 3. 修改镜像 tag 并重启
- 更新 FastGPT 镜像 tag: v4.8.11-alpha
- 更新 FastGPT 商业版镜像 tag: v4.8.11-alpha
- 更新 FastGPT Sandbox 镜像 tag: v4.8.11-alpha
## V4.8.11 更新说明
1. 新增 - 表单输入节点,允许用户在工作流中让用户输入一些信息。
2. 新增 - 循环运行节点,可传入数组进行批量调用,目前最多支持 50 长度的数组串行执行。
3. 新增 - 节点支持折叠
4. 新增 - 聊天记录滚动加载,不再只加载 30 条
5. 新增 - 工作流增加触摸板优先模式,可以通过工作流右下角按键进行切换
6. 新增 - 沙盒增加字符串转 base64 全局方法(全局变量 strToBase64)
7. 新增 - 支持 Openai o1 模型,需增加模型的 `defaultConfig` 配置,覆盖 `temperature``max_tokens``stream`配置o1 不支持 stream 模式, 详细可重新拉取 `config.json` 配置文件查看
8. 新增 - AI 对话节点知识库引用,支持配置 role=system 和 role=user已配置的过自定义提示词的节点将会保持 user 模式,其余用户将转成 system 模式
9. 新增 - 插件支持上传系统文件
10. 新增 - 插件输出,支持指定字段作为工具响应
11. 新增 - 支持工作流嵌套子应用时,可以设置`非流模式`,同时简易模式也可以选择工作流作为插件了,简易模式调用子应用时,都将强制使用非流模式
12. 新增 - 调试模式下,子应用调用,支持返回详细运行数据
13. 新增 - 保留所有模式下子应用嵌套调用的日志
14. 优化 - 工作流嵌套层级限制 20 层,避免因编排不合理导致的无限死循环
15. 优化 - 工作流 handler 性能优化
16. 优化 - 工作流快捷键,避免调试测试时也会触发复制和回退
17. 优化 - 流输出,切换浏览器 Tab 后仍可以继续输出
18. 优化 - 完善外部文件知识库相关 API
19. 修复 - 知识库选择权限问题
20. 修复 - 空 chatId 发起对话,首轮携带用户选择时会异常
21. 修复 - createDataset 接口intro 为赋值
22. 修复 - 对话框渲染性能问题。
3. 新增 - 自定义工具变量节点,可以为工具调用子流程完全自定义变量。在构建复杂 Agent 时有帮助
4. 新增 - 节点支持折叠
5. 新增 - 聊天记录滚动加载,不再只加载 30 条
6. 新增 - 工作流增加触摸板优先模式,可以通过工作流右下角按键进行切换
7. 新增 - 沙盒增加字符串转 base64 全局方法(全局变量 strToBase64)
8. 新增 - 支持 Openai o1 模型,需增加模型的 `defaultConfig` 配置,覆盖 `temperature``max_tokens``stream`配置o1 不支持 stream 模式, 详细可重新拉取 `config.json` 配置文件查看
9. 新增 - AI 对话节点知识库引用,支持配置 role=system 和 role=user已配置的过自定义提示词的节点将会保持 user 模式,其余用户将转成 system 模式
10. 新增 - 插件支持上传系统文件
11. 新增 - 子应用嵌套调用时,版本锁定。主应用未主动更新版本时,不会取最新版进行执行,保证主应用服务稳定
12. 新增 - 插件输出,支持指定字段作为工具响应
13. 新增 - 支持工作流嵌套子应用时,可以设置`非流模式`,同时简易模式也可以选择工作流作为插件了,简易模式调用子应用时,都将强制使用非流模式
14. 新增 - 调试模式下,子应用调用,支持返回详细运行数据
15. 新增 - 保留所有模式下子应用嵌套调用的日志
16. 新增 - 商业版支持团队成员组,后续将逐渐覆盖工作台和知识库权限
17. 优化 - 工作流嵌套层级限制 20 层,避免因编排不合理导致的无限死循环
18. 优化 - 工作流 handler 性能优化。
19. 优化 - 工作流快捷键,避免调试测试时也会触发复制和回退
20. 修复 - 工作流工具调用中修改全局变量后,无法传递到后续流程
21. 优化 - 流输出,切换浏览器 Tab 后仍可以继续输出
22. 优化 - 完善外部文件知识库相关 API
23. 修复 - 知识库选择权限问题。
24. 修复 - 空 chatId 发起对话,首轮携带用户选择时会异常。
25. 修复 - createDataset 接口intro 为赋值。
26. 修复 - 对话框渲染性能问题。
27. 修复 - 工具调用历史记录存储不正确。

View File

@@ -0,0 +1,11 @@
---
title: 'FAQ'
description: '常见问题的解答'
icon: 'quiz'
draft: false
toc: true
weight: 900
---
<!-- 9800 ~ 1000 -->
FastGPT 是一个由用户和贡献者参与推动的开源项目,如果您对产品使用存在疑问和建议,可尝试[加入社区](community)寻求支持。我们的团队与社区会竭尽所能为您提供帮助。

View File

@@ -0,0 +1,16 @@
---
title: '应用使用问题'
description: 'FastGPT 常见应用使用问题,包括简易应用、工作流和插件'
icon: 'quiz'
draft: false
toc: true
weight: 903
---
## 工作流中多轮对话场景中如何使连续问题被问题分类节点正确的归类
问题分类节点具有获取上下文信息的能力,当处理两个关联性较大的问题时,模型的判断准确性往往依赖于这两个问题之间的联系和模型的能力。例如,当用户先问“我该如何使用这个功能?”接着又询问“这个功能有什么限制?”时,模型借助上下文信息,就能够更精准地理解并响应。
但是,当连续问题之间的关联性较小,模型判断的准确度可能会受到限制。在这种情况下,我们可以引入全局变量的概念来记录分类结果。在后续的问题分类阶段,首先检查全局变量是否存有分类结果。如果有,那么直接沿用该结果;若没有,则让模型自行判断。
建议:构建批量运行脚本进行测试,评估问题分类的准确性。

View File

@@ -0,0 +1,18 @@
---
title: '聊天框问题'
description: 'FastGPT 常见聊天框问题'
icon: 'quiz'
draft: false
toc: true
weight: 905
---
## 我修改了工作台的应用,为什么在“聊天”时没有更新配置?
应用需要点击发布后,聊天才会更新应用。
## 浏览器不支持语音输入
1. 首先需要确保浏览器、电脑本身麦克风权限的开启。
2. 确认浏览器允许该站点使用麦克风,并且选择正确的麦克风来源。
3. 需有 SSL 证书的站点才可以使用麦克风。

View File

@@ -0,0 +1,17 @@
---
title: '知识库使用问题'
description: '常见知识库使用问题'
icon: 'quiz'
draft: false
toc: true
weight: 904
---
## 上传的文件内容出现中文乱码
将文件另存为 UTF-8 编码格式。
## 知识库配置里的文件处理模型是什么?与索引模型有什么区别?
* **文件处理模型**:用于数据处理的【增强处理】和【问答拆分】。在【增强处理】中,生成相关问题和摘要,在【问答拆分】中执行问答对生成。
* **索引模型**:用于向量化,即通过对文本数据进行处理和组织,构建出一个能够快速查询的数据结构。

View File

@@ -0,0 +1,10 @@
---
title: 'Docker 部署问题'
description: 'FastGPT Docker 部署问题'
icon: ''
draft: false
toc: true
weight: 901
type: redirect
target: /docs/development/docker/#faq
---

View File

@@ -0,0 +1,7 @@
---
title: '常见错误'
icon: 'quiz'
draft: false
toc: true
weight: 920
---

View File

@@ -0,0 +1,11 @@
---
title: '其他问题'
icon: 'quiz'
draft: false
toc: true
weight: 925
---
## oneapi 官网是哪个
只有开源的 README没官网GitHub: https://github.com/songquanpeng/one-api

View File

@@ -0,0 +1,10 @@
---
title: "私有部署常见问题"
description: "FastGPT 私有部署常见问题"
icon: upgrade
draft: false
images: []
weight: 902
type: redirect
target: /docs/development/faq/
---

View File

@@ -26,7 +26,7 @@ weight: 504
## 3. 创建 docker-compose.yml 文件
只需要修改 `OPEN_AI_API_KEY``OPEN_AI_API_BASE` 两个环境变量即可。其中 `OPEN_AI_API_KEY` 为第一步获取的密钥,`OPEN_AI_API_BASE` 为 FastGPT 的 OpenAPI 地址,例如:`https://api.tryfastgpt.ai/api/v1`
只需要修改 `OPEN_AI_API_KEY``OPEN_AI_API_BASE` 两个环境变量即可。其中 `OPEN_AI_API_KEY` 为第一步获取的密钥,`OPEN_AI_API_BASE` 为 FastGPT 的 OpenAPI 地址,例如:`https://api.fastgpt.in/api/v1`
随便找一个目录,创建一个 docker-compose.yml 文件,将下面的代码复制进去。
@@ -40,7 +40,7 @@ services:
- seccomp:unconfined
environment:
OPEN_AI_API_KEY: 'fastgpt-z51pkjqm9nrk03a1rx2funoy'
OPEN_AI_API_BASE: 'https://api.tryfastgpt.ai/api/v1'
OPEN_AI_API_BASE: 'https://api.fastgpt.in/api/v1'
MODEL: 'gpt-3.5-turbo'
CHANNEL_TYPE: 'wx'
PROXY: ''

View File

@@ -0,0 +1,87 @@
---
title: "代码运行"
description: "FastGPT 代码运行节点介绍"
icon: "input"
draft: false
toc: true
weight: 364
---
![alt text](/imgs/image.png)
## 功能
可用于执行一段简单的 js 代码用于进行一些复杂的数据处理。代码运行在沙盒中无法进行网络请求、dom和异步操作。如需复杂操作需外挂 HTTP 实现。
**注意事项**
- 私有化用户需要部署`fastgpt-sandbox` 镜像,并配置`SANDBOX_URL`环境变量。
- 沙盒最大运行 10s 32M 内存限制。
## 变量输入
可在自定义输入中添加代码运行需要的变量,在代码的 main 函数中,可解构出相同名字的变量。
如上图,自定义输入中有 data1 和 data2 两个变量main 函数中可以解构出相同名字的变量。
## 结果输出
务必返回一个 object 对象
自定义输出中,可以添加变量名来获取 object 对应 key 下的值。例如上图中,返回了一个对象:
```json
{
result: data1,
data2
}
```
他有 2 个 keyresult和 data2(js 缩写key=data2value=data2)。这时候自定义输出中就可以添加 2 个变量来获取对应 key 下的 value。
## 内置 JS 全局变量
### delay 延迟
延迟 1 秒后返回
```js
async function main({data1, data2}){
await delay(1000)
return {
result: "111"
}
}
```
### countToken 统计 token
```js
function main({input}){
return {
result: countToken(input)
}
}
```
![alt text](/imgs/image-1.png)
### strToBase64 字符串转 base64(4.8.11 版本新增)
可用于将 SVG 图片转换为 base64 格式展示。
```js
function main({input}){
return {
/*
param1: input 需要转换的字符串
param2: base64 prefix 前缀
*/
result: strToBase64(input,'data:image/svg+xml;base64,')
}
}
```
![alt text](/imgs/image-2.png)

View File

@@ -35,8 +35,6 @@ weight: 356
1. 无必须的参数尽管上下文中没有适合的参数也可以调用该工具。但有时候LLM会自己伪造一个参数。
2. 有必须的参数如果没有适合的参数LLM可能不会调用该工具。可以通过提示词引导用户提供参数。
### 工具调用逻辑
在支持`函数调用`的模型中,可以一次性调用多个工具,调用逻辑如下:
![](/imgs/flow-tool2.png)
@@ -49,10 +47,24 @@ weight: 356
高级编排中,托动工具调用的连接点,可用的工具头部会出现一个菱形,可以将它与工具调用模块底部的菱形相连接。
被连接的工具,会自动分离工具输入与普通的输入,并且可以编辑`介绍`,可以通过调整介绍,使得该工具调用时机更加精确。
被连接的工具,会自动分离工具输入与普通的输入,并且可以编辑`描述`,可以通过调整介绍,使得该工具调用时机更加精确。对于一些内置的节点,务必修改`描述`才能让模型正常调用。
关于工具调用,如何调试仍然是一个玄学,所以建议,不要一次性增加太多工具,选择少量工具调优后再进一步尝试。
## 组合节点
### 工具调用终止
工具调用默认会把子流程运行的结果作为`工具结果`,返回给模型进行回答。有时候,你可能不希望模型做回答,你可以给对应子流程的末尾增加上一个`工具调用终止`节点,这样,子流程的结果就不会被返回给模型。
![alt text](/imgs/image-3.png)
### 自定义工具变量
工具调用的子流程运行,有时候会依赖`AI`生成的一些变量,为了简化交互流程,我们给系统内置的节点都指定了`工具变量`。然而,有些时候,你需要的变量不仅是目标流程的`首个节点`的变量,而是需要更复杂的变量,此时你可以使用`自定义工具变量`。它允许你完全自定义该`工具流程`的变量。
![alt text](/imgs/image-4.png)
## 相关示例
- [谷歌搜索](/docs/workflow/examples/google_search/)

View File

@@ -10,7 +10,12 @@ export enum TeamErrEnum {
appAmountNotEnough = 'appAmountNotEnough',
pluginAmountNotEnough = 'pluginAmountNotEnough',
websiteSyncNotEnough = 'websiteSyncNotEnough',
reRankNotEnough = 'reRankNotEnough'
reRankNotEnough = 'reRankNotEnough',
groupNameEmpty = 'groupNameEmpty',
groupNameDuplicate = 'groupNameDuplicate',
groupNotExist = 'groupNotExist',
cannotDeleteDefaultGroup = 'cannotDeleteDefaultGroup',
userNotActive = 'userNotActive'
}
const teamErr = [
@@ -46,6 +51,26 @@ const teamErr = [
{
statusText: TeamErrEnum.reRankNotEnough,
message: i18nT('common:code_error.team_error.re_rank_not_enough')
},
{
statusText: TeamErrEnum.groupNameEmpty,
message: i18nT('common:code_error.team_error.group_name_empty')
},
{
statusText: TeamErrEnum.groupNotExist,
message: i18nT('common:code_error.team_error.group_not_exist')
},
{
statusText: TeamErrEnum.cannotDeleteDefaultGroup,
message: i18nT('common:code_error.team_error.cannot_delete_default_group')
},
{
statusText: TeamErrEnum.groupNameDuplicate,
message: i18nT('common:code_error.team_error.group_name_duplicate')
},
{
statusText: TeamErrEnum.userNotActive,
message: i18nT('common:code_error.team_error.user_not_active')
}
];

View File

@@ -16,6 +16,6 @@ export const bucketNameMap = {
}
};
export const ReadFileBaseUrl = '/api/common/file/read';
export const ReadFileBaseUrl = `${process.env.FE_DOMAIN || ''}/api/common/file/read`;
export const documentFileType = '.txt, .docx, .csv, .xlsx, .pdf, .md, .html, .pptx';

View File

@@ -7,6 +7,7 @@ export enum MongoImageTypeEnum {
datasetAvatar = 'datasetAvatar',
userAvatar = 'userAvatar',
teamAvatar = 'teamAvatar',
groupAvatar = 'groupAvatar',
chatImage = 'chatImage',
collectionImage = 'collectionImage'
@@ -36,6 +37,10 @@ export const mongoImageTypeMap = {
label: 'teamAvatar',
unique: true
},
[MongoImageTypeEnum.groupAvatar]: {
label: 'groupAvatar',
unique: true
},
[MongoImageTypeEnum.chatImage]: {
label: 'chatImage',

View File

@@ -8,3 +8,24 @@ export const retryRun = <T>(fn: () => T, retry = 2): T => {
throw error;
}
};
export const batchRun = async <T>(arr: T[], fn: (arr: T) => any, batchSize = 10) => {
const batchArr = new Array(batchSize).fill(null);
const result: any[] = [];
const batchFn = async () => {
const data = arr.shift();
if (data) {
result.push(await fn(data));
return batchFn();
}
};
await Promise.all(
batchArr.map(async () => {
await batchFn();
})
);
return result;
};

View File

@@ -1,3 +1,4 @@
import { batchRun } from '../fn/utils';
import { simpleText } from './tools';
/* Delete redundant text in markdown */
@@ -53,16 +54,19 @@ export const uploadMarkdownBase64 = async ({
const base64Arr = rawText.match(base64Regex) || [];
// upload base64 and replace it
for await (const base64Img of base64Arr) {
try {
const str = await uploadImgController(base64Img);
rawText = rawText.replace(base64Img, str);
} catch (error) {
rawText = rawText.replace(base64Img, '');
rawText = rawText.replace(/!\[.*\]\(\)/g, '');
}
}
await batchRun(
base64Arr,
async (base64Img) => {
try {
const str = await uploadImgController(base64Img);
rawText = rawText.replace(base64Img, str);
} catch (error) {
rawText = rawText.replace(base64Img, '');
rawText = rawText.replace(/!\[.*\]\(\)/g, '');
}
},
20
);
}
// Remove white space on both sides of the picture

View File

@@ -1,3 +1,4 @@
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`;

View File

@@ -289,7 +289,7 @@ export const GPTMessages2Chats = (
})
.filter((item) => item.value.length > 0);
// Merge data with the same dataId
// Merge data with the same dataIdSequential obj merging
const result = chatMessages.reduce((result: ChatItemType[], currentItem) => {
const lastItem = result[result.length - 1];

View File

@@ -37,6 +37,60 @@ export enum WorkflowIOValueTypeEnum {
selectDataset = 'selectDataset'
}
export const toolValueTypeList = [
{
label: WorkflowIOValueTypeEnum.string,
value: WorkflowIOValueTypeEnum.string,
jsonSchema: {
type: 'string'
}
},
{
label: WorkflowIOValueTypeEnum.number,
value: WorkflowIOValueTypeEnum.number,
jsonSchema: {
type: 'number'
}
},
{
label: WorkflowIOValueTypeEnum.boolean,
value: WorkflowIOValueTypeEnum.boolean,
jsonSchema: {
type: 'boolean'
}
},
{
label: 'array<string>',
value: WorkflowIOValueTypeEnum.arrayString,
jsonSchema: {
type: 'array',
items: {
type: 'string'
}
}
},
{
label: 'array<number>',
value: WorkflowIOValueTypeEnum.arrayNumber,
jsonSchema: {
type: 'array',
items: {
type: 'number'
}
}
},
{
label: 'array<boolean>',
value: WorkflowIOValueTypeEnum.arrayBoolean,
jsonSchema: {
type: 'array',
items: {
type: 'boolean'
}
}
}
];
/* reg: modulename key */
export enum NodeInputKeyEnum {
// old
@@ -151,7 +205,11 @@ export enum NodeInputKeyEnum {
loopEndInput = 'loopEndInput',
// form input
userInputForms = 'userInputForms'
userInputForms = 'userInputForms',
// comment
commentText = 'commentText',
commentSize = 'commentSize'
}
export enum NodeOutputKeyEnum {

View File

@@ -118,6 +118,7 @@ export enum FlowNodeTypeEnum {
queryExtension = 'cfr',
tools = 'tools',
stopTool = 'stopTool',
toolParams = 'toolParams',
lafModule = 'lafModule',
ifElseNode = 'ifElseNode',
variableUpdate = 'variableUpdate',
@@ -129,7 +130,8 @@ export enum FlowNodeTypeEnum {
loop = 'loop',
loopStart = 'loopStart',
loopEnd = 'loopEnd',
formInput = 'formInput'
formInput = 'formInput',
comment = 'comment'
}
// node IO value type

View File

@@ -25,7 +25,8 @@ export enum DispatchNodeResponseKeyEnum {
rewriteHistories = 'rewriteHistories', // If have the response, workflow histories will be rewrite
interactive = 'INTERACTIVE', // is interactive
runTimes = 'runTimes' // run times
runTimes = 'runTimes', // run times
newVariables = 'newVariables' // new variables
}
export const needReplaceReferenceInputTypeList = [

View File

@@ -79,6 +79,7 @@ export type RuntimeNodeItemType = {
outputs: FlowNodeOutputItemType[];
pluginId?: string; // workflow id / plugin id
version: string;
};
export type PluginRuntimeType = {
@@ -185,6 +186,9 @@ export type DispatchNodeResponseType = {
// form input
formInputResult?: string;
// tool params
toolParamsResult?: Record<string, any>;
};
export type DispatchNodeResultType<T = {}> = {
@@ -196,6 +200,7 @@ export type DispatchNodeResultType<T = {}> = {
[DispatchNodeResponseKeyEnum.assistantResponses]?: AIChatItemValueItemType[]; // Assistant response(Store to db)
[DispatchNodeResponseKeyEnum.rewriteHistories]?: ChatItemType[];
[DispatchNodeResponseKeyEnum.runTimes]?: number;
[DispatchNodeResponseKeyEnum.newVariables]?: Record<string, any>;
} & T;
/* Single node props */

View File

@@ -124,7 +124,8 @@ export const storeNodes2RuntimeNodes = (
isEntry: entryNodeIds.includes(node.nodeId),
inputs: node.inputs,
outputs: node.outputs,
pluginId: node.pluginId
pluginId: node.pluginId,
version: node.version
};
}) || []
);
@@ -233,7 +234,8 @@ export const getReferenceVariableValue = ({
nodes: RuntimeNodeItemType[];
variables: Record<string, any>;
}) => {
if (!isReferenceValue(value)) {
const nodeIds = nodes.map((node) => node.nodeId);
if (!isReferenceValue(value, nodeIds)) {
return value;
}
const sourceNodeId = value[0];

View File

@@ -33,17 +33,19 @@ import { LoopNode } from './system/loop/loop';
import { LoopStartNode } from './system/loop/loopStart';
import { LoopEndNode } from './system/loop/loopEnd';
import { FormInputNode } from './system/interactive/formInput';
import { ToolParamsNode } from './system/toolParams';
const systemNodes: FlowNodeTemplateType[] = [
AiChatModule,
TextEditorNode,
AssignedAnswerModule,
DatasetSearchModule,
DatasetConcatModule,
ToolModule,
StopToolNode,
ClassifyQuestionModule,
ContextExtractModule,
DatasetConcatModule,
ToolModule,
ToolParamsNode,
StopToolNode,
ReadFilesNode,
HttpNode468,
AiQueryExtension,

View File

@@ -1,7 +1,7 @@
import { NodeInputKeyEnum } from '../constants';
import { FlowNodeInputTypeEnum } from '../node/constant';
import { WorkflowIOValueTypeEnum } from '../constants';
import { chatNodeSystemPromptTip } from './tip';
import { chatNodeSystemPromptTip, systemPromptTip } from './tip';
import { FlowNodeInputItemType } from '../type/io';
import { i18nT } from '../../../../web/i18n/utils';
@@ -55,7 +55,7 @@ export const Input_Template_System_Prompt: FlowNodeInputItemType = {
max: 3000,
valueType: WorkflowIOValueTypeEnum.string,
label: i18nT('common:core.ai.Prompt'),
description: chatNodeSystemPromptTip,
description: systemPromptTip,
placeholder: chatNodeSystemPromptTip
};

View File

@@ -19,7 +19,7 @@ import {
Input_Template_UserChatInput,
Input_Template_Text_Quote
} from '../../input';
import { chatNodeSystemPromptTip } from '../../tip';
import { chatNodeSystemPromptTip, systemPromptTip } from '../../tip';
import { getHandleConfig } from '../../utils';
import { i18nT } from '../../../../../../web/i18n/utils';
@@ -94,7 +94,7 @@ export const AiChatModule: FlowNodeTemplateType = {
{
...Input_Template_System_Prompt,
label: i18nT('common:core.ai.Prompt'),
description: chatNodeSystemPromptTip,
description: systemPromptTip,
placeholder: chatNodeSystemPromptTip
},
Input_Template_History,

View File

@@ -0,0 +1,40 @@
import { FlowNodeTypeEnum } from '../../node/constant';
import { FlowNodeTemplateType } from '../../type/node.d';
import {
FlowNodeTemplateTypeEnum,
NodeInputKeyEnum,
WorkflowIOValueTypeEnum
} from '../../constants';
import { getHandleConfig } from '../utils';
export const CommentNode: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.comment,
templateType: FlowNodeTemplateTypeEnum.systemInput,
flowNodeType: FlowNodeTypeEnum.comment,
sourceHandle: getHandleConfig(false, false, false, false),
targetHandle: getHandleConfig(false, false, false, false),
avatar: '',
name: '',
intro: '',
version: '4811',
inputs: [
{
key: NodeInputKeyEnum.commentText,
renderTypeList: [],
valueType: WorkflowIOValueTypeEnum.string,
label: '',
value: ''
},
{
key: NodeInputKeyEnum.commentSize,
renderTypeList: [],
valueType: WorkflowIOValueTypeEnum.object,
label: '',
value: {
width: 240,
height: 140
}
}
],
outputs: []
};

View File

@@ -0,0 +1,20 @@
import { FlowNodeTypeEnum } from '../../node/constant';
import { FlowNodeTemplateType } from '../../type/node';
import { FlowNodeTemplateTypeEnum } from '../../constants';
import { getHandleConfig } from '../utils';
import { i18nT } from '../../../../../web/i18n/utils';
export const ToolParamsNode: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.toolParams,
templateType: FlowNodeTemplateTypeEnum.ai,
flowNodeType: FlowNodeTypeEnum.toolParams,
sourceHandle: getHandleConfig(true, true, true, true),
targetHandle: getHandleConfig(true, true, true, true),
avatar: 'core/workflow/template/toolParams',
name: i18nT('workflow:tool_custom_field'),
intro: i18nT('workflow:intro_tool_params_config'),
version: '4811',
isTool: true,
inputs: [],
outputs: []
};

View File

@@ -16,7 +16,7 @@ import {
Input_Template_System_Prompt,
Input_Template_UserChatInput
} from '../input';
import { chatNodeSystemPromptTip } from '../tip';
import { chatNodeSystemPromptTip, systemPromptTip } from '../tip';
import { LLMModelTypeEnum } from '../../../ai/constants';
import { getHandleConfig } from '../utils';
import { i18nT } from '../../../../../web/i18n/utils';
@@ -62,7 +62,7 @@ export const ToolModule: FlowNodeTemplateType = {
{
...Input_Template_System_Prompt,
label: i18nT('common:core.ai.Prompt'),
description: chatNodeSystemPromptTip,
description: systemPromptTip,
placeholder: chatNodeSystemPromptTip
},
Input_Template_History,

View File

@@ -1 +1,2 @@
export const chatNodeSystemPromptTip = 'core.app.tip.chatNodeSystemPromptTip';
export const systemPromptTip = 'core.app.tip.systemPromptTip';

View File

@@ -50,6 +50,7 @@ export type FlowNodeInputItemType = InputComponentPropsType & {
description?: string; // field desc
required?: boolean;
toolDescription?: string; // If this field is not empty, it is entered as a tool
enum?: string;
// render components params
canEdit?: boolean; // dynamic inputs

View File

@@ -25,6 +25,7 @@ import { ParentIdType } from 'common/parentFolder/type';
import { AppTypeEnum } from 'core/app/constants';
export type FlowNodeCommonType = {
parentNodeId?: string;
flowNodeType: FlowNodeTypeEnum; // render node card
abandon?: boolean; // abandon node

View File

@@ -297,8 +297,9 @@ export const formatEditorVariablePickerIcon = (
}));
};
export const isReferenceValue = (value: any): boolean => {
return Array.isArray(value) && value.length === 2 && typeof value[0] === 'string';
export const isReferenceValue = (value: any, nodeIds: string[]): boolean => {
const validIdList = [VARIABLE_NODE_ID, ...nodeIds];
return Array.isArray(value) && value.length === 2 && validIdList.includes(value[0]);
};
export const getElseIFLabel = (i: number) => {

View File

@@ -1,3 +1,4 @@
import { RequireAtLeastOne, RequireOnlyOne } from '../../common/type/utils';
import { Permission } from './controller';
import { PermissionValueType } from './type';
@@ -10,6 +11,19 @@ export type CollaboratorItemType = {
};
export type UpdateClbPermissionProps = {
tmbIds: string[];
members?: string[];
groups?: string[];
permission: PermissionValueType;
};
export type DeleteClbPermissionProps = RequireOnlyOne<{
tmbId: string;
groupId: string;
}>;
export type UpdatePermissionBody = {
permission: PermissionValueType;
} & RequireOnlyOne<{
memberId: string;
groupId: string;
}>;

View File

@@ -61,7 +61,7 @@ export const PermissionList: PermissionListType = {
[PermissionKeyEnum.write]: {
name: i18nT('common:permission.write'),
description: '',
value: 0b110, // 如果某个资源有特殊要求,再重写这个值
value: 0b110,
checkBoxType: 'single'
},
[PermissionKeyEnum.manage]: {

View File

@@ -1,9 +1,10 @@
import { PermissionValueType } from './type';
import { PermissionListType, PermissionValueType } from './type';
import { PermissionList, NullPermission, OwnerPermissionVal } from './constant';
export type PerConstructPros = {
per?: PermissionValueType;
isOwner?: boolean;
permissionList?: PermissionListType;
};
// the Permission helper class
@@ -13,9 +14,10 @@ export class Permission {
hasManagePer: boolean;
hasWritePer: boolean;
hasReadPer: boolean;
_permissionList: PermissionListType;
constructor(props?: PerConstructPros) {
const { per = NullPermission, isOwner = false } = props || {};
const { per = NullPermission, isOwner = false, permissionList = PermissionList } = props || {};
if (isOwner) {
this.value = OwnerPermissionVal;
} else {
@@ -23,9 +25,10 @@ export class Permission {
}
this.isOwner = isOwner;
this.hasManagePer = this.checkPer(PermissionList['manage'].value);
this.hasWritePer = this.checkPer(PermissionList['write'].value);
this.hasReadPer = this.checkPer(PermissionList['read'].value);
this._permissionList = permissionList;
this.hasManagePer = this.checkPer(this._permissionList['manage'].value);
this.hasWritePer = this.checkPer(this._permissionList['write'].value);
this.hasReadPer = this.checkPer(this._permissionList['read'].value);
}
// add permission(s)
@@ -36,36 +39,39 @@ export class Permission {
// perm.add(PermissionList['read'], PermissionList['write'])
// perm.add(PermissionList['read']).add(PermissionList['write'])
addPer(...perList: PermissionValueType[]) {
for (let oer of perList) {
this.value = this.value | oer;
if (this.isOwner) {
return this;
}
for (const per of perList) {
this.value = this.value | per;
}
this.updatePermissions();
return this.value;
return this;
}
removePer(...perList: PermissionValueType[]) {
for (let per of perList) {
if (this.isOwner) {
return this.value;
}
for (const per of perList) {
this.value = this.value & ~per;
}
this.updatePermissions();
return this.value;
return this;
}
checkPer(perm: PermissionValueType): boolean {
// if the permission is owner permission, only owner has this permission.
if (perm === OwnerPermissionVal) {
return this.value === OwnerPermissionVal;
} else if (this.hasManagePer) {
// The manager has all permissions except the owner permission
return true;
}
return (this.value & perm) === perm;
}
private updatePermissions() {
this.isOwner = this.value === OwnerPermissionVal;
this.hasManagePer = this.checkPer(PermissionList['manage'].value);
this.hasWritePer = this.checkPer(PermissionList['write'].value);
this.hasReadPer = this.checkPer(PermissionList['read'].value);
this.hasManagePer = this.checkPer(this._permissionList['manage'].value);
this.hasWritePer = this.checkPer(this._permissionList['write'].value);
this.hasReadPer = this.checkPer(this._permissionList['read'].value);
}
}

View File

@@ -0,0 +1,23 @@
import { PermissionKeyEnum, PermissionList } from '../constant';
import { PermissionListType } from '../type';
export enum GroupMemberRole {
owner = 'owner',
admin = 'admin',
member = 'member'
}
export const memberGroupPermissionList: PermissionListType = {
[PermissionKeyEnum.read]: {
...PermissionList[PermissionKeyEnum.read],
value: 0b100
},
[PermissionKeyEnum.write]: {
...PermissionList[PermissionKeyEnum.write],
value: 0b010
},
[PermissionKeyEnum.manage]: {
...PermissionList[PermissionKeyEnum.manage],
value: 0b001
}
};

View File

@@ -0,0 +1,27 @@
import { TeamMemberItemType } from 'support/user/team/type';
import { TeamPermission } from '../user/controller';
import { GroupMemberRole } from './constant';
type MemberGroupSchemaType = {
_id: string;
teamId: string;
name: string;
avatar: string;
updateTime: Date;
};
type GroupMemberSchemaType = {
groupId: string;
tmbId: string;
role: `${GroupMemberRole}`;
};
type MemberGroupType = MemberGroupSchemaType & {
members: {
tmbId: string;
role: `${GroupMemberRole}`;
}[]; // we can get tmb's info from other api. there is no need but only need to get tmb's id
permission: TeamPermission;
};
type MemberGroupListType = MemberGroupType[];

View File

@@ -1,3 +1,4 @@
import { RequireOnlyOne } from '../../common/type/utils';
import { TeamMemberWithUserSchema } from '../user/team/type';
import { AuthUserTypeEnum, PermissionKeyEnum, PerResourceTypeEnum } from './constant';
@@ -20,11 +21,13 @@ export type PermissionListType<T = {}> = Record<
export type ResourcePermissionType = {
teamId: string;
tmbId: string;
resourceType: ResourceType;
permission: PermissionValueType;
resourceId: string;
};
} & RequireOnlyOne<{
tmbId: string;
groupId: string;
}>;
export type ResourcePerWithTmbWithUser = Omit<ResourcePermissionType, 'tmbId'> & {
tmbId: TeamMemberWithUserSchema;

View File

@@ -1,19 +1,22 @@
import { PermissionKeyEnum, PermissionList, ReadPermissionVal } from '../constant';
import { PermissionKeyEnum } from '../constant';
import { PermissionListType } from '../type';
import { i18nT } from '../../../../web/i18n/utils';
import { PermissionList } from '../constant';
export const TeamPermissionList: PermissionListType = {
[PermissionKeyEnum.read]: {
...PermissionList[PermissionKeyEnum.read],
description: i18nT('user:permission_des.read')
value: 0b100
},
[PermissionKeyEnum.write]: {
...PermissionList[PermissionKeyEnum.write],
description: i18nT('user:permission_des.write')
value: 0b010
},
[PermissionKeyEnum.manage]: {
...PermissionList[PermissionKeyEnum.manage],
description: i18nT('user:permission_des.manage')
value: 0b001
}
};
export const TeamDefaultPermissionVal = ReadPermissionVal;
export const TeamReadPermissionVal = TeamPermissionList['read'].value;
export const TeamWritePermissionVal = TeamPermissionList['write'].value;
export const TeamManagePermissionVal = TeamPermissionList['manage'].value;
export const TeamDefaultPermissionVal = TeamReadPermissionVal;

View File

@@ -1,5 +1,5 @@
import { PerConstructPros, Permission } from '../controller';
import { TeamDefaultPermissionVal } from './constant';
import { TeamDefaultPermissionVal, TeamPermissionList } from './constant';
export class TeamPermission extends Permission {
constructor(props?: PerConstructPros) {
@@ -10,6 +10,7 @@ export class TeamPermission extends Permission {
} else if (!props?.per) {
props.per = TeamDefaultPermissionVal;
}
props.permissionList = TeamPermissionList;
super(props);
}
}

View File

@@ -33,7 +33,6 @@ export type UpdateTeamMemberProps = {
export type InviteMemberProps = {
teamId: string;
usernames: string[];
permission: PermissionValueType;
};
export type UpdateInviteProps = {
tmbId: string;

View File

@@ -0,0 +1,17 @@
import { GroupMemberRole } from '../../../../support/permission/memberGroup/constant';
export type postCreateGroupData = {
name: string;
avatar?: string;
memberIdList?: string[];
};
export type putUpdateGroupData = {
groupId: string;
name?: string;
avatar?: string;
memberList?: {
tmbId: string;
role: `${GroupMemberRole}`;
}[];
};

View File

@@ -0,0 +1 @@
export const DefaultGroupName = 'DEFAULT_GROUP';

View File

@@ -17,7 +17,6 @@ export type TeamSchema = {
lastWebsiteSyncTime: Date;
};
lafAccount: LafAccountType;
defaultPermission: PermissionValueType;
notificationAccount?: string;
};
@@ -25,6 +24,7 @@ export type tagsType = {
label: string;
key: string;
};
export type TeamTagSchema = TeamTagItemType & {
_id: string;
teamId: string;
@@ -45,9 +45,11 @@ export type TeamMemberSchema = {
export type TeamMemberWithUserSchema = Omit<TeamMemberSchema, 'userId'> & {
userId: UserModelSchema;
};
export type TeamMemberWithTeamSchema = Omit<TeamMemberSchema, 'teamId'> & {
teamId: TeamSchema;
};
export type TeamMemberWithTeamAndUserSchema = Omit<TeamMemberWithTeamSchema, 'userId'> & {
userId: UserModelSchema;
};

View File

@@ -1,11 +1,13 @@
{
"name": "@fastgpt/plugins",
"version": "1.0.0",
"type": "module",
"dependencies": {
"duck-duck-scrape": "^2.2.5",
"lodash": "^4.17.21",
"axios": "^1.5.1",
"expr-eval": "^2.0.2"
"expr-eval": "^2.0.2",
"echarts": "5.4.1"
},
"devDependencies": {
"@fastgpt/global": "workspace:*",

View File

@@ -22,7 +22,9 @@ const packagePluginList = [
'duckduckgo/search',
'duckduckgo/searchImg',
'duckduckgo/searchNews',
'duckduckgo/searchVideo'
'duckduckgo/searchVideo',
'drawing',
'drawing/baseChart'
];
export const list = [...staticPluginList, ...packagePluginList];

View File

@@ -0,0 +1,94 @@
import * as echarts from 'echarts';
type Props = {
title: string;
xAxis: string;
yAxis: string;
chartType: string;
};
type Response = Promise<{
result: string;
}>;
type SeriesData = {
name: string;
type: 'bar' | 'line' | 'pie'; // 只允许这三种类型
data: number[] | { value: number; name: string }[]; // 根据图表类型的数据结构
};
type Option = {
backgroundColor: string;
title: { text: string };
tooltip: {};
xAxis: { data: string[] };
yAxis: {};
series: SeriesData[]; // 使用定义的类型
};
const generateChart = async (title: string, xAxis: string, yAxis: string, chartType: string) => {
// @ts-ignore 无法使用dom如使用jsdom会出现生成图片无法正常展示有高手可以帮忙解决
const chart = echarts.init(undefined, undefined, {
renderer: 'svg', // 必须使用 SVG 模式
ssr: true, // 开启 SSR
width: 400, // 需要指明高和宽
height: 300
});
let parsedXAxis: string[] = [];
let parsedYAxis: number[] = [];
try {
parsedXAxis = JSON.parse(xAxis);
parsedYAxis = JSON.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 },
yAxis: {},
series: [] // 初始化为空数组
};
// 根据 chartType 生成不同的图表
switch (chartType) {
case '柱状图':
option.series.push({ name: 'Sample', type: 'bar', data: parsedYAxis });
break;
case '折线图':
option.series.push({ name: 'Sample', type: 'line', data: parsedYAxis });
break;
case '饼图':
option.series.push({
name: 'Sample',
type: 'pie',
data: parsedYAxis.map((value, index) => ({
value,
name: parsedXAxis[index] // 使用 xAxis 作为饼图的名称
}))
});
break;
default:
console.error('不支持的图表类型:', chartType);
return '';
}
chart.setOption(option);
// 生成 Base64 图像
const base64Image = chart.getDataURL({ type: 'png' });
// 释放图表实例
chart.dispose();
return base64Image;
};
const main = async ({ title, xAxis, yAxis, chartType }: Props): Response => {
return {
result: await generateChart(title, xAxis, yAxis, chartType)
};
};
export default main;

View File

@@ -0,0 +1,500 @@
{
"author": "",
"version": "486",
"name": "基础图表",
"avatar": "core/workflow/template/baseChart",
"intro": "根据数据生成图表可根据chartType生成柱状图折线图饼图",
"showStatus": true,
"weight": 10,
"isTool": true,
"templateType": "search",
"workflow": {
"nodes": [
{
"nodeId": "pluginInput",
"name": "common:core.module.template.self_input",
"intro": "workflow:intro_plugin_input",
"avatar": "core/workflow/template/workflowStart",
"flowNodeType": "pluginInput",
"showStatus": false,
"position": {
"x": 613.7921798611637,
"y": -124.66724109717275
},
"version": "481",
"inputs": [
{
"renderTypeList": ["reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "title",
"label": "title",
"description": "BI图表的标题",
"defaultValue": "",
"list": [
{
"label": "",
"value": ""
}
],
"required": true,
"toolDescription": "BI图表的标题"
},
{
"renderTypeList": ["reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "xAxis",
"label": "xAxis",
"description": "x轴数据",
"defaultValue": "",
"required": true,
"toolDescription": "x轴数据例如['A', 'B', 'C']",
"list": [
{
"label": "",
"value": ""
}
]
},
{
"renderTypeList": ["reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "yAxis",
"label": "yAxis",
"description": "y轴数据",
"defaultValue": "",
"list": [
{
"label": "",
"value": ""
}
],
"required": true,
"toolDescription": "y轴数据例如['A', 'B', 'C']"
},
{
"renderTypeList": ["select"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "chartType",
"label": "chartType",
"description": "图表类型:柱状图,折线图,饼图",
"defaultValue": "",
"required": true,
"list": [
{
"label": "柱状图",
"value": "柱状图"
},
{
"label": "折线图",
"value": "折线图"
},
{
"label": "饼图",
"value": "饼图"
}
],
"toolDescription": "图表类型,目前支持三种: 柱状图,折线图,饼图"
}
],
"outputs": [
{
"id": "title",
"valueType": "string",
"key": "title",
"label": "title",
"type": "hidden"
},
{
"id": "xAxis",
"valueType": "string",
"key": "xAxis",
"label": "xAxis",
"type": "hidden"
},
{
"id": "yAxis",
"valueType": "string",
"key": "yAxis",
"label": "yAxis",
"type": "hidden"
},
{
"id": "chartType",
"valueType": "string",
"key": "chartType",
"label": "chartType",
"type": "hidden"
}
]
},
{
"nodeId": "pluginOutput",
"name": "common:core.module.template.self_output",
"intro": "workflow:intro_custom_plugin_output",
"avatar": "core/workflow/template/pluginOutput",
"flowNodeType": "pluginOutput",
"showStatus": false,
"position": {
"x": 2122.252754006148,
"y": -63.5218674613718
},
"version": "481",
"inputs": [
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "相对路径URL",
"label": "相对路径URL",
"description": "可用使用markdown格式展示图片![图片](url)",
"value": ["ws0DFKJnCPhk", "bzaYjKyQFOw2"]
}
],
"outputs": []
},
{
"nodeId": "ws0DFKJnCPhk",
"name": "HTTP 请求",
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
"avatar": "core/workflow/template/httpRequest",
"flowNodeType": "httpRequest468",
"showStatus": true,
"position": {
"x": 1216.5166647574395,
"y": -206.30162946606856
},
"version": "481",
"inputs": [
{
"key": "system_addInputParam",
"renderTypeList": ["addInputParam"],
"valueType": "dynamic",
"label": "",
"required": false,
"description": "接收前方节点的输出值作为变量,这些变量可以被 HTTP 请求参数使用。",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"valueDesc": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpMethod",
"renderTypeList": ["custom"],
"valueType": "string",
"label": "",
"value": "POST",
"required": true,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpTimeout",
"renderTypeList": ["custom"],
"valueType": "number",
"label": "",
"value": 30,
"min": 5,
"max": 600,
"required": true,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpReqUrl",
"renderTypeList": ["hidden"],
"valueType": "string",
"label": "",
"description": "新的 HTTP 请求地址。如果出现两个“请求地址”,可以删除该模块重新加入,会拉取最新的模块配置。",
"placeholder": "https://api.ai.com/getInventory",
"required": false,
"valueDesc": "",
"debugLabel": "",
"toolDescription": "",
"value": "drawing/baseChart"
},
{
"key": "system_httpHeader",
"renderTypeList": ["custom"],
"valueType": "any",
"value": [],
"label": "",
"description": "自定义请求头,请严格填入 JSON 字符串。\n1. 确保最后一个属性没有逗号\n2. 确保 key 包含双引号\n例如{\"Authorization\":\"Bearer xxx\"}",
"placeholder": "common:core.module.input.description.Http Request Header",
"required": false,
"valueDesc": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpParams",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"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}",
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpFormBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpContentType",
"renderTypeList": ["hidden"],
"valueType": "string",
"value": "json",
"label": "",
"required": false,
"valueDesc": "",
"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",
"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",
"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",
"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",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "chartType"]
}
],
"outputs": [
{
"id": "system_addOutputParam",
"key": "system_addOutputParam",
"type": "dynamic",
"valueType": "dynamic",
"label": "",
"customFieldConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": false
},
"valueDesc": "",
"description": ""
},
{
"id": "error",
"key": "error",
"label": "请求错误",
"description": "HTTP请求错误信息成功时返回空",
"valueType": "object",
"type": "static",
"valueDesc": ""
},
{
"id": "httpRawResponse",
"key": "httpRawResponse",
"required": true,
"label": "原始响应",
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
"valueType": "any",
"type": "static",
"valueDesc": ""
},
{
"id": "bzaYjKyQFOw2",
"valueType": "string",
"type": "dynamic",
"key": "result",
"label": "result"
}
]
}
],
"edges": [
{
"source": "pluginInput",
"target": "ws0DFKJnCPhk",
"sourceHandle": "pluginInput-source-right",
"targetHandle": "ws0DFKJnCPhk-target-left"
},
{
"source": "ws0DFKJnCPhk",
"target": "pluginOutput",
"sourceHandle": "ws0DFKJnCPhk-source-right",
"targetHandle": "pluginOutput-target-left"
}
]
}
}

View File

@@ -0,0 +1,17 @@
{
"author": "",
"version": "486",
"name": "BI图表功能",
"avatar": "core/workflow/template/BI",
"intro": "BI图表功能可以生成一些常用的图表如饼图柱状图折线图等",
"showStatus": false,
"weight": 100,
"isTool": true,
"templateType": "tools",
"workflow": {
"nodes": [],
"edges": []
}
}

View File

@@ -31,8 +31,8 @@ const ImageSchema = new Schema({
});
try {
// tts expired
ImageSchema.index({ expiredTime: 1 }, { expireAfterSeconds: 60 });
// tts expired60 Minutes
ImageSchema.index({ expiredTime: 1 }, { expireAfterSeconds: 60 * 60 });
ImageSchema.index({ type: 1 });
ImageSchema.index({ createTime: 1 });
// delete related img

View File

@@ -30,9 +30,13 @@ export const getUploadModel = ({ maxSize = 500 }: { maxSize?: number }) => {
// destination: (_req, _file, cb) => {
// cb(null, tmpFileDirPath);
// },
filename: async (req, file, cb) => {
const { ext } = path.parse(decodeURIComponent(file.originalname));
cb(null, `${getNanoid()}${ext}`);
filename: (req, file, cb) => {
if (!file?.originalname) {
cb(new Error('File not found'), '');
} else {
const { ext } = path.parse(decodeURIComponent(file.originalname));
cb(null, `${getNanoid()}${ext}`);
}
}
})
}).single('file');

View File

@@ -63,7 +63,7 @@ export const getMongoModel = <T>(name: string, schema: mongoose.Schema) => {
const model = connectionMongo.model<T>(name, schema);
if (process.env.SYNC_INDEX !== '0') {
if (process.env.SYNC_INDEX !== '0' && process.env.NODE_ENV !== 'test') {
try {
model.syncIndexes({ background: true });
} catch (error) {

View File

@@ -51,6 +51,7 @@ export function reRankRecall({
}));
})
.catch((err) => {
console.log(err);
addLog.error('rerank error', err);
return [];

View File

@@ -2,7 +2,6 @@ import { AppSchema } from '@fastgpt/global/core/app/type';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { getLLMModel } from '../ai/model';
import { MongoAppVersion } from './version/schema';
import { MongoApp } from './schema';
export const beforeUpdateAppFormat = <T extends AppSchema['modules'] | undefined>({
@@ -46,30 +45,6 @@ export const beforeUpdateAppFormat = <T extends AppSchema['modules'] | undefined
};
};
export const getAppLatestVersion = async (appId: string, app?: AppSchema) => {
const version = await MongoAppVersion.findOne({
appId,
isPublish: true
})
.sort({
time: -1
})
.lean();
if (version) {
return {
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig || app?.chatConfig || {}
};
}
return {
nodes: app?.modules || [],
edges: app?.edges || [],
chatConfig: app?.chatConfig || {}
};
};
/* Get apps */
export async function findAppAndAllChildren({
teamId,

View File

@@ -10,6 +10,7 @@ import { cloneDeep } from 'lodash';
import { MongoApp } from '../schema';
import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type';
import { getSystemPluginTemplates } from '../../../../plugins/register';
import { getAppLatestVersion, getAppVersionById } from '../version/controller';
/*
plugin id rule:
@@ -34,38 +35,14 @@ export async function splitCombinePluginId(id: string) {
return { source, pluginId: id };
}
const getChildAppTemplateById = async (
id: string
): Promise<SystemPluginTemplateItemType & { teamId?: string }> => {
const { source, pluginId } = await splitCombinePluginId(id);
type ChildAppType = SystemPluginTemplateItemType & { teamId?: string };
const getSystemPluginTemplateById = async (
pluginId: string
): Promise<SystemPluginTemplateItemType> => {
const item = getSystemPluginTemplates().find((plugin) => plugin.id === pluginId);
if (!item) return Promise.reject('plugin not found');
if (source === PluginSourceEnum.personal) {
const item = await MongoApp.findById(id).lean();
if (!item) return Promise.reject('plugin not found');
return {
id: String(item._id),
teamId: String(item.teamId),
name: item.name,
avatar: item.avatar,
intro: item.intro,
showStatus: true,
workflow: {
nodes: item.modules,
edges: item.edges,
chatConfig: item.chatConfig
},
templateType: FlowNodeTemplateTypeEnum.teamApp,
version: item?.pluginData?.nodeVersion || defaultNodeVersion,
originCost: 0,
currentCost: 0
};
} else {
const item = getSystemPluginTemplates().find((plugin) => plugin.id === pluginId);
if (!item) return Promise.reject('plugin not found');
return cloneDeep(item);
}
return cloneDeep(item);
};
/* format plugin modules to plugin preview module */
@@ -74,7 +51,39 @@ export async function getChildAppPreviewNode({
}: {
id: string;
}): Promise<FlowNodeTemplateType> {
const app = await getChildAppTemplateById(id);
const app: ChildAppType = await (async () => {
const { source, pluginId } = await splitCombinePluginId(id);
if (source === PluginSourceEnum.personal) {
const item = await MongoApp.findById(id).lean();
if (!item) return Promise.reject('plugin not found');
const version = await getAppLatestVersion(id, item);
if (!version.versionId) return Promise.reject('App version not found');
return {
id: String(item._id),
teamId: String(item.teamId),
name: item.name,
avatar: item.avatar,
intro: item.intro,
showStatus: true,
workflow: {
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig
},
templateType: FlowNodeTemplateTypeEnum.teamApp,
version: version.versionId,
originCost: 0,
currentCost: 0
};
} else {
return getSystemPluginTemplateById(pluginId);
}
})();
const isPlugin = !!app.workflow.nodes.find(
(node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput
);
@@ -99,9 +108,51 @@ export async function getChildAppPreviewNode({
};
}
/* run plugin time */
export async function getChildAppRuntimeById(id: string): Promise<PluginRuntimeType> {
const app = await getChildAppTemplateById(id);
/*
Get runtime plugin data
System plugin: plugin id
Personal plugin: Version id
*/
export async function getChildAppRuntimeById(
id: string,
versionId?: string
): Promise<PluginRuntimeType> {
const app: ChildAppType = await (async () => {
const { source, pluginId } = await splitCombinePluginId(id);
if (source === PluginSourceEnum.personal) {
const item = await MongoApp.findById(id).lean();
if (!item) return Promise.reject('plugin not found');
const version = await getAppVersionById({
appId: id,
versionId,
app: item
});
return {
id: String(item._id),
teamId: String(item.teamId),
name: item.name,
avatar: item.avatar,
intro: item.intro,
showStatus: true,
workflow: {
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig
},
templateType: FlowNodeTemplateTypeEnum.teamApp,
// 用不到
version: item?.pluginData?.nodeVersion || defaultNodeVersion,
originCost: 0,
currentCost: 0
};
} else {
return getSystemPluginTemplateById(pluginId);
}
})();
return {
id: app.id,

View File

@@ -0,0 +1,59 @@
import { AppSchema } from '@fastgpt/global/core/app/type';
import { MongoAppVersion } from './schema';
import { Types } from '../../../common/mongo';
export const getAppLatestVersion = async (appId: string, app?: AppSchema) => {
const version = await MongoAppVersion.findOne({
appId,
isPublish: true
})
.sort({
time: -1
})
.lean();
if (version) {
return {
versionId: version._id,
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig || app?.chatConfig || {}
};
}
return {
versionId: app?.pluginData?.nodeVersion,
nodes: app?.modules || [],
edges: app?.edges || [],
chatConfig: app?.chatConfig || {}
};
};
export const getAppVersionById = async ({
appId,
versionId,
app
}: {
appId: string;
versionId?: string;
app?: AppSchema;
}) => {
// 检查 versionId 是否符合 ObjectId 格式
if (versionId && Types.ObjectId.isValid(versionId)) {
const version = await MongoAppVersion.findOne({
_id: versionId,
appId
}).lean();
if (version) {
return {
versionId: version._id,
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig || app?.chatConfig || {}
};
}
}
// If the version does not exist, the latest version is returned
return getAppLatestVersion(appId, app);
};

View File

@@ -127,7 +127,7 @@ export const loadRequestMessages = async ({
})();
// If imgUrl is a local path, load image from local, and set url to base64
if (imgUrl.startsWith('/') || process.env.VISION_FOCUS_BASE64 === 'true') {
if (imgUrl.startsWith('/')) {
addLog.debug('Load image from local server', {
baseUrl: serverRequestBaseUrl,
requestUrl: imgUrl
@@ -234,7 +234,13 @@ export const loadRequestMessages = async ({
}
}
if (item.role === ChatCompletionRequestMessageRoleEnum.Assistant) {
if (item.content !== undefined && !item.content) return;
if (
item.content !== undefined &&
!item.content &&
!item.tool_calls &&
!item.function_call
)
return;
if (Array.isArray(item.content) && item.content.length === 0) return;
}

View File

@@ -25,6 +25,7 @@ import { AIChatItemType } from '@fastgpt/global/core/chat/type';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { updateToolInputValue } from './utils';
import { computedMaxToken, llmCompletionsBodyFormat } from '../../../../ai/utils';
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
type FunctionRunResponseType = {
toolRunResponse: DispatchFlowResponse;
@@ -60,12 +61,18 @@ export const runToolWithFunctionCall = async (
type: string;
description: string;
required?: boolean;
enum?: string[];
}
> = {};
item.toolParams.forEach((item) => {
const jsonSchema = (
toolValueTypeList.find((type) => type.value === item.valueType) || toolValueTypeList[0]
).jsonSchema;
properties[item.key] = {
type: item.valueType || 'string',
description: item.toolDescription || ''
...jsonSchema,
description: item.toolDescription || '',
enum: item.enum?.split('\n').filter(Boolean) || []
};
});
@@ -244,33 +251,36 @@ export const runToolWithFunctionCall = async (
role: ChatCompletionRequestMessageRoleEnum.Assistant,
function_call: functionCall
};
/*
...
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, functions);
/*
...
user
assistant: tool data
tool: tool response
*/
const completeMessages = [
...concatToolMessages,
...toolsRunResponse.map((item) => item?.functionCallMsg)
];
// console.log(tokens, 'tool');
// tool assistant
const toolAssistants = toolsRunResponse
.map((item) => {
const assistantResponses = item.toolRunResponse.assistantResponses || [];
return assistantResponses;
})
.flat();
// tool node assistant
const adaptChatMessages = GPTMessages2Chats(completeMessages);
const toolNodeAssistant = adaptChatMessages.pop() as AIChatItemType;
const toolNodeAssistant = GPTMessages2Chats([
assistantToolMsgParams,
...toolsRunResponse.map((item) => item?.functionCallMsg)
])[0] as AIChatItemType;
const toolNodeAssistants = [
...assistantResponses,
...toolAssistants,
...toolNodeAssistant.value
];
const toolNodeAssistants = [...assistantResponses, ...toolNodeAssistant.value];
// concat tool responses
const dispatchFlowResponse = response
@@ -285,7 +295,7 @@ export const runToolWithFunctionCall = async (
return {
dispatchFlowResponse,
totalTokens: response?.totalTokens ? response.totalTokens + tokens : tokens,
completeMessages: filterMessages,
completeMessages,
assistantResponses: toolNodeAssistants,
runTimes:
(response?.runTimes || 0) +

View File

@@ -183,7 +183,18 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
});
// flat child tool response
const childToolResponse = dispatchFlowResponse.map((item) => item.flowResponses).flat();
let newVariables: Record<string, any> = props.variables;
const childToolResponse = dispatchFlowResponse
.map((item) => {
// Computed new variables
newVariables = {
...newVariables,
...item.newVariables
};
return item.flowResponses;
})
.flat();
// concat tool usage
const totalPointsUsage =
@@ -219,6 +230,7 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
tokens: totalTokens
},
...flatUsages
]
],
[DispatchNodeResponseKeyEnum.newVariables]: newVariables
};
};

View File

@@ -27,6 +27,7 @@ import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { updateToolInputValue } from './utils';
import { computedMaxToken, llmCompletionsBodyFormat } from '../../../../ai/utils';
import { WorkflowResponseType } from '../../type';
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
type FunctionCallCompletion = {
id: string;
@@ -68,12 +69,18 @@ export const runToolWithPromptCall = async (
type: string;
description: string;
required?: boolean;
enum?: string[];
}
> = {};
item.toolParams.forEach((item) => {
const jsonSchema = (
toolValueTypeList.find((type) => type.value === item.valueType) || toolValueTypeList[0]
).jsonSchema;
properties[item.key] = {
type: 'string',
description: item.toolDescription || ''
...jsonSchema,
description: item.toolDescription || '',
enum: item.enum?.split('\n').filter(Boolean) || []
};
});
@@ -280,27 +287,37 @@ export const runToolWithPromptCall = async (
role: ChatCompletionRequestMessageRoleEnum.Assistant,
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 completeMessages: ChatCompletionMessageParam[] = [
...concatToolMessages,
{
role: ChatCompletionRequestMessageRoleEnum.Function,
name: toolJson.name,
content: toolsRunResponse.toolResponsePrompt
}
];
// tool assistant
const toolAssistants = toolsRunResponse.moduleRunResponse.assistantResponses || [];
/*
...
user
assistant: tool data
function: tool response
*/
const functionResponseMessage: ChatCompletionMessageParam = {
role: ChatCompletionRequestMessageRoleEnum.Function,
name: toolJson.name,
content: toolsRunResponse.toolResponsePrompt
};
// tool node assistant
const adaptChatMessages = GPTMessages2Chats(completeMessages);
const toolNodeAssistant = adaptChatMessages.pop() as AIChatItemType;
const toolNodeAssistants = [...assistantResponses, ...toolAssistants, ...toolNodeAssistant.value];
const toolNodeAssistant = GPTMessages2Chats([
assistantToolMsgParams,
functionResponseMessage
])[0] as AIChatItemType;
const toolNodeAssistants = [...assistantResponses, ...toolNodeAssistant.value];
const dispatchFlowResponse = response
? response.dispatchFlowResponse.concat(toolsRunResponse.moduleRunResponse)

View File

@@ -6,7 +6,6 @@ import {
ChatCompletionMessageToolCall,
StreamChatType,
ChatCompletionToolMessageParam,
ChatCompletionAssistantToolParam,
ChatCompletionMessageParam,
ChatCompletionTool,
ChatCompletionAssistantMessageParam
@@ -27,6 +26,7 @@ import { updateToolInputValue } from './utils';
import { computedMaxToken, llmCompletionsBodyFormat } from '../../../../ai/utils';
import { getNanoid, sliceStrStartEnd } from '@fastgpt/global/common/string/tools';
import { addLog } from '../../../../../common/system/log';
import { toolValueTypeList } from '@fastgpt/global/core/workflow/constants';
type ToolRunResponseType = {
toolRunResponse: DispatchFlowResponse;
@@ -54,7 +54,6 @@ export const runToolWithToolChoice = async (
res,
requestOrigin,
runtimeNodes,
node,
stream,
workflowStreamResponse,
params: { temperature = 0, maxToken = 4000, aiChatVision }
@@ -72,13 +71,21 @@ export const runToolWithToolChoice = async (
{
type: string;
description: string;
enum?: string[];
required?: boolean;
items?: {
type: string;
};
}
> = {};
item.toolParams.forEach((item) => {
const jsonSchema = (
toolValueTypeList.find((type) => type.value === item.valueType) || toolValueTypeList[0]
)?.jsonSchema;
properties[item.key] = {
type: item.valueType || 'string',
description: item.toolDescription || ''
...jsonSchema,
description: item.toolDescription || '',
enum: item.enum?.split('\n').filter(Boolean) || []
};
});
@@ -86,7 +93,7 @@ export const runToolWithToolChoice = async (
type: 'function',
function: {
name: item.nodeId,
description: item.intro,
description: item.intro || item.name,
parameters: {
type: 'object',
properties,
@@ -140,7 +147,6 @@ export const runToolWithToolChoice = async (
toolModel
);
// console.log(JSON.stringify(requestBody, null, 2));
/* Run llm */
const ai = getAIApi({
timeout: 480000
@@ -282,12 +288,24 @@ export const runToolWithToolChoice = async (
).filter(Boolean) as ToolRunResponseType;
const flatToolsResponseData = toolsRunResponse.map((item) => item.toolRunResponse).flat();
if (toolCalls.length > 0 && !res?.closed) {
// Run the tool, combine its results, and perform another round of AI calls
const assistantToolMsgParams: ChatCompletionAssistantToolParam = {
role: ChatCompletionRequestMessageRoleEnum.Assistant,
tool_calls: toolCalls
};
const assistantToolMsgParams: ChatCompletionAssistantMessageParam[] = [
...(answer
? [
{
role: ChatCompletionRequestMessageRoleEnum.Assistant as 'assistant',
content: answer
}
]
: []),
{
role: ChatCompletionRequestMessageRoleEnum.Assistant,
tool_calls: toolCalls
}
];
/*
...
user
@@ -295,8 +313,10 @@ export const runToolWithToolChoice = async (
*/
const concatToolMessages = [
...requestMessages,
assistantToolMsgParams
...assistantToolMsgParams
] as ChatCompletionMessageParam[];
// Only toolCall tokens are counted here, Tool response tokens count towards the next reply
const tokens = await countGptMessagesTokens(concatToolMessages, tools);
/*
...
@@ -309,25 +329,12 @@ export const runToolWithToolChoice = async (
...toolsRunResponse.map((item) => item?.toolMsgParams)
];
// console.log(tokens, 'tool');
// tool assistant
const toolAssistants = toolsRunResponse
.map((item) => {
const assistantResponses = item.toolRunResponse.assistantResponses || [];
return assistantResponses;
})
.flat();
// tool node assistant
const adaptChatMessages = GPTMessages2Chats(completeMessages);
const toolNodeAssistant = adaptChatMessages.pop() as AIChatItemType;
const toolNodeAssistants = [
...assistantResponses,
...toolAssistants,
...toolNodeAssistant.value
];
// Assistant tool response adapt to chatStore
const toolNodeAssistant = GPTMessages2Chats([
...assistantToolMsgParams,
...toolsRunResponse.map((item) => item?.toolMsgParams)
])[0] as AIChatItemType;
const toolNodeAssistants = [...assistantResponses, ...toolNodeAssistant.value];
// concat tool responses
const dispatchFlowResponse = response
@@ -373,7 +380,6 @@ export const runToolWithToolChoice = async (
};
const completeMessages = filterMessages.concat(gptAssistantResponse);
const tokens = await countGptMessagesTokens(completeMessages, tools);
// console.log(tokens, 'response token');
// concat tool assistant
const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType;

View File

@@ -0,0 +1,17 @@
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
export type Props = ModuleDispatchProps<{}>;
export type Response = DispatchNodeResultType<{}>;
export const dispatchToolParams = (props: Props): Response => {
const { params } = props;
return {
...params,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
toolParamsResult: params
}
};
};

View File

@@ -70,6 +70,7 @@ import { dispatchLoop } from './loop/runLoop';
import { dispatchLoopEnd } from './loop/runLoopEnd';
import { dispatchLoopStart } from './loop/runLoopStart';
import { dispatchFormInput } from './interactive/formInput';
import { dispatchToolParams } from './agent/runTool/toolParams';
const callbackMap: Record<FlowNodeTypeEnum, Function> = {
[FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart,
@@ -87,6 +88,7 @@ const callbackMap: Record<FlowNodeTypeEnum, Function> = {
[FlowNodeTypeEnum.queryExtension]: dispatchQueryExtension,
[FlowNodeTypeEnum.tools]: dispatchRunTools,
[FlowNodeTypeEnum.stopTool]: dispatchStopToolCall,
[FlowNodeTypeEnum.toolParams]: dispatchToolParams,
[FlowNodeTypeEnum.lafModule]: dispatchLafRequest,
[FlowNodeTypeEnum.ifElseNode]: dispatchIfElse,
[FlowNodeTypeEnum.variableUpdate]: dispatchUpdateVariable,
@@ -105,6 +107,7 @@ const callbackMap: Record<FlowNodeTypeEnum, Function> = {
[FlowNodeTypeEnum.pluginConfig]: () => Promise.resolve(),
[FlowNodeTypeEnum.emptyNode]: () => Promise.resolve(),
[FlowNodeTypeEnum.globalVariable]: () => Promise.resolve(),
[FlowNodeTypeEnum.comment]: () => Promise.resolve(),
[FlowNodeTypeEnum.runApp]: dispatchAppRequest // abandoned
};
@@ -555,6 +558,14 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
dispatchRes[item.key] = valueTypeFormat(item.defaultValue, item.valueType);
});
// Update new variables
if (dispatchRes[DispatchNodeResponseKeyEnum.newVariables]) {
variables = {
...variables,
...dispatchRes[DispatchNodeResponseKeyEnum.newVariables]
};
}
return {
node,
runStatus: 'run',

View File

@@ -38,6 +38,7 @@ export const dispatchLoop = async (props: Props): Promise<Response> => {
const loopDetail: ChatHistoryItemResType[] = [];
let assistantResponses: AIChatItemValueItemType[] = [];
let totalPoints = 0;
let newVariables: Record<string, any> = props.variables;
for await (const item of loopInputArray) {
const response = await dispatchWorkFlow({
@@ -72,6 +73,10 @@ export const dispatchLoop = async (props: Props): Promise<Response> => {
assistantResponses.push(...response.assistantResponses);
totalPoints = response.flowUsages.reduce((acc, usage) => acc + usage.totalPoints, 0);
newVariables = {
...newVariables,
...response.newVariables
};
}
return {
@@ -88,6 +93,7 @@ export const dispatchLoop = async (props: Props): Promise<Response> => {
moduleName: name
}
],
[NodeOutputKeyEnum.loopArray]: outputValueArr
[NodeOutputKeyEnum.loopArray]: outputValueArr,
[DispatchNodeResponseKeyEnum.newVariables]: newVariables
};
};

View File

@@ -26,7 +26,7 @@ type RunPluginResponse = DispatchNodeResultType<{}>;
export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPluginResponse> => {
const {
node: { pluginId },
node: { pluginId, version },
runningAppInfo,
query,
params: { system_forbid_stream = false, ...data } // Plugin input
@@ -45,7 +45,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
per: ReadPermissionVal
});
const plugin = await getChildAppRuntimeById(pluginId);
const plugin = await getChildAppRuntimeById(pluginId, version);
const outputFilterMap =
plugin.nodes
@@ -91,8 +91,9 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
: {}),
runningAppInfo: {
id: String(plugin.id),
teamId: plugin.teamId || '',
tmbId: pluginData?.tmbId || ''
// 如果是系统插件,则使用当前团队的 teamId 和 tmbId
teamId: plugin.teamId || runningAppInfo.teamId,
tmbId: pluginData?.tmbId || runningAppInfo.tmbId
},
variables: runtimeVariables,
query: getPluginRunUserQuery({

View File

@@ -16,7 +16,7 @@ import { chatValue2RuntimePrompt, runtimePrompt2ChatsValue } from '@fastgpt/glob
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { authAppByTmbId } from '../../../../support/permission/app/auth';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { getAppLatestVersion } from '../../../app/controller';
import { getAppVersionById } from '../../../app/version/controller';
type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.userChatInput]: string;
@@ -34,8 +34,7 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
runningAppInfo,
histories,
query,
mode,
node: { pluginId },
node: { pluginId: appId, version },
workflowStreamResponse,
params,
variables
@@ -45,19 +44,23 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
if (!userChatInput) {
return Promise.reject('Input is empty');
}
if (!pluginId) {
if (!appId) {
return Promise.reject('pluginId is empty');
}
// Auth the app by tmbId(Not the user, but the workflow user)
const { app: appData } = await authAppByTmbId({
appId: pluginId,
appId: appId,
tmbId: runningAppInfo.tmbId,
per: ReadPermissionVal
});
const { nodes, edges, chatConfig } = await getAppLatestVersion(pluginId);
const childStreamResponse = system_forbid_stream ? false : props.stream;
const { nodes, edges, chatConfig } = await getAppVersionById({
appId,
versionId: version,
app: appData
});
const childStreamResponse = system_forbid_stream ? false : props.stream;
// Auto line
if (childStreamResponse) {
workflowStreamResponse?.({

View File

@@ -86,7 +86,7 @@ export const authAppByTmbId = async ({
resourceId: appId,
resourceType: PerResourceTypeEnum.app
});
const Per = new AppPermission({ per: rp?.permission ?? app.defaultPermission, isOwner });
const Per = new AppPermission({ per: rp ?? app.defaultPermission, isOwner });
return {
Per,
defaultPermission: app.defaultPermission

View File

@@ -8,51 +8,113 @@ import { authOpenApiKey } from '../openapi/auth';
import { FileTokenQuery } from '@fastgpt/global/common/file/type';
import { MongoResourcePermission } from './schema';
import { ClientSession } from 'mongoose';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { ResourcePermissionType } from '@fastgpt/global/support/permission/type';
import {
PermissionValueType,
ResourcePermissionType
} from '@fastgpt/global/support/permission/type';
import { bucketNameMap } from '@fastgpt/global/common/file/constants';
import { addMinutes } from 'date-fns';
import { getGroupsByTmbId } from './memberGroup/controllers';
import { Permission } from '@fastgpt/global/support/permission/controller';
/** get resource permission for a team member
* If there is no permission for the team member, it will return undefined
* @param resourceType: PerResourceTypeEnum
* @param teamId
* @param tmbId
* @param resourceId
* @returns PermissionValueType | undefined
*/
export const getResourcePermission = async ({
resourceType,
teamId,
tmbId,
resourceId
}: {
resourceType: PerResourceTypeEnum;
teamId: string;
tmbId: string;
resourceId?: string;
}) => {
const per = await MongoResourcePermission.findOne({
tmbId,
teamId,
resourceType,
resourceId
});
} & (
| {
resourceType: 'team';
resourceId?: undefined;
}
| {
resourceType: Omit<PerResourceTypeEnum, 'team'>;
resourceId: string;
}
)): Promise<PermissionValueType | undefined> => {
// Personal permission has the highest priority
const tmbPer = (
await MongoResourcePermission.findOne(
{
tmbId,
teamId,
resourceType,
resourceId
},
'permission'
).lean()
)?.permission;
if (!per) {
return null;
// could be 0
if (tmbPer !== undefined) {
return tmbPer;
}
return per;
// If there is no personal permission, get the group permission
const groupIdList = (await getGroupsByTmbId({ tmbId, teamId })).map((item) => item._id);
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;
};
/* 仅取 members 不取 groups */
export async function getResourceAllClbs({
resourceId,
teamId,
resourceType,
session
}: {
resourceId: ParentIdType;
teamId: string;
resourceType: PerResourceTypeEnum;
session?: ClientSession;
}): Promise<ResourcePermissionType[]> {
if (!resourceId) return [];
} & (
| {
resourceType: 'team';
resourceId?: undefined;
}
| {
resourceType: Omit<PerResourceTypeEnum, 'team'>;
resourceId?: string | null;
}
)): Promise<ResourcePermissionType[]> {
return MongoResourcePermission.find(
{
resourceId,
resourceType: resourceType,
teamId: teamId
teamId: teamId,
groupId: {
$exists: false
}
},
null,
{
@@ -60,6 +122,7 @@ export async function getResourceAllClbs({
}
).lean();
}
export const delResourcePermissionById = (id: string) => {
return MongoResourcePermission.findByIdAndRemove(id);
};
@@ -301,3 +364,11 @@ export const authFileToken = (token?: string) =>
});
});
});
export const getGroupPer = (groups: PermissionValueType[] = []) => {
if (groups.length === 0) {
return undefined;
}
return new Permission().addPer(...groups).value;
};

View File

@@ -78,7 +78,7 @@ export const authDatasetByTmbId = async ({
resourceType: PerResourceTypeEnum.dataset
});
const Per = new DatasetPermission({
per: rp?.permission ?? dataset.defaultPermission,
per: rp ?? dataset.defaultPermission,
isOwner
});
return {

View File

@@ -3,8 +3,9 @@ import { MongoResourcePermission } from './schema';
import { ClientSession, Model } from 'mongoose';
import { NullPermission, PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { PermissionValueType } from '@fastgpt/global/support/permission/type';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
import { getResourceAllClbs } from './controller';
import { RequireOnlyOne } from '@fastgpt/global/common/type/utils';
import { ParentIdType } from '@fastgpt/global/common/parentFolder/type';
export type SyncChildrenPermissionResourceType = {
_id: string;
@@ -14,8 +15,10 @@ export type SyncChildrenPermissionResourceType = {
};
export type UpdateCollaboratorItem = {
permission: PermissionValueType;
} & RequireOnlyOne<{
tmbId: string;
};
groupId: string;
}>;
// sync the permission to all children folders.
export async function syncChildrenPermission({

View File

@@ -0,0 +1,183 @@
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 { MongoMemberGroupModel } from './memberGroupSchema';
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
import { ClientSession } from 'mongoose';
import { GroupMemberRole } from '@fastgpt/global/support/permission/memberGroup/constant';
import { AuthModeType, AuthResponseType } from '../type';
import { TeamErrEnum } from '@fastgpt/global/common/error/code/team';
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
import { getTmbInfoByTmbId } from '../../user/team/controller';
/**
* Get the default group of a team
* @param{Object} obj
* @param{string} obj.teamId
* @param{ClientSession} obj.session
*/
export const getTeamDefaultGroup = async ({
teamId,
session
}: {
teamId: string;
session?: ClientSession;
}) => {
const group = await MongoMemberGroupModel.findOne({ teamId, name: DefaultGroupName }, undefined, {
session
}).lean();
// Create the default group if it does not exist
if (!group) {
const [group] = await MongoMemberGroupModel.create(
[
{
teamId,
name: DefaultGroupName
}
],
{ session }
);
return group;
}
return group;
};
export const getGroupsByTmbId = async ({
tmbId,
teamId,
role
}: {
tmbId: string;
teamId: string;
role?: `${GroupMemberRole}`[];
}) =>
(
await Promise.all([
(
await MongoGroupMemberModel.find({
tmbId,
groupId: {
$exists: true
},
role: role ? { $in: role } : undefined
})
.populate('groupId')
.lean()
).map((item) => {
return {
...(item.groupId as any as MemberGroupSchemaType)
};
}),
role ? [] : getTeamDefaultGroup({ teamId })
])
).flat();
export const getTmbByGroupId = async (groupId: string) => {
return (
await MongoGroupMemberModel.find({
groupId
})
.populate('tmbId')
.lean()
).map((item) => {
return {
...(item.tmbId as any as MemberGroupSchemaType)
};
});
};
export const getGroupMembersByGroupId = async (groupId: string) => {
return await MongoGroupMemberModel.find({
groupId
}).lean();
};
export const getGroupMembersWithInfoByGroupId = async (groupId: string) => {
return (
await MongoGroupMemberModel.find({
groupId
})
.populate('tmbId')
.lean()
).map((item) => item.tmbId) as any as TeamMemberSchema[]; // HACK: type casting
};
/**
* 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,
role,
...props
}: {
groupId: string;
role: `${GroupMemberRole}`[];
} & AuthModeType): Promise<AuthResponseType> => {
const result = await parseHeaderCert(props);
const { teamId, tmbId, isRoot } = result;
if (isRoot) {
return {
...result,
permission: new TeamPermission({
isOwner: true
}),
teamId,
tmbId
};
}
const groupMember = await MongoGroupMemberModel.findOne({ groupId, tmbId });
const tmb = await getTmbInfoByTmbId({ tmbId });
if (tmb.permission.hasManagePer || (groupMember && role.includes(groupMember.role))) {
return {
...result,
permission: tmb.permission,
teamId,
tmbId
};
}
return Promise.reject(TeamErrEnum.unAuthTeam);
};

View File

@@ -0,0 +1,44 @@
import { TeamMemberCollectionName } from '@fastgpt/global/support/user/team/constant';
import { connectionMongo, getMongoModel } from '../../../common/mongo';
import { MemberGroupCollectionName } from './memberGroupSchema';
import { GroupMemberSchemaType } from '@fastgpt/global/support/permission/memberGroup/type';
import { GroupMemberRole } from '@fastgpt/global/support/permission/memberGroup/constant';
const { Schema } = connectionMongo;
export const GroupMemberCollectionName = 'team_group_members';
export const GroupMemberSchema = new Schema({
groupId: {
type: Schema.Types.ObjectId,
ref: MemberGroupCollectionName,
required: true
},
tmbId: {
type: Schema.Types.ObjectId,
ref: TeamMemberCollectionName,
required: true
},
role: {
type: String,
enum: Object.values(GroupMemberRole),
required: true,
default: GroupMemberRole.member
}
});
try {
GroupMemberSchema.index({
groupId: 1
});
GroupMemberSchema.index({
tmbId: 1
});
} catch (error) {
console.log(error);
}
export const MongoGroupMemberModel = getMongoModel<GroupMemberSchemaType>(
GroupMemberCollectionName,
GroupMemberSchema
);

View File

@@ -0,0 +1,51 @@
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
import { connectionMongo, getMongoModel } from '../../../common/mongo';
import { MemberGroupSchemaType } from '@fastgpt/global/support/permission/memberGroup/type';
const { Schema } = connectionMongo;
export const MemberGroupCollectionName = 'team_member_groups';
export const MemberGroupSchema = new Schema(
{
teamId: {
type: Schema.Types.ObjectId,
ref: TeamCollectionName,
required: true
},
name: {
type: String,
required: true
},
avatar: {
type: String
},
updateTime: {
type: Date,
default: () => new Date()
}
},
{
timestamps: {
updatedAt: 'updateTime'
}
}
);
try {
MemberGroupSchema.index(
{
teamId: 1,
name: 1
},
{
unique: true
}
);
} catch (error) {
console.log(error);
}
export const MongoMemberGroupModel = getMongoModel<MemberGroupSchemaType>(
MemberGroupCollectionName,
MemberGroupSchema
);

View File

@@ -5,9 +5,10 @@ import {
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';
const { Schema } = connectionMongo;
export const ResourcePermissionCollectionName = 'resource_permission';
export const ResourcePermissionCollectionName = 'resource_permissions';
export const ResourcePermissionSchema = new Schema({
teamId: {
@@ -18,6 +19,10 @@ export const ResourcePermissionSchema = new Schema({
type: Schema.Types.ObjectId,
ref: TeamMemberCollectionName
},
groupId: {
type: Schema.Types.ObjectId,
ref: MemberGroupCollectionName
},
resourceType: {
type: String,
enum: Object.values(PerResourceTypeEnum),
@@ -40,12 +45,14 @@ try {
resourceType: 1,
teamId: 1,
tmbId: 1,
resourceId: 1
resourceId: 1,
groupId: 1
},
{
unique: true
}
);
ResourcePermissionSchema.index({
resourceType: 1,
teamId: 1,

View File

@@ -1,6 +1,7 @@
import { Permission } from '@fastgpt/global/support/permission/controller';
import { ApiRequestProps } from '../../type/next';
import type { PermissionValueType } from '@fastgpt/global/support/permission/type';
import { RequireAtLeastOne } from '@fastgpt/global/common/type/utils';
export type ReqHeaderAuthType = {
cookie?: string;
@@ -11,11 +12,6 @@ export type ReqHeaderAuthType = {
authorization?: string;
};
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Omit<T, Keys> &
{
[K in Keys]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;
}[Keys];
type authModeType = {
req: ApiRequestProps;
authToken?: boolean;

View File

@@ -11,6 +11,10 @@ import { UpdateTeamProps } from '@fastgpt/global/support/user/team/controller';
import { getResourcePermission } from '../../permission/controller';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant';
import { TeamPermission } from '@fastgpt/global/support/permission/user/controller';
import { TeamDefaultPermissionVal } from '@fastgpt/global/support/permission/user/constant';
import { MongoMemberGroupModel } from '../../permission/memberGroup/memberGroupSchema';
import { mongoSessionRun } from '../../../common/mongo/sessionRun';
import { DefaultGroupName } from '@fastgpt/global/support/user/team/group/constant';
async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemType> {
const tmb = (await MongoTeamMember.findOne(match).populate('teamId')) as TeamMemberWithTeamSchema;
@@ -18,7 +22,7 @@ async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemTyp
return Promise.reject('member not exist');
}
const tmbPer = await getResourcePermission({
const Per = await getResourcePermission({
resourceType: PerResourceTypeEnum.team,
teamId: tmb.teamId._id,
tmbId: tmb._id
@@ -38,7 +42,7 @@ async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemTyp
defaultTeam: tmb.defaultTeam,
lafAccount: tmb.teamId.lafAccount,
permission: new TeamPermission({
per: tmbPer?.permission ?? tmb.teamId.defaultPermission,
per: Per ?? TeamDefaultPermissionVal,
isOwner: tmb.role === TeamMemberRoleEnum.owner
}),
notificationAccount: tmb.teamId.notificationAccount
@@ -64,6 +68,7 @@ export async function getUserDefaultTeam({ userId }: { userId: string }) {
defaultTeam: true
});
}
export async function createDefaultTeam({
userId,
teamName = 'My Team',
@@ -84,7 +89,7 @@ export async function createDefaultTeam({
});
if (!tmb) {
// create
// create team
const [{ _id: insertedId }] = await MongoTeam.create(
[
{
@@ -97,7 +102,8 @@ export async function createDefaultTeam({
],
{ session }
);
await MongoTeamMember.create(
// create team member
const [tmb] = await MongoTeamMember.create(
[
{
teamId: insertedId,
@@ -111,7 +117,19 @@ export async function createDefaultTeam({
],
{ session }
);
console.log('create default team', userId);
// create default group
await MongoMemberGroupModel.create(
[
{
teamId: tmb.teamId,
name: DefaultGroupName,
avatar
}
],
{ session }
);
console.log('create default team and group', userId);
return tmb;
} else {
console.log('default team exist', userId);
await MongoTeam.findByIdAndUpdate(tmb.teamId, {
@@ -129,10 +147,30 @@ export async function updateTeam({
teamDomain,
lafAccount
}: UpdateTeamProps & { teamId: string }) {
await MongoTeam.findByIdAndUpdate(teamId, {
name,
avatar,
teamDomain,
lafAccount
return mongoSessionRun(async (session) => {
await MongoTeam.findByIdAndUpdate(
teamId,
{
name,
avatar,
teamDomain,
lafAccount
},
{ session }
);
// update default group
if (avatar) {
await MongoMemberGroupModel.updateOne(
{
teamId: teamId,
name: DefaultGroupName
},
{
avatar
},
{ session }
);
}
});
}

View File

@@ -3,7 +3,6 @@ const { Schema } = connectionMongo;
import { TeamSchema as TeamType } from '@fastgpt/global/support/user/team/type.d';
import { userCollectionName } from '../../user/schema';
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
import { TeamDefaultPermissionVal } from '@fastgpt/global/support/permission/user/constant';
const TeamSchema = new Schema({
name: {
@@ -14,10 +13,6 @@ const TeamSchema = new Schema({
type: Schema.Types.ObjectId,
ref: userCollectionName
},
defaultPermission: {
type: Number,
default: TeamDefaultPermissionVal
},
avatar: {
type: String,
default: '/icon/logo.svg'

View File

@@ -77,8 +77,18 @@ try {
// timer task. Get standard plan;Get free plan;Clear expired extract plan
SubSchema.index({ type: 1, expiredTime: -1, currentSubLevel: 1 });
// unique
SubSchema.index({ teamId: 1, type: 1, currentSubLevel: 1 }, { unique: true });
// 修改后的唯一索引
SubSchema.index(
{
teamId: 1,
type: 1,
currentSubLevel: 1
},
{
unique: true,
partialFilterExpression: { type: SubTypeEnum.standard }
}
);
} catch (error) {
console.log(error);
}

View File

@@ -1,5 +1,4 @@
import TurndownService from 'turndown';
const domino = require('domino-ext');
const turndownPluginGfm = require('joplin-turndown-plugin-gfm');
export const html2md = (html: string): string => {
@@ -15,24 +14,11 @@ export const html2md = (html: string): string => {
});
try {
const window = domino.createWindow(html);
const document = window.document;
turndownService.remove(['i', 'script', 'iframe']);
turndownService.addRule('codeBlock', {
filter: 'pre',
replacement(_, node) {
const content = node.textContent?.trim() || '';
// @ts-ignore
const codeName = node?._attrsByQName?.class?.data?.trim() || '';
return `\n\`\`\`${codeName}\n${content}\n\`\`\`\n`;
}
});
turndownService.remove(['i', 'script', 'iframe', 'style']);
turndownService.use(turndownPluginGfm.gfm);
return turndownService.turndown(document);
return turndownService.turndown(html);
} catch (error) {
console.log('html 2 markdown error', error);
return '';

View File

@@ -0,0 +1,55 @@
import React from 'react';
import Avatar from '.';
import { Box, Flex } from '@chakra-ui/react';
/**
* AvatarGroup
*
* @param avatars - avatars array
* @param max - max avatars to show
* @param [groupId] - group id to make the key unique
* @returns
*/
function AvatarGroup({
avatars,
max = 3,
groupId
}: {
max?: number;
avatars: string[];
groupId?: string;
}) {
return (
<Flex position="relative">
{avatars.slice(0, max).map((avatar, index) => (
<Avatar
key={avatar + groupId}
src={avatar}
position={index > 0 ? 'absolute' : 'relative'}
left={index > 0 ? `${index * 15}px` : 0}
zIndex={index > 0 ? index + 1 : 0}
w={'24px'}
borderRadius={'50%'}
/>
))}
{avatars.length > max && (
<Box
position="relative"
left={`${(max - 1) * 15}px`}
w={'24px'}
h={'24px'}
borderRadius="50%"
display="flex"
alignItems="center"
justifyContent="center"
fontSize="sm"
color="myGray.500"
>
+{avatars.length - max}
</Box>
)}
</Flex>
);
}
export default AvatarGroup;

View File

@@ -7,6 +7,7 @@ export const iconPaths = {
closeSolid: () => import('./icons/closeSolid.svg'),
collectionLight: () => import('./icons/collectionLight.svg'),
collectionSolid: () => import('./icons/collectionSolid.svg'),
comment: () => import('./icons/comment.svg'),
'common/add2': () => import('./icons/common/add2.svg'),
'common/addCircleLight': () => import('./icons/common/addCircleLight.svg'),
'common/addLight': () => import('./icons/common/addLight.svg'),
@@ -37,6 +38,8 @@ export const iconPaths = {
'common/importLight': () => import('./icons/common/importLight.svg'),
'common/info': () => import('./icons/common/info.svg'),
'common/inviteLight': () => import('./icons/common/inviteLight.svg'),
'common/language/America': () => import('./icons/common/language/America.svg'),
'common/language/China': () => import('./icons/common/language/China.svg'),
'common/language/en': () => import('./icons/common/language/en.svg'),
'common/language/zh': () => import('./icons/common/language/zh.svg'),
'common/leftArrowLight': () => import('./icons/common/leftArrowLight.svg'),
@@ -210,8 +213,10 @@ export const iconPaths = {
'core/workflow/runSkip': () => import('./icons/core/workflow/runSkip.svg'),
'core/workflow/runSuccess': () => import('./icons/core/workflow/runSuccess.svg'),
'core/workflow/running': () => import('./icons/core/workflow/running.svg'),
'core/workflow/template/BI': () => import('./icons/core/workflow/template/BI.svg'),
'core/workflow/template/FileRead': () => import('./icons/core/workflow/template/FileRead.svg'),
'core/workflow/template/aiChat': () => import('./icons/core/workflow/template/aiChat.svg'),
'core/workflow/template/baseChart': () => import('./icons/core/workflow/template/baseChart.svg'),
'core/workflow/template/codeRun': () => import('./icons/core/workflow/template/codeRun.svg'),
'core/workflow/template/customFeedback': () =>
import('./icons/core/workflow/template/customFeedback.svg'),
@@ -245,15 +250,17 @@ export const iconPaths = {
'core/workflow/template/reply': () => import('./icons/core/workflow/template/reply.svg'),
'core/workflow/template/runApp': () => import('./icons/core/workflow/template/runApp.svg'),
'core/workflow/template/stopTool': () => import('./icons/core/workflow/template/stopTool.svg'),
'core/workflow/template/toolkitActive': () =>
import('./icons/core/workflow/template/toolkitActive.svg'),
'core/workflow/template/toolkitInactive': () =>
import('./icons/core/workflow/template/toolkitInactive.svg'),
'core/workflow/template/systemConfig': () =>
import('./icons/core/workflow/template/systemConfig.svg'),
'core/workflow/template/textConcat': () =>
import('./icons/core/workflow/template/textConcat.svg'),
'core/workflow/template/toolCall': () => import('./icons/core/workflow/template/toolCall.svg'),
'core/workflow/template/toolParams': () =>
import('./icons/core/workflow/template/toolParams.svg'),
'core/workflow/template/toolkitActive': () =>
import('./icons/core/workflow/template/toolkitActive.svg'),
'core/workflow/template/toolkitInactive': () =>
import('./icons/core/workflow/template/toolkitInactive.svg'),
'core/workflow/template/userSelect': () =>
import('./icons/core/workflow/template/userSelect.svg'),
'core/workflow/template/variable': () => import('./icons/core/workflow/template/variable.svg'),
@@ -335,6 +342,7 @@ export const iconPaths = {
'support/permission/collaborator': () => import('./icons/support/permission/collaborator.svg'),
'support/permission/privateLight': () => import('./icons/support/permission/privateLight.svg'),
'support/permission/publicLight': () => import('./icons/support/permission/publicLight.svg'),
'support/team/group': () => import('./icons/support/team/group.svg'),
'support/team/key': () => import('./icons/support/team/key.svg'),
'support/team/memberLight': () => import('./icons/support/team/memberLight.svg'),
'support/usage/usageRecordLight': () => import('./icons/support/usage/usageRecordLight.svg'),

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 17" >
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.7746 2.90224H5.22543C4.67152 2.90224 4.33762 2.90331 4.08927 2.92394C3.91796 2.93817 3.85352 2.95783 3.83939 2.96308C3.74318 3.01345 3.66463 3.09199 3.61426 3.18821C3.60902 3.20234 3.58935 3.26677 3.57512 3.43809C3.5545 3.68644 3.55343 4.02034 3.55343 4.57424V12.4544C3.55343 13.1464 3.55481 13.5712 3.58283 13.8723C3.58462 13.8915 3.58646 13.9093 3.58832 13.9258C3.60297 13.9181 3.61873 13.9096 3.63562 13.9002C3.90035 13.754 4.2523 13.5161 4.82438 13.1268L6.49832 11.9875C6.51469 11.9764 6.53278 11.9638 6.55248 11.9502C6.7415 11.8192 7.07919 11.5851 7.47593 11.4903C7.79844 11.4133 8.13446 11.4124 8.45734 11.4879C8.85455 11.5808 9.19338 11.8131 9.38304 11.9432C9.40281 11.9568 9.42096 11.9692 9.43739 11.9803L11.1789 13.153C11.7501 13.5377 12.1011 13.7724 12.365 13.9165C12.3817 13.9256 12.3974 13.934 12.4119 13.9415C12.4137 13.9252 12.4156 13.9076 12.4173 13.8886C12.4452 13.5892 12.4466 13.167 12.4466 12.4784V4.57424C12.4466 4.02034 12.4455 3.68644 12.4249 3.43809C12.4107 3.26677 12.391 3.20233 12.3857 3.18821C12.3354 3.09199 12.2568 3.01345 12.1606 2.96308C12.1465 2.95783 12.082 2.93816 11.9107 2.92394C11.6624 2.90331 11.3285 2.90224 10.7746 2.90224ZM2.43025 2.57509C2.22009 2.97967 2.22009 3.51119 2.22009 4.57424V12.4544C2.22009 13.7844 2.22009 14.4495 2.50121 14.8107C2.73556 15.1118 3.08767 15.2981 3.46845 15.3224C3.92523 15.3516 4.47501 14.9774 5.57457 14.229L7.24851 13.0898C7.51151 12.9108 7.64301 12.8213 7.78582 12.7871C7.90675 12.7582 8.03276 12.7579 8.15384 12.7862C8.29681 12.8197 8.42875 12.9085 8.69263 13.0862L10.4342 14.259C11.5318 14.9981 12.0805 15.3677 12.5361 15.337C12.9159 15.3115 13.2667 15.1248 13.5 14.824C13.7799 14.4633 13.7799 13.8017 13.7799 12.4784V4.57424C13.7799 3.51119 13.7799 2.97967 13.5697 2.57509C13.3926 2.23416 13.1147 1.95617 12.7737 1.77907C12.3691 1.56891 11.8376 1.56891 10.7746 1.56891H5.22543C4.16238 1.56891 3.63085 1.56891 3.22627 1.77907C2.88534 1.95617 2.60736 2.23416 2.43025 2.57509Z" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,11 +1,5 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1698673802976"
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2726"
xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128">
<path
d="M496.68187246 465.60374835c121.95586149 0 220.9345317-98.9786702 220.9345317-220.9345317s-98.9786702-220.9345317-220.9345317-220.93453167-220.9345317 98.9786702-220.93453167 220.93453167 98.9786702 220.9345317 220.93453167 220.9345317z m-125.04894492-345.98347662c33.43475913-33.28746944 77.76895517-51.69868042 125.04894492-51.69868042 47.27998978 0 91.61418581 18.41121097 125.04894495 51.69868042 33.43475913 33.43475913 51.69868042 77.76895517 51.69868041 125.04894492 0 47.27998978-18.41121097 91.61418581-51.69868041 125.04894495-33.43475913 33.43475913-77.76895517 51.69868042-125.04894495 51.69868042-47.27998978 0-91.61418581-18.41121097-125.04894492-51.69868042-33.43475913-33.43475913-51.69868042-77.76895517-51.69868041-125.04894495 0-47.27998978 18.41121097-91.61418581 51.69868041-125.04894492zM511.41084125 892.59655326h-213.5700473c-81.15661796 0-147.2896878-66.13306981-147.28968778-147.2896878 0-39.32634663 15.31812754-76.29605827 43.1558785-104.13380927 27.83775099-27.83775099 64.80746262-43.15587853 104.13380928-43.15587853h397.68215704c52.14054948 0 100.89343614 27.98504069 127.25829025 73.05568516 6.1861669 10.60485751 19.73681815 14.13981003 30.19438599 7.95364313 10.60485751-6.1861669 14.13981003-19.73681815 7.95364314-30.19438599-34.31849726-58.47400606-97.65306301-94.85455893-165.40631938-94.85455893h-397.68215704c-51.10952167 0-99.12595989 19.88410785-135.35922308 56.11737104-36.2332632 36.0859735-56.11737105 84.24970142-56.11737105 135.35922308 0 105.60670615 85.86988799 191.47659413 191.47659413 191.47659413h213.5700473c12.22504409 0 22.09345317-10.01569877 22.09345316-22.24074285s-9.86840908-22.09345317-22.09345316-22.09345317z"
p-id="2727"></path>
<path
d="M873.74347321 830.88217408h-103.10278144v-103.10278145c0-12.22504409-9.86840908-22.09345317-22.09345318-22.09345318s-22.09345317 9.86840908-22.09345316 22.09345318v103.10278145h-103.10278146c-12.22504409 0-22.09345317 9.86840908-22.09345318 22.09345315s9.86840908 22.09345317 22.09345318 22.09345318h103.10278146v103.10278146c0 12.22504409 9.86840908 22.09345317 22.09345316 22.09345315s22.09345317-9.86840908 22.09345318-22.09345315v-103.10278146h103.10278144c12.22504409 0 22.09345317-9.86840908 22.09345318-22.09345318s-9.86840908-22.09345317-22.09345318-22.09345315z"
p-id="2728"></path>
</svg>
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2292 4.81275C11.2292 6.59622 9.78346 8.04201 7.99999 8.04201C6.21652 8.04201 4.77073 6.59622 4.77073 4.81275C4.77073 3.02928 6.21652 1.5835 7.99999 1.5835C9.78346 1.5835 11.2292 3.02928 11.2292 4.81275ZM9.89591 4.81275C9.89591 5.85984 9.04708 6.70868 7.99999 6.70868C6.9529 6.70868 6.10406 5.85984 6.10406 4.81275C6.10406 3.76566 6.9529 2.91683 7.99999 2.91683C9.04708 2.91683 9.89591 3.76566 9.89591 4.81275Z" fill=""/>
<path d="M2.40472 13.2218C2.40472 11.0126 4.19559 9.22177 6.40472 9.22177H8.84072C9.20891 9.22177 9.50738 9.52025 9.50738 9.88844C9.50738 10.2566 9.20891 10.5551 8.84072 10.5551H6.40472C5.01236 10.5551 3.86924 11.6222 3.74858 12.9832H8.84072C9.20891 12.9832 9.50738 13.2817 9.50738 13.6499C9.50738 14.0181 9.20891 14.3165 8.84072 14.3165H3.13806C2.73305 14.3165 2.40472 13.9882 2.40472 13.5832V13.2218Z"/>
<path d="M13.1385 9.91359C13.1385 9.5454 12.84 9.24693 12.4718 9.24693C12.1036 9.24693 11.8052 9.54541 11.8052 9.91359V11.165H10.5537C10.1855 11.165 9.88702 11.4635 9.88702 11.8317C9.88702 12.1999 10.1855 12.4984 10.5537 12.4984H11.8052V13.7498C11.8052 14.118 12.1036 14.4165 12.4718 14.4165C12.84 14.4165 13.1385 14.118 13.1385 13.7498V12.4984H14.3899C14.7581 12.4984 15.0566 12.1999 15.0566 11.8317C15.0566 11.4635 14.7581 11.165 14.3899 11.165H13.1385V9.91359Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,56 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 10" fill="none">
<path d="M14 0H0V9.31222H14V0Z" fill="#B31942"/>
<path d="M0 1.07422H14H0ZM14 2.50687H0H14ZM0 3.93952H14H0ZM14 5.37217H0H14ZM0 6.80481H14H0ZM14 8.23746H0H14Z" fill="#000008"/>
<path d="M0 1.07422H14M14 2.50687H0M0 3.93952H14M14 5.37217H0M0 6.80481H14M14 8.23746H0" stroke="white" stroke-width="0.716325"/>
<path d="M7.07729 0H0V5.01427H7.07729V0Z" fill="#0A3161"/>
<path d="M0.589645 0.214844L0.758063 0.733181L0.317139 0.412831H0.862151L0.421227 0.733181L0.589645 0.214844Z" fill="white"/>
<path d="M0.589645 1.21777L0.758063 1.73611L0.317139 1.41576H0.862151L0.421227 1.73611L0.589645 1.21777Z" fill="white"/>
<path d="M0.589645 2.2207L0.758063 2.73904L0.317139 2.41869H0.862151L0.421227 2.73904L0.589645 2.2207Z" fill="white"/>
<path d="M0.589645 3.22363L0.758063 3.74197L0.317139 3.42162H0.862151L0.421227 3.74197L0.589645 3.22363Z" fill="white"/>
<path d="M0.589645 4.22607L0.758063 4.74441L0.317139 4.42406H0.862151L0.421227 4.74441L0.589645 4.22607Z" fill="white"/>
<path d="M1.17924 0.71582L1.34766 1.23416L0.906738 0.913808H1.45175L1.01083 1.23416L1.17924 0.71582Z" fill="white"/>
<path d="M1.17924 1.71875L1.34766 2.23709L0.906738 1.91674H1.45175L1.01083 2.23709L1.17924 1.71875Z" fill="white"/>
<path d="M1.17924 2.72168L1.34766 3.24002L0.906738 2.91967H1.45175L1.01083 3.24002L1.17924 2.72168Z" fill="white"/>
<path d="M1.17924 3.72461L1.34766 4.24295L0.906738 3.9226H1.45175L1.01083 4.24295L1.17924 3.72461Z" fill="white"/>
<path d="M1.76811 0.215332L1.93653 0.733669L1.49561 0.413319H2.04062L1.59969 0.733669L1.76811 0.215332Z" fill="white"/>
<path d="M1.76811 1.21826L1.93653 1.7366L1.49561 1.41625H2.04062L1.59969 1.7366L1.76811 1.21826Z" fill="white"/>
<path d="M1.76811 2.22119L1.93653 2.73953L1.49561 2.41918H2.04062L1.59969 2.73953L1.76811 2.22119Z" fill="white"/>
<path d="M1.76811 3.22412L1.93653 3.74246L1.49561 3.42211H2.04062L1.59969 3.74246L1.76811 3.22412Z" fill="white"/>
<path d="M1.76836 4.22656L1.93677 4.7449L1.49585 4.42455H2.04086L1.59994 4.7449L1.76836 4.22656Z" fill="white"/>
<path d="M2.3582 0.716309L2.52662 1.23465L2.08569 0.914296H2.63071L2.18978 1.23465L2.3582 0.716309Z" fill="white"/>
<path d="M2.3582 1.71924L2.52662 2.23758L2.08569 1.91723H2.63071L2.18978 2.23758L2.3582 1.71924Z" fill="white"/>
<path d="M2.3582 2.72217L2.52662 3.24051L2.08569 2.92016H2.63071L2.18978 3.24051L2.3582 2.72217Z" fill="white"/>
<path d="M2.3582 3.7251L2.52662 4.24343L2.08569 3.92308H2.63071L2.18978 4.24343L2.3582 3.7251Z" fill="white"/>
<path d="M2.94682 0.21582L3.11524 0.734158L2.67432 0.413808H3.21933L2.7784 0.734158L2.94682 0.21582Z" fill="white"/>
<path d="M2.94682 1.21875L3.11524 1.73709L2.67432 1.41674H3.21933L2.7784 1.73709L2.94682 1.21875Z" fill="white"/>
<path d="M2.94682 2.22168L3.11524 2.74002L2.67432 2.41967H3.21933L2.7784 2.74002L2.94682 2.22168Z" fill="white"/>
<path d="M2.94682 3.22461L3.11524 3.74295L2.67432 3.4226H3.21933L2.7784 3.74295L2.94682 3.22461Z" fill="white"/>
<path d="M2.94731 4.22705L3.11573 4.74539L2.6748 4.42504H3.21982L2.77889 4.74539L2.94731 4.22705Z" fill="white"/>
<path d="M3.53715 0.716797L3.70557 1.23513L3.26465 0.914784H3.80966L3.36874 1.23513L3.53715 0.716797Z" fill="white"/>
<path d="M3.53715 1.71973L3.70557 2.23806L3.26465 1.91771H3.80966L3.36874 2.23806L3.53715 1.71973Z" fill="white"/>
<path d="M3.53715 2.72266L3.70557 3.24099L3.26465 2.92064H3.80966L3.36874 3.24099L3.53715 2.72266Z" fill="white"/>
<path d="M3.53715 3.72559L3.70557 4.24392L3.26465 3.92357H3.80966L3.36874 4.24392L3.53715 3.72559Z" fill="white"/>
<path d="M4.12895 0.216309L4.29737 0.734646L3.85645 0.414296H4.40146L3.96053 0.734646L4.12895 0.216309Z" fill="white"/>
<path d="M4.12895 1.21924L4.29737 1.73758L3.85645 1.41723H4.40146L3.96053 1.73758L4.12895 1.21924Z" fill="white"/>
<path d="M4.12895 2.22217L4.29737 2.74051L3.85645 2.42016H4.40146L3.96053 2.74051L4.12895 2.22217Z" fill="white"/>
<path d="M4.12895 3.2251L4.29737 3.74343L3.85645 3.42308H4.40146L3.96053 3.74343L4.12895 3.2251Z" fill="white"/>
<path d="M4.12846 4.22754L4.29688 4.74588L3.85596 4.42553H4.40097L3.96005 4.74588L4.12846 4.22754Z" fill="white"/>
<path d="M4.71831 0.716797L4.88673 1.23513L4.4458 0.914784H4.99081L4.54989 1.23513L4.71831 0.716797Z" fill="white"/>
<path d="M4.71831 1.71973L4.88673 2.23806L4.4458 1.91771H4.99081L4.54989 2.23806L4.71831 1.71973Z" fill="white"/>
<path d="M4.71831 2.72266L4.88673 3.24099L4.4458 2.92064H4.99081L4.54989 3.24099L4.71831 2.72266Z" fill="white"/>
<path d="M4.71831 3.72559L4.88673 4.24392L4.4458 3.92357H4.99081L4.54989 4.24392L4.71831 3.72559Z" fill="white"/>
<path d="M5.30839 0.21582L5.47681 0.734158L5.03589 0.413808H5.5809L5.13998 0.734158L5.30839 0.21582Z" fill="white"/>
<path d="M5.30839 1.21875L5.47681 1.73709L5.03589 1.41674H5.5809L5.13998 1.73709L5.30839 1.21875Z" fill="white"/>
<path d="M5.30839 2.22168L5.47681 2.74002L5.03589 2.41967H5.5809L5.13998 2.74002L5.30839 2.22168Z" fill="white"/>
<path d="M5.30839 3.22461L5.47681 3.74295L5.03589 3.4226H5.5809L5.13998 3.74295L5.30839 3.22461Z" fill="white"/>
<path d="M5.30815 4.22705L5.47657 4.74539L5.03564 4.42504H5.58066L5.13973 4.74539L5.30815 4.22705Z" fill="white"/>
<path d="M5.89799 0.716309L6.06641 1.23465L5.62549 0.914296H6.1705L5.72958 1.23465L5.89799 0.716309Z" fill="white"/>
<path d="M5.89799 1.71924L6.06641 2.23758L5.62549 1.91723H6.1705L5.72958 2.23758L5.89799 1.71924Z" fill="white"/>
<path d="M5.89799 2.72217L6.06641 3.24051L5.62549 2.92016H6.1705L5.72958 3.24051L5.89799 2.72217Z" fill="white"/>
<path d="M5.89799 3.72461L6.06641 4.24295L5.62549 3.9226H6.1705L5.72958 4.24295L5.89799 3.72461Z" fill="white"/>
<path d="M6.48759 0.215332L6.65601 0.733669L6.21509 0.413319H6.7601L6.31918 0.733669L6.48759 0.215332Z" fill="white"/>
<path d="M6.48735 1.21826L6.65577 1.7366L6.21484 1.41625H6.75986L6.31893 1.7366L6.48735 1.21826Z" fill="white"/>
<path d="M6.48735 2.22119L6.65577 2.73953L6.21484 2.41918H6.75986L6.31893 2.73953L6.48735 2.22119Z" fill="white"/>
<path d="M6.48735 3.22412L6.65577 3.74246L6.21484 3.42211H6.75986L6.31893 3.74246L6.48735 3.22412Z" fill="white"/>
<path d="M6.48735 4.22656L6.65577 4.7449L6.21484 4.42455H6.75986L6.31893 4.7449L6.48735 4.22656Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

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