Compare commits
23 Commits
v4.8.15-al
...
v4.8.15-fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c41def5fbb | ||
|
|
b596976a96 | ||
|
|
e71708ee76 | ||
|
|
ddddd998c8 | ||
|
|
181b854342 | ||
|
|
40af63b1dd | ||
|
|
d2a56a2fed | ||
|
|
4b9b0dbbb9 | ||
|
|
c0135f5f21 | ||
|
|
8a4715293e | ||
|
|
69dc927a5a | ||
|
|
d5752ddbaa | ||
|
|
048f5a2d53 | ||
|
|
096afef629 | ||
|
|
f832d32c62 | ||
|
|
d7c6204e62 | ||
|
|
806109b748 | ||
|
|
b327e487a5 | ||
|
|
c98224dda3 | ||
|
|
5cea6015c0 | ||
|
|
fd47f73086 | ||
|
|
fc8f73fc55 | ||
|
|
1b8b11239c |
@@ -126,17 +126,40 @@ OneAPI 的 API Key 配置错误,需要修改`OPENAI_API_KEY`环境变量,并
|
||||
|
||||
## 四、常见模型问题
|
||||
|
||||
### 报错 - 模型响应为空
|
||||
### 报错 - 模型响应为空/模型报错
|
||||
|
||||
该错误是由于 stream 模式下,oneapi 直接结束了流请求,并且未返回任何内容导致。
|
||||
|
||||
4.8.10 版本新增了错误日志,报错时,会在日志中打印出实际发送的 Body 参数,可以复制该参数后,通过 curl 向 oneapi 发起请求测试。
|
||||
|
||||
由于 oneapi 在 stream 模式下,无法正确捕获错误,可以设置成 `stream=false` 后进行测试。可能的问题:
|
||||
由于 oneapi 在 stream 模式下,无法正确捕获错误,有时候可以设置成 `stream=false` 来获取到精确的错误。
|
||||
|
||||
可能的报错问题:
|
||||
|
||||
1. 国内模型命中风控
|
||||
2. 不支持的模型参数:只保留 messages 和必要参数来测试,删除其他参数测试。
|
||||
3. 参数不符合模型要求:例如有的模型 temperature 不支持 0,有些不支持两位小数。max_tokens 超出,上下文超长等。
|
||||
4. 模型部署有问题,stream 模式不兼容。
|
||||
|
||||
测试示例如下,可复制报错日志中的请求体进行测试:
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://api.openai.com/v1/chat/completions' \
|
||||
--header 'Authorization: Bearer sk-xxxx' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{
|
||||
"model": "xxx",
|
||||
"temperature": 0.01,
|
||||
"max_tokens": 1000,
|
||||
"stream": true,
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": " 你是饿"
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
### 如何测试模型是否支持工具调用
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: '升级到 V4.3(需要初始化)'
|
||||
title: '升级到 V4.3(包含升级脚本)'
|
||||
description: 'FastGPT 从旧版本升级到 V4.3 操作指南'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: '升级到 V4.4(需要初始化)'
|
||||
title: '升级到 V4.4(包含升级脚本)'
|
||||
description: 'FastGPT 从旧版本升级到 V4.4 操作指南'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: '升级到 V4.4.1(需要初始化)'
|
||||
title: '升级到 V4.4.1(包含升级脚本)'
|
||||
description: 'FastGPT 从旧版本升级到 V4.4.1 操作指南'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: '升级到 V4.4.2(需要初始化)'
|
||||
title: '升级到 V4.4.2(包含升级脚本)'
|
||||
description: 'FastGPT 从旧版本升级到 V4.4.2 操作指南'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.4.5(需要初始化)'
|
||||
title: 'V4.4.5(包含升级脚本)'
|
||||
description: 'FastGPT V4.4.5 更新'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.6(需要初始化)'
|
||||
title: 'V4.6(包含升级脚本)'
|
||||
description: 'FastGPT V4.6 更新'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.6.2(需要初始化)'
|
||||
title: 'V4.6.2(包含升级脚本)'
|
||||
description: 'FastGPT V4.6.2'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.6.3(需要初始化)'
|
||||
title: 'V4.6.3(包含升级脚本)'
|
||||
description: 'FastGPT V4.6.3'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.6.4(需要初始化)'
|
||||
title: 'V4.6.4(包含升级脚本)'
|
||||
description: 'FastGPT V4.6.4'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.6.9(需要初始化)'
|
||||
title: 'V4.6.9(包含升级脚本)'
|
||||
description: 'FastGPT V4.6.9更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.7.1(需要初始化)'
|
||||
title: 'V4.7.1(包含升级脚本)'
|
||||
description: 'FastGPT V4.7.1 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.1(需要初始化)'
|
||||
title: 'V4.8.1(包含升级脚本)'
|
||||
description: 'FastGPT V4.8.1 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.10(需要初始化)'
|
||||
title: 'V4.8.10(包含升级脚本)'
|
||||
description: 'FastGPT V4.8.10 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.12(需要初始化)'
|
||||
title: 'V4.8.12(包含升级脚本)'
|
||||
description: 'FastGPT V4.8.12 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.15(进行中)'
|
||||
title: 'V4.8.15(包含升级脚本)'
|
||||
description: 'FastGPT V4.8.15 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
@@ -23,11 +23,36 @@ weight: 809
|
||||
|
||||
## 升级指南
|
||||
|
||||
- 更新 FastGPT 镜像 tag: v4.8.15-alpha
|
||||
- 更新 FastGPT 商业版镜像 tag: v4.8.15-alpha (fastgpt-pro镜像)
|
||||
- 更新 fastgpt 镜像 tag: v4.8.15-fix2
|
||||
- 更新 fastgpt-pro 商业版镜像 tag: v4.8.15
|
||||
- Sandbox 镜像,可以不更新
|
||||
|
||||
|
||||
## 运行升级脚本
|
||||
|
||||
从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`;{{host}} 替换成**FastGPT 域名**。
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://{{host}}/api/admin/initv4815' \
|
||||
--header 'rootkey: {{rootkey}}' \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
会重置应用定时执行的字段,把 null 去掉,减少索引大小。
|
||||
|
||||
----
|
||||
|
||||
从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`;{{host}} 替换成**FastGPT 域名**。
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://{{host}}/api/admin/refreshFreeUser' \
|
||||
--header 'rootkey: {{rootkey}}' \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
重新计算一次免费版用户的时长,之前有版本升级时没有重新计算时间,导致会误发通知。
|
||||
|
||||
|
||||
## 完整更新内容
|
||||
|
||||
1. 新增 - API 知识库, 见 [API 知识库介绍](/docs/guide/knowledge_base/api_dataset/),外部文件库会被弃用。
|
||||
@@ -41,9 +66,11 @@ weight: 809
|
||||
9. 优化 - 支持 Markdown 文本分割时,只有标题,无内容。
|
||||
10. 优化 - 字符串变量替换,未赋值的变量会转成 undefined,而不是保留原来 id 串。
|
||||
11. 优化 - 全局变量默认值在 API 生效,并且自定义变量支持默认值。
|
||||
12. 修复 - 分享链接点赞鉴权问题。
|
||||
13. 修复 - 对话页面切换自动执行应用时,会误触发非自动执行应用。
|
||||
14. 修复 - 语言播放鉴权问题。
|
||||
15. 修复 - 插件应用知识库引用上限始终为 3000
|
||||
16. 修复 - 工作流编辑记录存储上限,去掉本地存储,增加异常离开时,强制自动保存。
|
||||
17. 修复 - 工作流特殊变量替换问题。($开头的字符串无法替换)
|
||||
12. 优化 - 增加 HTTP Body 的 JSON 解析,正则将 undefined 转 null,减少 Body 解析错误。
|
||||
13. 优化 - 定时执行增加运行日志,增加重试,减少报错概率。
|
||||
14. 修复 - 分享链接点赞鉴权问题。
|
||||
15. 修复 - 对话页面切换自动执行应用时,会误触发非自动执行应用。
|
||||
16. 修复 - 语言播放鉴权问题。
|
||||
17. 修复 - 插件应用知识库引用上限始终为 3000
|
||||
18. 修复 - 工作流编辑记录存储上限,去掉本地存储,增加异常离开时,强制自动保存。
|
||||
19. 修复 - 工作流特殊变量替换问题。($开头的字符串无法替换)
|
||||
|
||||
20
docSite/content/zh-cn/docs/development/upgrading/4816.md
Normal file
20
docSite/content/zh-cn/docs/development/upgrading/4816.md
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
title: 'V4.8.16(进行中)'
|
||||
description: 'FastGPT V4.8.16 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 808
|
||||
---
|
||||
|
||||
|
||||
## 完整更新内容
|
||||
|
||||
1.
|
||||
2. 新增 - 商业版支持 API 知识库和链接集合定时同步。
|
||||
3. 优化 - 工作流/简易模式变量初始化代码,去除监听初始化,避免因渲染顺序不一致导致的失败。
|
||||
4. 修复 - 无法自动切换默认语言。增加分享链接,强制执行一次切换默认语言。
|
||||
5. 修复 - 数组选择器自动兼容 4.8.13 以前的数据。
|
||||
6. 修复 - 站点同步知识库,链接同步时未使用选择器。
|
||||
7. 修复 - 简易模式转工作流,没有把系统配置项转化。
|
||||
8. 修复 - 插件独立运行,变量初始值未赋上。
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.4(需要初始化)'
|
||||
title: 'V4.8.4(包含升级脚本)'
|
||||
description: 'FastGPT V4.8.4 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.5(需要初始化)'
|
||||
title: 'V4.8.5(包含升级脚本)'
|
||||
description: 'FastGPT V4.8.5 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.6(需要初始化)'
|
||||
title: 'V4.8.6(包含升级脚本)'
|
||||
description: 'FastGPT V4.8.6 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 'V4.8.8(需要初始化)'
|
||||
title: 'V4.8.8(包含升级脚本)'
|
||||
description: 'FastGPT V4.8.8 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
|
||||
@@ -55,14 +55,14 @@ docker-compose up -d
|
||||
|
||||
## 执行升级初始化脚本
|
||||
|
||||
镜像更新完后,可以查看文档中的`版本介绍`,通常需要执行升级脚本的版本都会标明`需要初始化`,打开对应的文档,参考说明执行初始化脚本即可,大部分时候都是需要发送一个`POST`请求。
|
||||
镜像更新完后,可以查看文档中的`版本介绍`,通常需要执行升级脚本的版本都会标明`包含升级脚本`,打开对应的文档,参考说明执行**升级脚本**即可,大部分时候都是需要发送一个`POST`请求。
|
||||
|
||||
|
||||
## QA
|
||||
|
||||
### 为什么需要初始化
|
||||
### 为什么需要执行升级脚本
|
||||
|
||||
数据表出现大幅度变更,无法通过设置默认值,或复杂度较高时,会通过初始化来更新部分数据表字段。
|
||||
数据表出现大幅度变更,无法通过设置默认值,或复杂度较高时,会通过升级脚本来更新部分数据表字段。
|
||||
严格按初始化步骤进行操作,不会造成旧数据丢失。但在初始化过程中,如果数据量大,需要初始化的时间较长,这段时间可能会造成服务无法正常使用。
|
||||
|
||||
### {{host}} 是什么
|
||||
|
||||
@@ -114,15 +114,15 @@ services:
|
||||
# fastgpt
|
||||
sandbox:
|
||||
container_name: sandbox
|
||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.14 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.14 # 阿里云
|
||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.15 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.15 # 阿里云
|
||||
networks:
|
||||
- fastgpt
|
||||
restart: always
|
||||
fastgpt:
|
||||
container_name: fastgpt
|
||||
image: ghcr.io/labring/fastgpt:v4.8.14-milvus-fix # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.14-milvus-fix # 阿里云
|
||||
image: ghcr.io/labring/fastgpt:v4.8.15-fix2 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.15-fix2 # 阿里云
|
||||
ports:
|
||||
- 3000:3000
|
||||
networks:
|
||||
|
||||
@@ -72,15 +72,15 @@ services:
|
||||
# fastgpt
|
||||
sandbox:
|
||||
container_name: sandbox
|
||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.13 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.13 # 阿里云
|
||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.15 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.15 # 阿里云
|
||||
networks:
|
||||
- fastgpt
|
||||
restart: always
|
||||
fastgpt:
|
||||
container_name: fastgpt
|
||||
image: ghcr.io/labring/fastgpt:v4.8.14 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.14 # 阿里云
|
||||
image: ghcr.io/labring/fastgpt:v4.8.15-fix2 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.15-fix2 # 阿里云
|
||||
ports:
|
||||
- 3000:3000
|
||||
networks:
|
||||
|
||||
@@ -53,15 +53,15 @@ services:
|
||||
wait $$!
|
||||
sandbox:
|
||||
container_name: sandbox
|
||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.14 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.14 # 阿里云
|
||||
image: ghcr.io/labring/fastgpt-sandbox:v4.8.15 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.8.15 # 阿里云
|
||||
networks:
|
||||
- fastgpt
|
||||
restart: always
|
||||
fastgpt:
|
||||
container_name: fastgpt
|
||||
image: ghcr.io/labring/fastgpt:v4.8.14-milvus-fix # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.14-milvus-fix # 阿里云
|
||||
image: ghcr.io/labring/fastgpt:v4.8.15-fix2 # git
|
||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.8.15-fix2 # 阿里云
|
||||
ports:
|
||||
- 3000:3000
|
||||
networks:
|
||||
|
||||
@@ -72,7 +72,7 @@ export const ERROR_RESPONSE: Record<
|
||||
[ERROR_ENUM.tooManyRequest]: {
|
||||
code: 429,
|
||||
statusText: ERROR_ENUM.tooManyRequest,
|
||||
message: 'Too many request',
|
||||
message: i18nT('common:error.too_many_request'),
|
||||
data: null
|
||||
},
|
||||
[ERROR_ENUM.insufficientQuota]: {
|
||||
|
||||
@@ -29,7 +29,7 @@ export const simpleText = (text = '') => {
|
||||
replace {{variable}} to value
|
||||
*/
|
||||
export function replaceVariable(text: any, obj: Record<string, string | number>) {
|
||||
if (!(typeof text === 'string')) return text;
|
||||
if (typeof text !== 'string') return text;
|
||||
|
||||
for (const key in obj) {
|
||||
const val = obj[key];
|
||||
|
||||
@@ -33,6 +33,7 @@ export enum ChatSourceEnum {
|
||||
online = 'online',
|
||||
share = 'share',
|
||||
api = 'api',
|
||||
cronJob = 'cronJob',
|
||||
team = 'team',
|
||||
feishu = 'feishu',
|
||||
official_account = 'official_account',
|
||||
@@ -52,6 +53,9 @@ export const ChatSourceMap = {
|
||||
[ChatSourceEnum.api]: {
|
||||
name: i18nT('common:core.chat.logs.api')
|
||||
},
|
||||
[ChatSourceEnum.cronJob]: {
|
||||
name: i18nT('chat:source_cronJob')
|
||||
},
|
||||
[ChatSourceEnum.team]: {
|
||||
name: i18nT('common:core.chat.logs.team')
|
||||
},
|
||||
|
||||
5
packages/global/core/dataset/api.d.ts
vendored
5
packages/global/core/dataset/api.d.ts
vendored
@@ -17,6 +17,9 @@ export type DatasetUpdateBody = {
|
||||
externalReadUrl?: DatasetSchemaType['externalReadUrl'];
|
||||
defaultPermission?: DatasetSchemaType['defaultPermission'];
|
||||
apiServer?: DatasetSchemaType['apiServer'];
|
||||
|
||||
// sync schedule
|
||||
autoSync?: boolean;
|
||||
};
|
||||
|
||||
/* ================= collection ===================== */
|
||||
@@ -47,6 +50,8 @@ export type CreateDatasetCollectionParams = DatasetCollectionChunkMetadataType &
|
||||
tags?: string[];
|
||||
|
||||
createTime?: Date;
|
||||
updateTime?: Date;
|
||||
nextSyncTime?: Date;
|
||||
};
|
||||
|
||||
export type ApiCreateDatasetCollectionParams = DatasetCollectionChunkMetadataType & {
|
||||
|
||||
@@ -82,7 +82,8 @@ export const DatasetCollectionTypeMap = {
|
||||
|
||||
export enum DatasetCollectionSyncResultEnum {
|
||||
sameRaw = 'sameRaw',
|
||||
success = 'success'
|
||||
success = 'success',
|
||||
failed = 'failed'
|
||||
}
|
||||
export const DatasetCollectionSyncResultMap = {
|
||||
[DatasetCollectionSyncResultEnum.sameRaw]: {
|
||||
@@ -90,6 +91,9 @@ export const DatasetCollectionSyncResultMap = {
|
||||
},
|
||||
[DatasetCollectionSyncResultEnum.success]: {
|
||||
label: i18nT('common:core.dataset.collection.sync.result.success')
|
||||
},
|
||||
[DatasetCollectionSyncResultEnum.failed]: {
|
||||
label: i18nT('dataset:sync_collection_failed')
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
9
packages/global/core/dataset/type.d.ts
vendored
9
packages/global/core/dataset/type.d.ts
vendored
@@ -34,8 +34,7 @@ export type DatasetSchemaType = {
|
||||
inheritPermission: boolean;
|
||||
apiServer?: APIFileServer;
|
||||
|
||||
syncSchedule?: { cronString: string; timezone: string };
|
||||
syncNextTime?: Date;
|
||||
autoSync?: boolean;
|
||||
|
||||
// abandon
|
||||
externalReadUrl?: string;
|
||||
@@ -65,11 +64,13 @@ export type DatasetCollectionSchemaType = {
|
||||
fileId?: string; // local file id
|
||||
rawLink?: string; // link url
|
||||
externalFileId?: string; //external file id
|
||||
apiFileId?: string; // api file id
|
||||
externalFileUrl?: string; // external import url
|
||||
|
||||
nextSyncTime?: Date;
|
||||
|
||||
rawTextLength?: number;
|
||||
hashRawText?: string;
|
||||
externalFileUrl?: string; // external import url
|
||||
apiFileId?: string; // api file id
|
||||
metadata?: {
|
||||
webPageSelector?: string;
|
||||
relatedImgId?: string; // The id of the associated image collections
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChatCompletionRequestMessageRoleEnum } from '../../ai/constants';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '../constants';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum, WorkflowIOValueTypeEnum } from '../constants';
|
||||
import { FlowNodeTypeEnum } from '../node/constant';
|
||||
import { StoreNodeItemType } from '../type/node';
|
||||
import { StoreEdgeItemType } from '../type/edge';
|
||||
@@ -251,6 +251,7 @@ export const getReferenceVariableValue = ({
|
||||
return variables[outputId];
|
||||
}
|
||||
|
||||
// 避免 value 刚好就是二个元素的字符串数组
|
||||
const node = nodes.find((node) => node.nodeId === sourceNodeId);
|
||||
if (!node) {
|
||||
return value;
|
||||
@@ -279,6 +280,27 @@ export const getReferenceVariableValue = ({
|
||||
return value;
|
||||
};
|
||||
|
||||
export const formatVariableValByType = (val: any, valueType?: WorkflowIOValueTypeEnum) => {
|
||||
if (!valueType) return val;
|
||||
// Value type check, If valueType invalid, return undefined
|
||||
if (valueType.startsWith('array') && !Array.isArray(val)) return undefined;
|
||||
if (valueType === WorkflowIOValueTypeEnum.boolean && typeof val !== 'boolean') return undefined;
|
||||
if (valueType === WorkflowIOValueTypeEnum.number && typeof val !== 'number') return undefined;
|
||||
if (valueType === WorkflowIOValueTypeEnum.string && typeof val !== 'string') return undefined;
|
||||
if (
|
||||
[
|
||||
WorkflowIOValueTypeEnum.object,
|
||||
WorkflowIOValueTypeEnum.chatHistory,
|
||||
WorkflowIOValueTypeEnum.datasetQuote,
|
||||
WorkflowIOValueTypeEnum.selectApp,
|
||||
WorkflowIOValueTypeEnum.selectDataset
|
||||
].includes(valueType) &&
|
||||
typeof val !== 'object'
|
||||
)
|
||||
return undefined;
|
||||
|
||||
return val;
|
||||
};
|
||||
// replace {{$xx.xx$}} variables for text
|
||||
export function replaceEditorVariable({
|
||||
text,
|
||||
@@ -308,7 +330,7 @@ export function replaceEditorVariable({
|
||||
if (!node) return;
|
||||
|
||||
const output = node.outputs.find((output) => output.id === id);
|
||||
if (output) return output.value;
|
||||
if (output) return formatVariableValByType(output.value, output.valueType);
|
||||
|
||||
const input = node.inputs.find((input) => input.key === id);
|
||||
if (input) return getReferenceVariableValue({ value: input.value, nodes, variables });
|
||||
|
||||
@@ -121,6 +121,13 @@ const AppSchema = new Schema({
|
||||
|
||||
AppSchema.index({ teamId: 1, updateTime: -1 });
|
||||
AppSchema.index({ teamId: 1, type: 1 });
|
||||
AppSchema.index({ scheduledTriggerConfig: 1, scheduledTriggerNextTime: -1 });
|
||||
AppSchema.index(
|
||||
{ scheduledTriggerConfig: 1, scheduledTriggerNextTime: -1 },
|
||||
{
|
||||
partialFilterExpression: {
|
||||
scheduledTriggerConfig: { $exists: true }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const MongoApp = getMongoModel<AppType>(AppCollectionName, AppSchema);
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import {
|
||||
DatasetCollectionTypeEnum,
|
||||
TrainingModeEnum
|
||||
} from '@fastgpt/global/core/dataset/constants';
|
||||
import type { CreateDatasetCollectionParams } from '@fastgpt/global/core/dataset/api.d';
|
||||
import { MongoDatasetCollection } from './schema';
|
||||
import {
|
||||
@@ -24,6 +27,7 @@ import { getLLMModel, getVectorModel } from '../../ai/model';
|
||||
import { pushDataListToTrainingQueue } from '../training/controller';
|
||||
import { MongoImage } from '../../../common/file/image/schema';
|
||||
import { hashStr } from '@fastgpt/global/common/string/tools';
|
||||
import { addDays } from 'date-fns';
|
||||
|
||||
export const createCollectionAndInsertData = async ({
|
||||
dataset,
|
||||
@@ -72,6 +76,17 @@ export const createCollectionAndInsertData = async ({
|
||||
|
||||
hashRawText: hashStr(rawText),
|
||||
rawTextLength: rawText.length,
|
||||
nextSyncTime: (() => {
|
||||
if (!dataset.autoSync) return undefined;
|
||||
if (
|
||||
[DatasetCollectionTypeEnum.link, DatasetCollectionTypeEnum.apiFile].includes(
|
||||
createCollectionParams.type
|
||||
)
|
||||
) {
|
||||
return addDays(new Date(), 1);
|
||||
}
|
||||
return undefined;
|
||||
})(),
|
||||
session
|
||||
});
|
||||
|
||||
@@ -155,10 +170,8 @@ export async function createOneCollection({
|
||||
|
||||
fileId,
|
||||
rawLink,
|
||||
|
||||
externalFileId,
|
||||
externalFileUrl,
|
||||
|
||||
apiFileId,
|
||||
|
||||
hashRawText,
|
||||
@@ -166,7 +179,10 @@ export async function createOneCollection({
|
||||
metadata = {},
|
||||
session,
|
||||
tags,
|
||||
createTime
|
||||
|
||||
createTime,
|
||||
updateTime,
|
||||
nextSyncTime
|
||||
}: CreateOneCollectionParams) {
|
||||
// Create collection tags
|
||||
const collectionTags = await createOrGetCollectionTags({ tags, teamId, datasetId, session });
|
||||
@@ -197,7 +213,10 @@ export async function createOneCollection({
|
||||
rawTextLength,
|
||||
hashRawText,
|
||||
tags: collectionTags,
|
||||
createTime
|
||||
|
||||
createTime,
|
||||
updateTime,
|
||||
nextSyncTime
|
||||
}
|
||||
],
|
||||
{ session }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { connectionMongo, getMongoModel, type Model } from '../../../common/mongo';
|
||||
import { connectionMongo, getMongoModel } from '../../../common/mongo';
|
||||
const { Schema, model, models } = connectionMongo;
|
||||
import { DatasetCollectionSchemaType } from '@fastgpt/global/core/dataset/type.d';
|
||||
import { TrainingTypeMap, DatasetCollectionTypeMap } from '@fastgpt/global/core/dataset/constants';
|
||||
@@ -10,100 +10,95 @@ import {
|
||||
|
||||
export const DatasetColCollectionName = 'dataset_collections';
|
||||
|
||||
const DatasetCollectionSchema = new Schema(
|
||||
{
|
||||
parentId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: DatasetColCollectionName,
|
||||
default: null
|
||||
},
|
||||
teamId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: TeamCollectionName,
|
||||
required: true
|
||||
},
|
||||
tmbId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: TeamMemberCollectionName,
|
||||
required: true
|
||||
},
|
||||
datasetId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: DatasetCollectionName,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
enum: Object.keys(DatasetCollectionTypeMap),
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
createTime: {
|
||||
type: Date,
|
||||
default: () => new Date()
|
||||
},
|
||||
updateTime: {
|
||||
type: Date,
|
||||
default: () => new Date()
|
||||
},
|
||||
forbid: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
// chunk filed
|
||||
trainingType: {
|
||||
type: String,
|
||||
enum: Object.keys(TrainingTypeMap)
|
||||
},
|
||||
chunkSize: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
chunkSplitter: {
|
||||
type: String
|
||||
},
|
||||
qaPrompt: {
|
||||
type: String
|
||||
},
|
||||
ocrParse: Boolean,
|
||||
|
||||
tags: {
|
||||
type: [String],
|
||||
default: []
|
||||
},
|
||||
|
||||
// local file collection
|
||||
fileId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'dataset.files'
|
||||
},
|
||||
// web link collection
|
||||
rawLink: String,
|
||||
// api collection
|
||||
apiFileId: String,
|
||||
// external collection
|
||||
externalFileId: String,
|
||||
externalFileUrl: String, // external import url
|
||||
|
||||
// metadata
|
||||
rawTextLength: Number,
|
||||
hashRawText: String,
|
||||
metadata: {
|
||||
type: Object,
|
||||
default: {}
|
||||
}
|
||||
const DatasetCollectionSchema = new Schema({
|
||||
parentId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: DatasetColCollectionName,
|
||||
default: null
|
||||
},
|
||||
{
|
||||
// Auto update updateTime
|
||||
timestamps: {
|
||||
updatedAt: 'updateTime'
|
||||
}
|
||||
teamId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: TeamCollectionName,
|
||||
required: true
|
||||
},
|
||||
tmbId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: TeamMemberCollectionName,
|
||||
required: true
|
||||
},
|
||||
datasetId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: DatasetCollectionName,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
enum: Object.keys(DatasetCollectionTypeMap),
|
||||
required: true
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
createTime: {
|
||||
type: Date,
|
||||
default: () => new Date()
|
||||
},
|
||||
updateTime: {
|
||||
type: Date,
|
||||
default: () => new Date()
|
||||
},
|
||||
forbid: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
// chunk filed
|
||||
trainingType: {
|
||||
type: String,
|
||||
enum: Object.keys(TrainingTypeMap)
|
||||
},
|
||||
chunkSize: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
chunkSplitter: {
|
||||
type: String
|
||||
},
|
||||
qaPrompt: {
|
||||
type: String
|
||||
},
|
||||
ocrParse: Boolean,
|
||||
|
||||
tags: {
|
||||
type: [String],
|
||||
default: []
|
||||
},
|
||||
|
||||
// local file collection
|
||||
fileId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'dataset.files'
|
||||
},
|
||||
// web link collection
|
||||
rawLink: String,
|
||||
// api collection
|
||||
apiFileId: String,
|
||||
// external collection
|
||||
externalFileId: String,
|
||||
externalFileUrl: String, // external import url
|
||||
|
||||
// next sync time
|
||||
nextSyncTime: Date,
|
||||
|
||||
// metadata
|
||||
rawTextLength: Number,
|
||||
hashRawText: String,
|
||||
metadata: {
|
||||
type: Object,
|
||||
default: {}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
try {
|
||||
// auth file
|
||||
@@ -122,6 +117,16 @@ try {
|
||||
// create time filter
|
||||
DatasetCollectionSchema.index({ teamId: 1, datasetId: 1, createTime: 1 });
|
||||
|
||||
// next sync time filter
|
||||
DatasetCollectionSchema.index(
|
||||
{ type: 1, nextSyncTime: -1 },
|
||||
{
|
||||
partialFilterExpression: {
|
||||
nextSyncTime: { $exists: true }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Get collection by external file id
|
||||
DatasetCollectionSchema.index(
|
||||
{ datasetId: 1, externalFileId: 1 },
|
||||
|
||||
@@ -163,6 +163,10 @@ export const syncCollection = async (collection: CollectionWithDatasetType) => {
|
||||
...sourceReadType
|
||||
});
|
||||
|
||||
if (!rawText) {
|
||||
return DatasetCollectionSyncResultEnum.failed;
|
||||
}
|
||||
|
||||
// Check if the original text is the same: skip if same
|
||||
const hashRawText = hashStr(rawText);
|
||||
if (collection.hashRawText && hashRawText === collection.hashRawText) {
|
||||
@@ -178,28 +182,30 @@ export const syncCollection = async (collection: CollectionWithDatasetType) => {
|
||||
createCollectionParams: {
|
||||
teamId: collection.teamId,
|
||||
tmbId: collection.tmbId,
|
||||
datasetId: collection.datasetId._id,
|
||||
name: collection.name,
|
||||
datasetId: collection.datasetId._id,
|
||||
parentId: collection.parentId,
|
||||
type: collection.type,
|
||||
|
||||
trainingType: collection.trainingType,
|
||||
chunkSize: collection.chunkSize,
|
||||
chunkSplitter: collection.chunkSplitter,
|
||||
qaPrompt: collection.qaPrompt,
|
||||
|
||||
fileId: collection.fileId,
|
||||
rawLink: collection.rawLink,
|
||||
externalFileId: collection.externalFileId,
|
||||
externalFileUrl: collection.externalFileUrl,
|
||||
apiFileId: collection.apiFileId,
|
||||
|
||||
rawTextLength: rawText.length,
|
||||
hashRawText,
|
||||
rawTextLength: rawText.length,
|
||||
|
||||
metadata: collection.metadata,
|
||||
|
||||
tags: collection.tags,
|
||||
createTime: collection.createTime,
|
||||
|
||||
parentId: collection.parentId,
|
||||
trainingType: collection.trainingType,
|
||||
chunkSize: collection.chunkSize,
|
||||
chunkSplitter: collection.chunkSplitter,
|
||||
qaPrompt: collection.qaPrompt,
|
||||
metadata: collection.metadata
|
||||
updateTime: new Date()
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -91,17 +91,7 @@ const DatasetSchema = new Schema({
|
||||
type: Object
|
||||
},
|
||||
|
||||
syncSchedule: {
|
||||
cronString: {
|
||||
type: String
|
||||
},
|
||||
timezone: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
syncNextTime: {
|
||||
type: Date
|
||||
},
|
||||
autoSync: Boolean,
|
||||
|
||||
// abandoned
|
||||
externalReadUrl: {
|
||||
@@ -112,7 +102,6 @@ const DatasetSchema = new Schema({
|
||||
|
||||
try {
|
||||
DatasetSchema.index({ teamId: 1 });
|
||||
DatasetSchema.index({ syncSchedule: 1, syncNextTime: -1 });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
@@ -165,7 +165,8 @@ export async function pushDataListToTrainingQueue({
|
||||
a: item.a,
|
||||
chunkIndex: item.chunkIndex ?? 0,
|
||||
weight: weight ?? 0,
|
||||
indexes: item.indexes
|
||||
indexes: item.indexes,
|
||||
retryCount: 5
|
||||
})),
|
||||
{
|
||||
session,
|
||||
|
||||
@@ -18,6 +18,7 @@ import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
|
||||
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
|
||||
import { loadRequestMessages } from '../../../chat/utils';
|
||||
import { llmCompletionsBodyFormat } from '../../../ai/utils';
|
||||
import { addLog } from '../../../../common/system/log';
|
||||
|
||||
type Props = ModuleDispatchProps<{
|
||||
[NodeInputKeyEnum.aiModel]: string;
|
||||
@@ -65,7 +66,7 @@ export const dispatchClassifyQuestion = async (props: Props): Promise<CQResponse
|
||||
return {
|
||||
[NodeOutputKeyEnum.cqResult]: result.value,
|
||||
[DispatchNodeResponseKeyEnum.skipHandleId]: agents
|
||||
.filter((item) => item.key !== arg?.type)
|
||||
.filter((item) => item.key !== result.key)
|
||||
.map((item) => getHandleId(nodeId, 'source', item.key)),
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: {
|
||||
totalPoints: user.openaiAccount?.key ? 0 : totalPoints,
|
||||
@@ -142,6 +143,10 @@ const completions = async ({
|
||||
agents.find((item) => answer.includes(item.value))?.key ||
|
||||
'';
|
||||
|
||||
if (!id) {
|
||||
addLog.warn('Classify error', { answer });
|
||||
}
|
||||
|
||||
return {
|
||||
tokens: await countMessagesTokens(messages),
|
||||
arg: { type: id }
|
||||
|
||||
@@ -42,7 +42,8 @@ import {
|
||||
filterWorkflowEdges,
|
||||
checkNodeRunStatus,
|
||||
textAdaptGptResponse,
|
||||
replaceEditorVariable
|
||||
replaceEditorVariable,
|
||||
formatVariableValByType
|
||||
} from '@fastgpt/global/core/workflow/runtime/utils';
|
||||
import { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
|
||||
import { dispatchRunTools } from './agent/runTool/index';
|
||||
@@ -72,6 +73,7 @@ import { dispatchLoopEnd } from './loop/runLoopEnd';
|
||||
import { dispatchLoopStart } from './loop/runLoopStart';
|
||||
import { dispatchFormInput } from './interactive/formInput';
|
||||
import { dispatchToolParams } from './agent/runTool/toolParams';
|
||||
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
|
||||
|
||||
const callbackMap: Record<FlowNodeTypeEnum, Function> = {
|
||||
[FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart,
|
||||
@@ -685,7 +687,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
|
||||
}
|
||||
|
||||
/* get system variable */
|
||||
export function getSystemVariable({
|
||||
const getSystemVariable = ({
|
||||
user,
|
||||
runningAppInfo,
|
||||
chatId,
|
||||
@@ -693,7 +695,7 @@ export function getSystemVariable({
|
||||
histories = [],
|
||||
uid,
|
||||
chatConfig
|
||||
}: Props): SystemVariablesType {
|
||||
}: Props): SystemVariablesType => {
|
||||
const variables = chatConfig?.variables || [];
|
||||
const variablesMap = variables.reduce<Record<string, any>>((acc, item) => {
|
||||
acc[item.key] = valueTypeFormat(item.defaultValue, item.valueType);
|
||||
@@ -709,10 +711,10 @@ export function getSystemVariable({
|
||||
histories,
|
||||
cTime: getSystemTime(user.timezone)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/* Merge consecutive text messages into one */
|
||||
export const mergeAssistantResponseAnswerText = (response: AIChatItemValueItemType[]) => {
|
||||
const mergeAssistantResponseAnswerText = (response: AIChatItemValueItemType[]) => {
|
||||
const result: AIChatItemValueItemType[] = [];
|
||||
// 合并连续的text
|
||||
for (let i = 0; i < response.length; i++) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import { ReadFileBaseUrl } from '@fastgpt/global/common/file/constants';
|
||||
import { createFileToken } from '../../../../support/permission/controller';
|
||||
import { JSONPath } from 'jsonpath-plus';
|
||||
import type { SystemPluginSpecialResponse } from '../../../../../plugins/type';
|
||||
import json5 from 'json5';
|
||||
|
||||
type PropsArrType = {
|
||||
key: string;
|
||||
@@ -103,8 +104,6 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
|
||||
[NodeInputKeyEnum.addInputParam]: concatVariables,
|
||||
...concatVariables
|
||||
};
|
||||
httpReqUrl = replaceVariable(httpReqUrl, allVariables);
|
||||
|
||||
const replaceStringVariables = (text: string) => {
|
||||
return replaceVariable(
|
||||
replaceEditorVariable({
|
||||
@@ -116,6 +115,8 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
|
||||
);
|
||||
};
|
||||
|
||||
httpReqUrl = replaceStringVariables(httpReqUrl);
|
||||
|
||||
// parse header
|
||||
const headers = await (() => {
|
||||
try {
|
||||
@@ -175,9 +176,11 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
|
||||
}
|
||||
if (!httpJsonBody) return {};
|
||||
if (httpContentType === ContentTypes.json) {
|
||||
httpJsonBody = replaceVariable(httpJsonBody, allVariables);
|
||||
httpJsonBody = replaceStringVariables(httpJsonBody);
|
||||
// Json body, parse and return
|
||||
const jsonParse = JSON.parse(httpJsonBody);
|
||||
const jsonParse = json5.parse(
|
||||
httpJsonBody.replace(/(".*?")\s*:\s*undefined\b/g, '$1: null')
|
||||
);
|
||||
const removeSignJson = removeUndefinedSign(jsonParse);
|
||||
return removeSignJson;
|
||||
}
|
||||
@@ -195,7 +198,7 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
|
||||
return Object.fromEntries(requestBody);
|
||||
} else if (typeof requestBody === 'string') {
|
||||
try {
|
||||
return JSON.parse(requestBody);
|
||||
return json5.parse(requestBody);
|
||||
} catch {
|
||||
return { content: requestBody };
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function getTmpData<T extends TmpDataEnum>({
|
||||
}).lean()) as TmpDataSchema<TmpDataType<T>> | null;
|
||||
}
|
||||
|
||||
export async function setTmpData<T extends TmpDataEnum>({
|
||||
export function setTmpData<T extends TmpDataEnum>({
|
||||
type,
|
||||
metadata,
|
||||
data
|
||||
@@ -33,7 +33,7 @@ export async function setTmpData<T extends TmpDataEnum>({
|
||||
metadata: TmpDataMetadata<T>;
|
||||
data: TmpDataType<T>;
|
||||
}) {
|
||||
return await MongoTmpData.updateOne(
|
||||
return MongoTmpData.updateOne(
|
||||
{
|
||||
dataId: getDataId(type, metadata)
|
||||
},
|
||||
@@ -43,7 +43,8 @@ export async function setTmpData<T extends TmpDataEnum>({
|
||||
expireAt: addMilliseconds(Date.now(), TmpDataExpireTime[type])
|
||||
},
|
||||
{
|
||||
upsert: true
|
||||
upsert: true,
|
||||
new: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export const iconPaths = {
|
||||
check: () => import('./icons/check.svg'),
|
||||
checkCircle: () => import('./icons/checkCircle.svg'),
|
||||
closeSolid: () => import('./icons/closeSolid.svg'),
|
||||
code: () => import('./icons/code.svg'),
|
||||
collectionLight: () => import('./icons/collectionLight.svg'),
|
||||
collectionSolid: () => import('./icons/collectionSolid.svg'),
|
||||
comment: () => import('./icons/comment.svg'),
|
||||
@@ -58,8 +59,6 @@ export const iconPaths = {
|
||||
'common/monitor': () => import('./icons/common/monitor.svg'),
|
||||
'common/more': () => import('./icons/common/more.svg'),
|
||||
'common/moreFill': () => import('./icons/common/moreFill.svg'),
|
||||
'common/navbar/pluginFill': () => import('./icons/common/navbar/pluginFill.svg'),
|
||||
'common/navbar/pluginLight': () => import('./icons/common/navbar/pluginLight.svg'),
|
||||
'common/openai': () => import('./icons/common/openai.svg'),
|
||||
'common/overviewLight': () => import('./icons/common/overviewLight.svg'),
|
||||
'common/paramsLight': () => import('./icons/common/paramsLight.svg'),
|
||||
@@ -94,9 +93,6 @@ export const iconPaths = {
|
||||
'common/wechatFill': () => import('./icons/common/wechatFill.svg'),
|
||||
configmap: () => import('./icons/configmap.svg'),
|
||||
copy: () => import('./icons/copy.svg'),
|
||||
code: () => import('./icons/code.svg'),
|
||||
preview: () => import('./icons/preview.svg'),
|
||||
fullScreen: () => import('./icons/fullScreen.svg'),
|
||||
'core/app/aiFill': () => import('./icons/core/app/aiFill.svg'),
|
||||
'core/app/aiLight': () => import('./icons/core/app/aiLight.svg'),
|
||||
'core/app/aiLightSmall': () => import('./icons/core/app/aiLightSmall.svg'),
|
||||
@@ -128,7 +124,9 @@ export const iconPaths = {
|
||||
'core/app/type/httpPluginFill': () => import('./icons/core/app/type/httpPluginFill.svg'),
|
||||
'core/app/type/plugin': () => import('./icons/core/app/type/plugin.svg'),
|
||||
'core/app/type/pluginFill': () => import('./icons/core/app/type/pluginFill.svg'),
|
||||
'core/app/type/pluginLight': () => import('./icons/core/app/type/pluginLight.svg'),
|
||||
'core/app/type/simple': () => import('./icons/core/app/type/simple.svg'),
|
||||
'core/app/type/simpleFill': () => import('./icons/core/app/type/simpleFill.svg'),
|
||||
'core/app/type/workflow': () => import('./icons/core/app/type/workflow.svg'),
|
||||
'core/app/type/workflowFill': () => import('./icons/core/app/type/workflowFill.svg'),
|
||||
'core/app/variable/external': () => import('./icons/core/app/variable/external.svg'),
|
||||
@@ -320,6 +318,7 @@ export const iconPaths = {
|
||||
'file/pdf': () => import('./icons/file/pdf.svg'),
|
||||
'file/qaImport': () => import('./icons/file/qaImport.svg'),
|
||||
'file/uploadFile': () => import('./icons/file/uploadFile.svg'),
|
||||
fullScreen: () => import('./icons/fullScreen.svg'),
|
||||
help: () => import('./icons/help.svg'),
|
||||
history: () => import('./icons/history.svg'),
|
||||
infoRounded: () => import('./icons/infoRounded.svg'),
|
||||
@@ -345,6 +344,7 @@ export const iconPaths = {
|
||||
'plugins/doc2x': () => import('./icons/plugins/doc2x.svg'),
|
||||
'plugins/textEditor': () => import('./icons/plugins/textEditor.svg'),
|
||||
point: () => import('./icons/point.svg'),
|
||||
preview: () => import('./icons/preview.svg'),
|
||||
'price/bg': () => import('./icons/price/bg.svg'),
|
||||
'price/right': () => import('./icons/price/right.svg'),
|
||||
save: () => import('./icons/save.svg'),
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none">
|
||||
<path
|
||||
d="M13.9765 20H9.99764V17.9556C9.99764 17.8624 9.98917 17.7693 9.97224 17.6762C9.95531 17.5831 9.92568 17.4942 9.88759 17.4053C9.84949 17.3164 9.80716 17.236 9.75214 17.1598C9.69711 17.0836 9.63785 17.0116 9.57013 16.9439C9.22727 16.6053 8.7278 16.4148 8.20716 16.4233C8.09288 16.4233 7.97859 16.436 7.86431 16.4571C7.75002 16.4783 7.63997 16.5079 7.52991 16.5503C7.41986 16.5926 7.31827 16.6392 7.21669 16.6984C7.1151 16.7577 7.02198 16.8254 6.93732 16.9016C6.70452 17.109 6.42939 17.4603 6.42939 17.9937V20H2.48018C2.39976 20 2.31933 19.9958 2.23891 19.9873C2.15849 19.9788 2.07806 19.9661 1.99764 19.9534C1.91722 19.9365 1.84103 19.9196 1.7606 19.8942C1.68441 19.8688 1.60822 19.8434 1.53203 19.8138C1.45584 19.7841 1.38388 19.746 1.31193 19.7079C1.23997 19.6698 1.17224 19.6275 1.10452 19.581C1.03679 19.5344 0.973301 19.4878 0.909809 19.437C0.846317 19.3862 0.787057 19.3312 0.727798 19.272C0.668539 19.2127 0.617745 19.1534 0.562719 19.0899C0.511925 19.0265 0.461132 18.963 0.418804 18.8952C0.372243 18.8275 0.329915 18.7598 0.291819 18.6878C0.253724 18.6159 0.219862 18.5439 0.185999 18.4677C0.15637 18.3915 0.12674 18.3153 0.105576 18.2392C0.0801791 18.163 0.0632479 18.0825 0.0463167 18.0021C0.0293855 17.9217 0.0166871 17.8413 0.0124543 17.7608C0.00398871 17.6804 -0.000244141 17.6 -0.000244141 17.5196V13.5746H2.03997C2.45055 13.5746 2.84843 13.4011 3.16166 13.0836C3.25055 12.9947 3.32674 12.9016 3.39446 12.7958C3.46219 12.6942 3.52145 12.5841 3.56801 12.4698C3.61457 12.3556 3.64843 12.237 3.67383 12.1143C3.69923 11.9915 3.70769 11.8688 3.70769 11.746C3.69076 10.7979 2.91193 9.99788 2.00187 9.99788H-0.000244141V6.01058C-0.000244141 5.84974 0.0166871 5.68889 0.0463167 5.52804C0.0801792 5.3672 0.12674 5.21481 0.190232 5.06667C0.253724 4.91852 0.329915 4.7746 0.423036 4.64339C0.516158 4.50794 0.621978 4.38519 0.736264 4.27513C0.85055 4.16508 0.977534 4.06349 1.11298 3.9746C1.24843 3.88571 1.39235 3.81376 1.5405 3.7545C1.68864 3.69524 1.84526 3.64868 2.00187 3.61905C2.15849 3.58942 2.31933 3.57249 2.48018 3.57672H5.30346V2.92487C5.30346 2.87831 5.30346 2.82751 5.30769 2.78095C5.31192 2.73439 5.31192 2.6836 5.32039 2.63704C5.32462 2.59048 5.33309 2.53968 5.33732 2.49312C5.34579 2.44656 5.35425 2.39577 5.36272 2.34921C5.37118 2.30265 5.38388 2.25608 5.39658 2.20952L5.43468 2.06984C5.44737 2.02328 5.46431 1.97672 5.48124 1.93016C5.49817 1.8836 5.5151 1.84127 5.53203 1.79471C5.54896 1.74815 5.57013 1.70582 5.59129 1.66349C5.61245 1.62116 5.63362 1.5746 5.65901 1.53228C5.68018 1.48995 5.70558 1.44762 5.73097 1.40529C5.75637 1.36296 5.78177 1.32487 5.8114 1.28254C5.83679 1.24444 5.86642 1.20212 5.89605 1.16402L5.98494 1.04974C6.01457 1.01164 6.04843 0.977778 6.0823 0.939683C6.11616 0.90582 6.15002 0.867725 6.18388 0.833862C6.21774 0.8 6.25584 0.766138 6.2897 0.736508C6.3278 0.702646 6.36166 0.673016 6.39976 0.643386L6.51404 0.554497C6.55214 0.524868 6.59446 0.499471 6.63256 0.474074C6.67065 0.448677 6.71298 0.42328 6.75531 0.397884C6.79764 0.372487 6.83997 0.351323 6.8823 0.325926C6.92462 0.304762 6.96695 0.283598 7.01351 0.262434C7.05584 0.24127 7.1024 0.224339 7.14896 0.207407C7.19552 0.190476 7.23785 0.173545 7.28441 0.156614C7.33097 0.139683 7.37753 0.126984 7.42409 0.114286L7.56378 0.0761905C7.61034 0.0634921 7.6569 0.0550265 7.70769 0.0465609C7.75849 0.0380953 7.80505 0.0296296 7.85161 0.0253968C7.89817 0.021164 7.94896 0.0126984 7.99552 0.00846564C8.04208 0.00423284 8.09288 0 8.13944 0H8.28335C9.86642 0.0296296 11.1574 1.3545 11.1574 2.96296V3.57249H13.9765C14.0569 3.57249 14.1373 3.57672 14.2177 3.58095C14.2982 3.58942 14.3786 3.59788 14.4548 3.61481C14.5352 3.63175 14.6114 3.64868 14.6876 3.66984C14.7638 3.691 14.84 3.72064 14.9162 3.75026C14.9923 3.77989 15.0643 3.81376 15.1363 3.85185C15.2082 3.88995 15.2759 3.93227 15.3437 3.9746C15.4114 4.01693 15.4749 4.06772 15.5384 4.11852C15.6019 4.16931 15.6611 4.22434 15.7162 4.27937C15.7712 4.33439 15.8262 4.39788 15.877 4.45714C15.9278 4.52064 15.9744 4.58413 16.0209 4.65185C16.0675 4.71958 16.1056 4.7873 16.1437 4.85926C16.1818 4.93122 16.2156 5.00317 16.2453 5.07936C16.2749 5.15556 16.3003 5.23175 16.3257 5.30794C16.3468 5.38413 16.368 5.46455 16.3807 5.54074C16.3976 5.62116 16.4061 5.69735 16.4146 5.77778C16.423 5.8582 16.423 5.93862 16.423 6.01905V8.84233H17.0326C18.6664 8.84233 19.9955 10.1376 19.9955 11.7291C19.9955 13.363 18.6834 14.6921 17.0707 14.6921H16.423V17.5153C16.4273 18.8825 15.3267 20 13.9765 20Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="20" height="20" transform="translate(0.5 0.5)" fill="url(#paint0_linear_5846_2933)"/>
|
||||
<path d="M9.96659 4.38986C10.2456 4.22877 10.6979 4.22877 10.9769 4.38986L15.2403 6.85129C15.7983 7.17346 15.7983 7.6958 15.2403 8.01796L11.0331 10.447C10.7541 10.6081 10.3017 10.6081 10.0227 10.447L5.75936 7.98557C5.20135 7.6634 5.20135 7.14107 5.75935 6.8189L9.96659 4.38986Z" fill="white"/>
|
||||
<path d="M4.9906 9.46161C4.9906 9.01057 5.30725 8.82776 5.69785 9.05327L9.6388 11.3286C9.862 11.4574 10.0429 11.7708 10.0429 12.0286V16.1949C10.0429 16.6459 9.7263 16.8287 9.33569 16.6032L5.39475 14.3279C5.17154 14.1991 4.9906 13.8857 4.9906 13.6279V9.46161Z" fill="white"/>
|
||||
<path d="M10.9563 12.06C10.9563 11.8023 11.1372 11.4889 11.3604 11.36L15.302 9.08435C15.6926 8.85883 16.0093 9.04165 16.0093 9.49268V13.6585C16.0093 13.9162 15.8283 14.2296 15.6051 14.3585L11.6635 16.6342C11.2729 16.8597 10.9563 16.6769 10.9563 16.2258V12.06Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_5846_2933" x1="10" y1="0" x2="3.05556" y2="18.3333" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#67BFFF"/>
|
||||
<stop offset="1" stop-color="#5BA6FF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -1,4 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M17.0801 5.93836H17.0764C18.9297 5.93842 19.8659 5.94335 20.5834 6.30893C21.2232 6.6349 21.7433 7.15504 22.0693 7.79479C22.4399 8.52209 22.4399 9.47418 22.4399 11.3784V16.56C22.4399 18.4641 22.4399 19.4162 22.0693 20.1435C21.7433 20.7833 21.2232 21.3034 20.5834 21.6294C19.8561 22 18.904 22 16.9999 22H7.00005C5.09588 22 4.14379 22 3.41649 21.6294C2.77674 21.3034 2.25661 20.7833 1.93064 20.1435C1.56006 19.4162 1.56006 18.4641 1.56006 16.56V11.3784C1.56006 9.47418 1.56006 8.52209 1.93064 7.79479C2.25661 7.15504 2.77674 6.6349 3.41649 6.30893C4.14379 5.93835 5.09588 5.93835 7.00006 5.93835H7.15448V5.59988C7.15448 3.61165 8.76625 1.99988 10.7545 1.99988H13.4801C15.4684 1.99988 17.0801 3.61165 17.0801 5.59988V5.93836ZM10.7545 3.99988H13.4801C14.3638 3.99988 15.0801 4.71622 15.0801 5.59988V5.93835H9.15448V5.59988C9.15448 4.71622 9.87082 3.99988 10.7545 3.99988ZM16.9999 7.93835H7.00006C6.01497 7.93835 5.39643 7.93991 4.92981 7.97803C4.48653 8.01425 4.35919 8.07326 4.32447 8.09095C4.06104 8.22517 3.84687 8.43934 3.71265 8.70277C3.69496 8.73748 3.63596 8.86483 3.59974 9.30811C3.57866 9.5661 3.56876 9.87054 3.56412 10.2577H20.4358C20.4312 9.87054 20.4213 9.5661 20.4002 9.3081C20.364 8.86483 20.305 8.73748 20.2873 8.70277C20.153 8.43934 19.9389 8.22517 19.6754 8.09095C19.6407 8.07326 19.5134 8.01425 19.0701 7.97803C18.6035 7.93991 17.9849 7.93835 16.9999 7.93835ZM20.4399 12.0577H14.6355V15.1893C14.6355 15.5206 14.3669 15.7893 14.0355 15.7893H9.96433C9.63296 15.7893 9.36433 15.5206 9.36433 15.1893V12.0577H3.56006V16.56C3.56006 17.5451 3.56161 18.1636 3.59974 18.6302C3.63596 19.0735 3.69496 19.2008 3.71265 19.2356C3.84687 19.499 4.06104 19.7132 4.32447 19.8474C4.35919 19.8651 4.48653 19.9241 4.92981 19.9603C5.39643 19.9984 6.01496 20 7.00005 20H16.9999C17.9849 20 18.6035 19.9984 19.0701 19.9603C19.5134 19.9241 19.6407 19.8651 19.6754 19.8474C19.9389 19.7132 20.153 19.499 20.2873 19.2356C20.305 19.2008 20.364 19.0735 20.4002 18.6302C20.4383 18.1636 20.4399 17.5451 20.4399 16.56V12.0577ZM12.8355 12.0577H11.1643V13.9893H12.8355V12.0577Z" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" >
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.2334 4.94869H14.2303C15.7747 4.94875 16.5549 4.95285 17.1528 5.2575C17.6859 5.52915 18.1194 5.96259 18.391 6.49572C18.6998 7.1018 18.6998 7.89521 18.6998 9.48202V13.8C18.6998 15.3869 18.6998 16.1803 18.391 16.7863C18.1194 17.3195 17.6859 17.7529 17.1528 18.0246C16.5467 18.3334 15.7533 18.3334 14.1665 18.3334H5.83332C4.2465 18.3334 3.4531 18.3334 2.84701 18.0246C2.31389 17.7529 1.88044 17.3195 1.6088 16.7863C1.29999 16.1803 1.29999 15.3869 1.29999 13.8V9.48202C1.29999 7.89521 1.29999 7.1018 1.6088 6.49572C1.88044 5.96259 2.31389 5.52915 2.84701 5.2575C3.4531 4.94869 4.24651 4.94869 5.83332 4.94869H5.96201V4.66663C5.96201 3.00977 7.30515 1.66663 8.96201 1.66663H11.2334C12.8902 1.66663 14.2334 3.00977 14.2334 4.66663V4.94869ZM8.96201 3.33329H11.2334C11.9698 3.33329 12.5667 3.93025 12.5667 4.66663V4.94869H7.62867V4.66663C7.62867 3.93025 8.22563 3.33329 8.96201 3.33329ZM14.1665 6.61536H5.83332C5.01241 6.61536 4.49697 6.61665 4.10811 6.64842C3.73872 6.6786 3.63259 6.72778 3.60367 6.74252C3.38414 6.85437 3.20567 7.03285 3.09381 7.25237C3.07907 7.2813 3.0299 7.38742 2.99972 7.75682C2.98216 7.97181 2.97391 8.22551 2.97004 8.54814H17.0298C17.0259 8.22551 17.0177 7.97181 17.0001 7.75682C16.9699 7.38742 16.9207 7.2813 16.906 7.25237C16.7941 7.03285 16.6157 6.85437 16.3961 6.74252C16.3672 6.72778 16.2611 6.6786 15.8917 6.64842C15.5028 6.61665 14.9874 6.61536 14.1665 6.61536ZM17.0332 10.0481H12.1962V12.6578C12.1962 12.9339 11.9723 13.1578 11.6962 13.1578H8.30355C8.02741 13.1578 7.80355 12.9339 7.80355 12.6578V10.0481H2.96665V13.8C2.96665 14.6209 2.96795 15.1364 2.99972 15.5252C3.0299 15.8946 3.07907 16.0008 3.09381 16.0297C3.20567 16.2492 3.38414 16.4277 3.60367 16.5395C3.63259 16.5543 3.73872 16.6035 4.10811 16.6336C4.49696 16.6654 5.01241 16.6667 5.83332 16.6667H14.1665C14.9874 16.6667 15.5028 16.6654 15.8917 16.6336C16.2611 16.6035 16.3672 16.5543 16.3961 16.5395C16.6157 16.4277 16.7941 16.2492 16.906 16.0297C16.9207 16.0008 16.9699 15.8946 17.0001 15.5252C17.0319 15.1364 17.0332 14.6209 17.0332 13.8V10.0481ZM10.6962 10.0481H9.30355V11.6578H10.6962V10.0481Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -42,7 +42,7 @@ export const cronString2Fields = (cronString?: string) => {
|
||||
};
|
||||
|
||||
export const cronString2Label = (
|
||||
cronString: string,
|
||||
cronString = '',
|
||||
t: any // i18nT
|
||||
) => {
|
||||
const cronField = cronString2Fields(cronString);
|
||||
|
||||
@@ -104,7 +104,7 @@ export const MultipleRowSelect = ({
|
||||
);
|
||||
|
||||
const onOpenSelect = useCallback(() => {
|
||||
setCloneValue(value);
|
||||
setCloneValue(Array.isArray(value) ? value : []);
|
||||
onOpen();
|
||||
}, [value, onOpen]);
|
||||
|
||||
@@ -193,6 +193,18 @@ export const MultipleRowArraySelect = ({
|
||||
ref: ref,
|
||||
handler: onClose
|
||||
});
|
||||
const onChange = useCallback(
|
||||
(val: any[][]) => {
|
||||
// Filter invalid value
|
||||
const validList = val.filter((item) => {
|
||||
const listItem = list.find((v) => v.value === item[0]);
|
||||
if (!listItem) return false;
|
||||
return listItem.children?.some((v) => v.value === item[1]);
|
||||
});
|
||||
onSelect(validList);
|
||||
},
|
||||
[onSelect]
|
||||
);
|
||||
|
||||
const RenderList = useCallback(
|
||||
({ index, list }: { index: number; list: MultipleSelectProps['list'] }) => {
|
||||
@@ -213,9 +225,9 @@ export const MultipleRowArraySelect = ({
|
||||
const newValue = [parentValue, item.value];
|
||||
|
||||
if (newValues.some((v) => v[0] === parentValue && v[1] === item.value)) {
|
||||
onSelect(newValues.filter((v) => !(v[0] === parentValue && v[1] === item.value)));
|
||||
onChange(newValues.filter((v) => !(v[0] === parentValue && v[1] === item.value)));
|
||||
} else {
|
||||
onSelect([...newValues, newValue]);
|
||||
onChange([...newValues, newValue]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -74,7 +74,7 @@ export const systemPluginTemplateList: TGroupType[] = [
|
||||
];
|
||||
export const defaultGroup: PluginGroupSchemaType = {
|
||||
groupId: 'systemPlugin',
|
||||
groupAvatar: 'common/navbar/pluginLight',
|
||||
groupAvatar: 'core/app/type/pluginLight',
|
||||
groupName: i18nT('common:core.module.template.System Plugin'),
|
||||
groupOrder: 0,
|
||||
groupTypes: systemPluginTemplateList
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
import Cookies, { CookieAttributes } from 'js-cookie';
|
||||
import Cookies from 'js-cookie';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { LangEnum } from '../../../projects/app/src/web/common/utils/i18n';
|
||||
|
||||
const setCookie = (key: string, value: string, options?: CookieAttributes) => {
|
||||
Cookies.set(key, value, options);
|
||||
};
|
||||
const getCookie = (key: string) => {
|
||||
return Cookies.get(key);
|
||||
};
|
||||
|
||||
const LANG_KEY = 'NEXT_LOCALE';
|
||||
const isInIframe = () => {
|
||||
try {
|
||||
return window.self !== window.top;
|
||||
} catch (e) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
const setLang = (value: string) => {
|
||||
if (isInIframe()) {
|
||||
localStorage.setItem(LANG_KEY, value);
|
||||
} else {
|
||||
// 不在 iframe 中,同时使用 Cookie 和 localStorage
|
||||
Cookies.set(LANG_KEY, value, { expires: 30 });
|
||||
localStorage.setItem(LANG_KEY, value);
|
||||
}
|
||||
};
|
||||
const getLang = () => {
|
||||
return localStorage.getItem(LANG_KEY) || Cookies.get(LANG_KEY);
|
||||
};
|
||||
|
||||
export const useI18nLng = () => {
|
||||
const { i18n } = useTranslation();
|
||||
@@ -26,23 +38,21 @@ export const useI18nLng = () => {
|
||||
|
||||
const onChangeLng = async (lng: string) => {
|
||||
const lang = languageMap[lng] || 'en';
|
||||
const prevLang = getLang();
|
||||
|
||||
setCookie(LANG_KEY, lang, {
|
||||
expires: 30
|
||||
});
|
||||
|
||||
const currentLng = i18n?.language;
|
||||
if (!currentLng) return;
|
||||
setLang(lang);
|
||||
|
||||
await i18n?.changeLanguage?.(lang);
|
||||
if (currentLng !== lang) {
|
||||
|
||||
if (!i18n.hasResourceBundle(lang, 'common') && prevLang !== lang) {
|
||||
window?.location?.reload?.();
|
||||
}
|
||||
};
|
||||
|
||||
const setUserDefaultLng = () => {
|
||||
const setUserDefaultLng = (forceGetDefaultLng: boolean = false) => {
|
||||
if (!navigator || !localStorage) return;
|
||||
if (getCookie(LANG_KEY)) return onChangeLng(getCookie(LANG_KEY) as string);
|
||||
|
||||
if (getLang() && !forceGetDefaultLng) return onChangeLng(getLang() as string);
|
||||
|
||||
const lang = languageMap[navigator.language] || 'en';
|
||||
|
||||
|
||||
@@ -43,9 +43,10 @@
|
||||
"select_file": "Upload File",
|
||||
"select_file_img": "Upload file / image",
|
||||
"select_img": "Upload Image",
|
||||
"source_cronJob": "Scheduled execution",
|
||||
"stream_output": "Stream Output",
|
||||
"unsupported_file_type": "Unsupported file types",
|
||||
"upload": "Upload",
|
||||
"view_citations": "View References",
|
||||
"web_site_sync": "Web Site Sync"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -751,7 +751,6 @@
|
||||
"core.module.template.config_params": "Can configure application system parameters",
|
||||
"core.module.template.empty_plugin": "Blank plugin",
|
||||
"core.module.template.empty_workflow": "Blank workflow",
|
||||
"core.module.template.http body placeholder": "Same syntax as Apifox",
|
||||
"core.module.template.self_input": "Plug-in input",
|
||||
"core.module.template.self_output": "Custom plug-in output",
|
||||
"core.module.template.system_config": "System configuration",
|
||||
@@ -875,6 +874,7 @@
|
||||
"error.fileNotFound": "File not found~",
|
||||
"error.inheritPermissionError": "Inherit permission Error",
|
||||
"error.missingParams": "Insufficient parameters",
|
||||
"error.too_many_request": "Too many request",
|
||||
"error.upload_file_error_filename": "{{name}} Upload Failed",
|
||||
"error.username_empty": "Account cannot be empty",
|
||||
"extraction_results": "Extraction Results",
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
"add_file": "Import",
|
||||
"api_file": "API Dataset",
|
||||
"api_url": "API Url",
|
||||
"chunk_max_tokens": "max_tokens",
|
||||
"close_auto_sync": "Are you sure you want to turn off automatic sync?",
|
||||
"collection.Create update time": "Creation/Update Time",
|
||||
"collection.Training type": "Training",
|
||||
"collection_not_support_retraining": "This collection type does not support retuning parameters",
|
||||
@@ -12,6 +14,7 @@
|
||||
"collection_tags": "Collection Tags",
|
||||
"common_dataset": "General Dataset",
|
||||
"common_dataset_desc": "Build a Dataset by importing files, web links, or manual input.",
|
||||
"config_sync_schedule": "Configure scheduled synchronization",
|
||||
"confirm_to_rebuild_embedding_tip": "Are you sure you want to switch the index for the Dataset?\nSwitching the index is a significant operation that requires re-indexing all data in your Dataset, which may take a long time. Please ensure your account has sufficient remaining points.\n\nAdditionally, you need to update the applications that use this Dataset to avoid conflicts with other indexed model Datasets.",
|
||||
"core.dataset.import.Adjust parameters": "Adjust parameters",
|
||||
"custom_data_process_params": "Custom",
|
||||
@@ -36,7 +39,9 @@
|
||||
"ideal_chunk_length_tips": "Segment according to the end symbol and combine multiple segments into one block. This value determines the estimated size of the block, if there is any fluctuation.",
|
||||
"import.Auto mode Estimated Price Tips": "The text understanding model needs to be called, which requires more points: {{price}} points/1K tokens",
|
||||
"import.Embedding Estimated Price Tips": "Only use the index model and consume a small amount of AI points: {{price}} points/1K tokens",
|
||||
"is_open_schedule": "Enable scheduled synchronization",
|
||||
"move.hint": "After moving, the selected knowledge base/folder will inherit the permission settings of the new folder, and the original permission settings will become invalid.",
|
||||
"open_auto_sync": "After scheduled synchronization is turned on, the system will try to synchronize the collection from time to time every day. During the collection synchronization period, the collection data will not be searched.",
|
||||
"permission.des.manage": "Can manage the entire knowledge base data and information",
|
||||
"permission.des.read": "View knowledge base content",
|
||||
"permission.des.write": "Ability to add and change knowledge base content",
|
||||
@@ -47,6 +52,9 @@
|
||||
"retrain_task_submitted": "The retraining task has been submitted",
|
||||
"same_api_collection": "The same API set exists",
|
||||
"start_sync_website_tip": "Confirm to start synchronizing data? \nThe old data will be deleted and retrieved again, please confirm!",
|
||||
"sync_collection_failed": "Synchronization collection error, please check whether the source file can be accessed normally",
|
||||
"sync_schedule": "Timing synchronization",
|
||||
"sync_schedule_tip": "Only existing collections will be synchronized. \nIncludes linked collections and all collections in the API knowledge base. \nThe system will poll for updates every day, and the specific update time cannot be determined.",
|
||||
"tag.Add New": "Add New",
|
||||
"tag.Add_new_tag": "Add New Tag",
|
||||
"tag.Edit_tag": "Edit Tag",
|
||||
@@ -59,6 +67,7 @@
|
||||
"tag.total_tags": "Total {{total}} tags",
|
||||
"the_knowledge_base_has_indexes_that_are_being_trained_or_being_rebuilt": "The Dataset has indexes that are being trained or rebuilt",
|
||||
"training_mode": "Chunk mode",
|
||||
"vector_model_max_tokens_tip": "Each chunk of data has a maximum length of 3000 tokens",
|
||||
"website_dataset": "Website Sync",
|
||||
"website_dataset_desc": "Website sync allows you to build a Dataset directly using a web link."
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
"full_response_data": "Full Response Data",
|
||||
"greater_than": "Greater Than",
|
||||
"greater_than_or_equal_to": "Greater Than or Equal To",
|
||||
"http_body_placeholder": "Similar syntax to APIFox, variable selection can be activated via /. \nString variables need to be enclosed in double quotes, other types of variables do not need to be enclosed in double quotes. \nPlease strictly check whether it conforms to JSON format.",
|
||||
"http_extract_output": "Output field extraction",
|
||||
"http_extract_output_description": "Specified fields in the response value can be extracted through JSONPath syntax",
|
||||
"http_raw_response_description": "Raw HTTP response. Only accepts string or JSON type response data.",
|
||||
@@ -193,4 +194,4 @@
|
||||
"workflow.Switch_success": "Switch Successful",
|
||||
"workflow.Team cloud": "Team Cloud",
|
||||
"workflow.exit_tips": "Your changes have not been saved. 'Exit directly' will not save your edits."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,9 +43,10 @@
|
||||
"select_file": "上传文件",
|
||||
"select_file_img": "上传文件/图片",
|
||||
"select_img": "上传图片",
|
||||
"source_cronJob": "定时执行",
|
||||
"stream_output": "流输出",
|
||||
"unsupported_file_type": "不支持的文件类型",
|
||||
"upload": "上传",
|
||||
"view_citations": "查看引用",
|
||||
"web_site_sync": "Web站点同步"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -750,7 +750,6 @@
|
||||
"core.module.template.config_params": "可以配置应用的系统参数",
|
||||
"core.module.template.empty_plugin": "空白插件",
|
||||
"core.module.template.empty_workflow": "空白工作流",
|
||||
"core.module.template.http body placeholder": "与 Apifox 相同的语法",
|
||||
"core.module.template.self_input": "插件输入",
|
||||
"core.module.template.self_output": "插件输出",
|
||||
"core.module.template.system_config": "系统配置",
|
||||
@@ -874,6 +873,7 @@
|
||||
"error.fileNotFound": "文件找不到了~",
|
||||
"error.inheritPermissionError": "权限继承错误",
|
||||
"error.missingParams": "参数缺失",
|
||||
"error.too_many_request": "请求太频繁了,请稍后重试",
|
||||
"error.upload_file_error_filename": "{{name}} 上传失败",
|
||||
"error.username_empty": "账号不能为空",
|
||||
"extraction_results": "提取结果",
|
||||
@@ -1180,7 +1180,6 @@
|
||||
"user.password_message": "密码最少 4 位最多 60 位",
|
||||
"user.team.Balance": "团队余额",
|
||||
"user.team.Check Team": "切换",
|
||||
|
||||
"user.team.Invite Member": "邀请成员",
|
||||
"user.team.Invite Member Tips": "对方可查阅或使用团队内的其他资源",
|
||||
"user.team.Leave Team": "离开团队",
|
||||
@@ -1193,13 +1192,10 @@
|
||||
"user.team.Processing invitations Tips": "你有 {{amount}} 个需要处理的团队邀请",
|
||||
"user.team.Remove Member Confirm Tip": "确认将 {{username}} 移出团队?",
|
||||
"user.team.Select Team": "团队选择",
|
||||
|
||||
"user.team.Switch Team Failed": "切换团队异常",
|
||||
"user.team.Tags Async": "保存",
|
||||
|
||||
"user.team.Team Tags Async": "标签同步",
|
||||
"user.team.Team Tags Async Success": "链接报错成功,标签信息更新",
|
||||
|
||||
"user.team.invite.Accept Confirm": "确认加入该团队?",
|
||||
"user.team.invite.Accepted": "已加入团队",
|
||||
"user.team.invite.Deal Width Footer Tip": "处理完会自动关闭噢~",
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
"add_file": "添加文件",
|
||||
"api_file": "API 文件库",
|
||||
"api_url": "接口地址",
|
||||
"chunk_max_tokens": "分块上限",
|
||||
"close_auto_sync": "确认关闭自动同步功能?",
|
||||
"collection.Create update time": "创建/更新时间",
|
||||
"collection.Training type": "训练模式",
|
||||
"collection_not_support_retraining": "该集合类型不支持重新调整参数",
|
||||
@@ -12,6 +14,7 @@
|
||||
"collection_tags": "集合标签",
|
||||
"common_dataset": "通用知识库",
|
||||
"common_dataset_desc": "可通过导入文件、网页链接或手动录入形式构建知识库",
|
||||
"config_sync_schedule": "配置定时同步",
|
||||
"confirm_to_rebuild_embedding_tip": "确认为知识库切换索引?\n切换索引是一个非常重量的操作,需要对您知识库内所有数据进行重新索引,时间可能较长,请确保账号内剩余积分充足。\n\n此外,你还需要注意修改选择该知识库的应用,避免它们与其他索引模型知识库混用。",
|
||||
"core.dataset.import.Adjust parameters": "调整参数",
|
||||
"custom_data_process_params": "自定义",
|
||||
@@ -36,7 +39,9 @@
|
||||
"ideal_chunk_length_tips": "按结束符号进行分段,并将多个分段组成一个分块,该值决定了分块的预估大小,如果会有上下浮动。",
|
||||
"import.Auto mode Estimated Price Tips": "需调用文本理解模型,需要消耗较多AI 积分:{{price}} 积分/1K tokens",
|
||||
"import.Embedding Estimated Price Tips": "仅使用索引模型,消耗少量 AI 积分:{{price}} 积分/1K tokens",
|
||||
"is_open_schedule": "启用定时同步",
|
||||
"move.hint": "移动后,所选知识库/文件夹将继承新文件夹的权限设置,原先的权限设置失效。",
|
||||
"open_auto_sync": "开启定时同步后,系统将会每天不定时尝试同步集合,集合同步期间,会出现无法搜索到该集合数据现象。",
|
||||
"permission.des.manage": "可管理整个知识库数据和信息",
|
||||
"permission.des.read": "可查看知识库内容",
|
||||
"permission.des.write": "可增加和变更知识库内容",
|
||||
@@ -47,6 +52,9 @@
|
||||
"retrain_task_submitted": "重新训练任务已提交",
|
||||
"same_api_collection": "存在相同的 API 集合",
|
||||
"start_sync_website_tip": "确认开始同步数据?将会删除旧数据后重新获取,请确认!",
|
||||
"sync_collection_failed": "同步集合错误,请检查是否能正常访问源文件",
|
||||
"sync_schedule": "定时同步",
|
||||
"sync_schedule_tip": "仅会同步已存在的集合。包括链接集合以及 API 知识库里所有集合。系统会每天进行轮询更新,无法确定具体的更新时间。",
|
||||
"tag.Add New": "新建",
|
||||
"tag.Add_new_tag": "新建标签",
|
||||
"tag.Edit_tag": "编辑标签",
|
||||
@@ -59,6 +67,7 @@
|
||||
"tag.total_tags": "共{{total}}个标签",
|
||||
"the_knowledge_base_has_indexes_that_are_being_trained_or_being_rebuilt": "知识库有训练中或正在重建的索引",
|
||||
"training_mode": "处理方式",
|
||||
"vector_model_max_tokens_tip": "每个分块数据,最大长度为 3000 tokens",
|
||||
"website_dataset": "Web 站点同步",
|
||||
"website_dataset_desc": "Web 站点同步允许你直接使用一个网页链接构建知识库"
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"Variable_name": "变量名",
|
||||
"add_new_input": "新增输入",
|
||||
"add_new_output": "新增输出",
|
||||
"http_body_placeholder": "与 APIFox 类似的语法,可通过 / 来激活变量选择。字符串变量均需加双引号,其他类型变量无需加双引号。请严格检查是否符合 JSON 格式。",
|
||||
"append_application_reply_to_history_as_new_context": "将该应用回复内容拼接到历史记录中,作为新的上下文返回",
|
||||
"application_call": "应用调用",
|
||||
"assigned_reply": "指定回复",
|
||||
@@ -193,4 +194,4 @@
|
||||
"workflow.Switch_success": "切换成功",
|
||||
"workflow.Team cloud": "团队云端",
|
||||
"workflow.exit_tips": "您的更改尚未保存,「直接退出」将不会保存您的编辑记录。"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,9 +43,10 @@
|
||||
"select_file": "上傳檔案",
|
||||
"select_file_img": "上傳檔案 / 圖片",
|
||||
"select_img": "上傳圖片",
|
||||
"source_cronJob": "定時執行",
|
||||
"stream_output": "串流輸出",
|
||||
"unsupported_file_type": "不支援的檔案類型",
|
||||
"upload": "上傳",
|
||||
"view_citations": "檢視引用",
|
||||
"web_site_sync": "網站同步"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -751,7 +751,6 @@
|
||||
"core.module.template.config_params": "可以設定應用程式的系統參數",
|
||||
"core.module.template.empty_plugin": "空白外掛程式",
|
||||
"core.module.template.empty_workflow": "空白工作流程",
|
||||
"core.module.template.http body placeholder": "與 Apifox 相同的語法",
|
||||
"core.module.template.self_input": "外掛程式輸入",
|
||||
"core.module.template.self_output": "外掛程式輸出",
|
||||
"core.module.template.system_config": "系統設定",
|
||||
@@ -874,6 +873,7 @@
|
||||
"error.fileNotFound": "找不到檔案",
|
||||
"error.inheritPermissionError": "繼承權限錯誤",
|
||||
"error.missingParams": "參數不足",
|
||||
"error.too_many_request": "請求太頻繁了,請稍後重試",
|
||||
"error.upload_file_error_filename": "{{name}} 上傳失敗",
|
||||
"error.username_empty": "帳號不能為空",
|
||||
"extraction_results": "提取結果",
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
"add_file": "新增文件",
|
||||
"api_file": "API 檔案庫",
|
||||
"api_url": "介面位址",
|
||||
"chunk_max_tokens": "分塊上限",
|
||||
"close_auto_sync": "確認關閉自動同步功能?",
|
||||
"collection.Create update time": "建立/更新時間",
|
||||
"collection.Training type": "分段模式",
|
||||
"collection_not_support_retraining": "此集合類型不支援重新調整參數",
|
||||
@@ -12,6 +14,7 @@
|
||||
"collection_tags": "集合標籤",
|
||||
"common_dataset": "通用資料集",
|
||||
"common_dataset_desc": "可透過匯入檔案、網頁連結或手動輸入的方式建立資料集",
|
||||
"config_sync_schedule": "配置定時同步",
|
||||
"confirm_to_rebuild_embedding_tip": "確定要為資料集切換索引嗎?\n切換索引是一個重要的操作,需要對您資料集內所有資料重新建立索引,可能需要較長時間,請確保帳號內剩餘點數充足。\n\n此外,您還需要注意修改使用此資料集的應用程式,避免與其他索引模型資料集混用。",
|
||||
"core.dataset.import.Adjust parameters": "調整參數",
|
||||
"custom_data_process_params": "自訂",
|
||||
@@ -36,7 +39,9 @@
|
||||
"ideal_chunk_length_tips": "依結束符號進行分段,並將多個分段組成一個分塊,此值決定了分塊的預估大小,可能會有上下浮動。",
|
||||
"import.Auto mode Estimated Price Tips": "需呼叫文字理解模型,將消耗較多 AI 點數:{{price}} 點數 / 1K tokens",
|
||||
"import.Embedding Estimated Price Tips": "僅使用索引模型,消耗少量 AI 點數:{{price}} 點數 / 1K tokens",
|
||||
"is_open_schedule": "啟用定時同步",
|
||||
"move.hint": "移動後,所選資料集/資料夾將繼承新資料夾的權限設定,原先的權限設定將失效。",
|
||||
"open_auto_sync": "開啟定時同步後,系統將每天不定時嘗試同步集合,集合同步期間,會出現無法搜尋到該集合資料現象。",
|
||||
"permission.des.manage": "可管理整個資料集的資料和資訊",
|
||||
"permission.des.read": "可檢視資料集內容",
|
||||
"permission.des.write": "可新增和變更資料集內容",
|
||||
@@ -47,6 +52,9 @@
|
||||
"retrain_task_submitted": "重新訓練任務已提交",
|
||||
"same_api_collection": "存在相同的 API 集合",
|
||||
"start_sync_website_tip": "確認開始同步資料?\n將會刪除舊資料後重新獲取,請確認!",
|
||||
"sync_collection_failed": "同步集合錯誤,請檢查是否能正常存取來源文件",
|
||||
"sync_schedule": "定時同步",
|
||||
"sync_schedule_tip": "只會同步已存在的集合。\n包括連結集合以及 API 知識庫裡所有集合。\n系統會每天進行輪詢更新,無法確定特定的更新時間。",
|
||||
"tag.Add New": "新增",
|
||||
"tag.Add_new_tag": "新增標籤",
|
||||
"tag.Edit_tag": "編輯標籤",
|
||||
@@ -59,6 +67,7 @@
|
||||
"tag.total_tags": "共 {{total}} 個標籤",
|
||||
"the_knowledge_base_has_indexes_that_are_being_trained_or_being_rebuilt": "資料集有索引正在訓練或重建中",
|
||||
"training_mode": "分段模式",
|
||||
"vector_model_max_tokens_tip": "每個分塊數據,最大長度為 3000 tokens",
|
||||
"website_dataset": "網站同步",
|
||||
"website_dataset_desc": "網站同步功能讓您可以直接使用網頁連結建立資料集"
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
"full_response_data": "完整回應資料",
|
||||
"greater_than": "大於",
|
||||
"greater_than_or_equal_to": "大於或等於",
|
||||
"http_body_placeholder": "與 APIFox 類似的語法,可透過 / 來啟動變數選擇。\n字串變數均需加雙引號,其他類型變數無需加雙引號。\n請嚴格檢查是否符合 JSON 格式。",
|
||||
"http_extract_output": "輸出欄位擷取",
|
||||
"http_extract_output_description": "可以透過 JSONPath 語法來擷取回應值中的指定欄位",
|
||||
"http_raw_response_description": "HTTP 請求的原始回應。僅接受字串或 JSON 類型回應資料。",
|
||||
@@ -193,4 +194,4 @@
|
||||
"workflow.Switch_success": "切換成功",
|
||||
"workflow.Team cloud": "團隊雲端",
|
||||
"workflow.exit_tips": "您的變更尚未儲存,「直接結束」將不會儲存您的編輯紀錄。"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ LOG_LEVEL=debug
|
||||
STORE_LOG_LEVEL=warn
|
||||
|
||||
# 安全配置
|
||||
# 启动 IP 限流(true),部分接口增加了 ip 限流策略,防止非正常请求操作。
|
||||
USE_IP_LIMIT=false
|
||||
# 工作流最大运行次数,避免极端的死循环情况
|
||||
WORKFLOW_MAX_RUN_TIMES=500
|
||||
# 循环最大运行次数,避免极端的死循环情况
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app",
|
||||
"version": "4.8.14",
|
||||
"version": "4.8.15",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
@@ -121,44 +121,55 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
</Box>
|
||||
{/* 导航列表 */}
|
||||
<Box flex={1}>
|
||||
{navbarList.map((item) => (
|
||||
<Box
|
||||
key={item.link}
|
||||
{...itemStyles}
|
||||
{...(item.activeLink.includes(router.pathname)
|
||||
? {
|
||||
color: 'primary.600',
|
||||
bg: 'white',
|
||||
boxShadow:
|
||||
'0px 0px 1px 0px rgba(19, 51, 107, 0.08), 0px 4px 4px 0px rgba(19, 51, 107, 0.05)'
|
||||
}
|
||||
: {
|
||||
color: 'myGray.500',
|
||||
bg: 'transparent',
|
||||
_hover: {
|
||||
bg: isSecondNavbarPage ? 'white' : 'rgba(255,255,255,0.9)'
|
||||
{navbarList.map((item) => {
|
||||
const isActive = item.activeLink.includes(router.pathname);
|
||||
return (
|
||||
<Box
|
||||
key={item.link}
|
||||
{...itemStyles}
|
||||
{...(isActive
|
||||
? {
|
||||
bg: 'white',
|
||||
boxShadow:
|
||||
'0px 0px 1px 0px rgba(19, 51, 107, 0.08), 0px 4px 4px 0px rgba(19, 51, 107, 0.05)'
|
||||
}
|
||||
})}
|
||||
{...(item.link !== router.asPath
|
||||
? {
|
||||
onClick: () => router.push(item.link)
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
<MyIcon
|
||||
name={
|
||||
item.activeLink.includes(router.pathname)
|
||||
? (item.activeIcon as any)
|
||||
: (item.icon as any)
|
||||
}
|
||||
width={'20px'}
|
||||
height={'20px'}
|
||||
/>
|
||||
<Box fontSize={'12px'} transform={'scale(0.9)'} mt={'5px'} lineHeight={1}>
|
||||
{item.label}
|
||||
: {
|
||||
bg: 'transparent',
|
||||
_hover: {
|
||||
bg: isSecondNavbarPage ? 'white' : 'rgba(255,255,255,0.9)'
|
||||
}
|
||||
})}
|
||||
{...(item.link !== router.asPath
|
||||
? {
|
||||
onClick: () => router.push(item.link)
|
||||
}
|
||||
: {})}
|
||||
>
|
||||
<MyIcon
|
||||
{...(isActive
|
||||
? {
|
||||
name: item.activeIcon as any,
|
||||
color: 'primary.600'
|
||||
}
|
||||
: {
|
||||
name: item.icon as any,
|
||||
color: 'myGray.400'
|
||||
})}
|
||||
width={'20px'}
|
||||
height={'20px'}
|
||||
/>
|
||||
<Box
|
||||
fontSize={'12px'}
|
||||
transform={'scale(0.9)'}
|
||||
mt={'5px'}
|
||||
lineHeight={1}
|
||||
color={isActive ? 'primary.700' : 'myGray.500'}
|
||||
>
|
||||
{item.label}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
|
||||
{unread > 0 && (
|
||||
@@ -191,10 +202,10 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
{...itemStyles}
|
||||
{...hoverStyle}
|
||||
mt={0}
|
||||
color={'myGray.500'}
|
||||
color={'myGray.400'}
|
||||
height={'48px'}
|
||||
>
|
||||
<Avatar src={item.avatar} borderRadius={'md'} />
|
||||
<Avatar src={item.avatar} borderRadius={'md'} width={'26px'} height={'26px'} />
|
||||
</Link>
|
||||
</MyTooltip>
|
||||
))}
|
||||
@@ -208,7 +219,7 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
{...itemStyles}
|
||||
{...hoverStyle}
|
||||
mt={0}
|
||||
color={'myGray.500'}
|
||||
color={'myGray.400'}
|
||||
height={'48px'}
|
||||
>
|
||||
<MyIcon name={'common/gitInlight'} width={'26px'} height={'26px'} />
|
||||
|
||||
@@ -48,7 +48,7 @@ const NavbarPhone = ({ unread }: { unread: number }) => {
|
||||
label: t('common:navbar.Account'),
|
||||
icon: 'support/user/userLight',
|
||||
activeIcon: 'support/user/userFill',
|
||||
link: '/account',
|
||||
link: '/account/info',
|
||||
activeLink: [
|
||||
'/account/bill',
|
||||
'/account/info',
|
||||
|
||||
@@ -29,6 +29,16 @@ const AIModelSelector = ({ list, onchange, disableTip, ...props }: Props) => {
|
||||
onOpen: onOpenAiPointsModal
|
||||
} = useDisclosure();
|
||||
|
||||
const avatarSize = useMemo(() => {
|
||||
const size = {
|
||||
sm: '1rem',
|
||||
md: '1.2rem',
|
||||
lg: '1.4rem'
|
||||
};
|
||||
//@ts-ignore
|
||||
return props.size ? size[props.size] : size['md'];
|
||||
}, [props.size]);
|
||||
|
||||
const avatarList = list.map((item) => {
|
||||
const modelData =
|
||||
llmModelList.find((model) => model.model === item.value) ||
|
||||
@@ -43,7 +53,7 @@ const AIModelSelector = ({ list, onchange, disableTip, ...props }: Props) => {
|
||||
mr={2}
|
||||
src={modelData?.avatar || HUGGING_FACE_ICON}
|
||||
fallbackSrc={HUGGING_FACE_ICON}
|
||||
w={'18px'}
|
||||
w={avatarSize}
|
||||
/>
|
||||
<Box>{item.label}</Box>
|
||||
</Flex>
|
||||
@@ -56,14 +66,14 @@ const AIModelSelector = ({ list, onchange, disableTip, ...props }: Props) => {
|
||||
? avatarList.concat({
|
||||
label: (
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar borderRadius={'0'} mr={2} src={LOGO_ICON} w={'18px'} />
|
||||
<Avatar borderRadius={'0'} mr={2} src={LOGO_ICON} w={avatarSize} />
|
||||
<Box>{t('common:support.user.Price')}</Box>
|
||||
</Flex>
|
||||
),
|
||||
value: 'price'
|
||||
})
|
||||
: avatarList;
|
||||
}, [feConfigs.show_pay, avatarList, t]);
|
||||
}, [feConfigs.show_pay, avatarList, avatarSize, t]);
|
||||
|
||||
const onSelect = useCallback(
|
||||
(e: string) => {
|
||||
@@ -73,7 +83,7 @@ const AIModelSelector = ({ list, onchange, disableTip, ...props }: Props) => {
|
||||
}
|
||||
return onchange?.(e);
|
||||
},
|
||||
[onchange, router]
|
||||
[onOpenAiPointsModal, onchange]
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Controller, UseFormReturn } from 'react-hook-form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { Box, Button, Card, Textarea } from '@chakra-ui/react';
|
||||
@@ -121,21 +121,16 @@ const VariableInput = ({
|
||||
const variablesForm = useContextSelector(ChatItemContext, (v) => v.variablesForm);
|
||||
const variableList = useContextSelector(ChatBoxContext, (v) => v.variableList);
|
||||
|
||||
const { setValue, handleSubmit: handleSubmitChat } = variablesForm;
|
||||
|
||||
const defaultValues = useMemo(() => {
|
||||
return variableList.reduce((acc: Record<string, any>, item) => {
|
||||
acc[item.key] = item.defaultValue;
|
||||
return acc;
|
||||
}, {});
|
||||
}, [variableList]);
|
||||
const { getValues, setValue, handleSubmit: handleSubmitChat } = variablesForm;
|
||||
|
||||
useEffect(() => {
|
||||
const values = variablesForm.getValues('variables');
|
||||
// If form is not empty, do not reset the variables
|
||||
if (Object.values(values).filter(Boolean).length > 0) return;
|
||||
setValue('variables', defaultValues);
|
||||
}, [defaultValues, setValue, variablesForm]);
|
||||
variableList.forEach((item) => {
|
||||
const val = getValues(`variables.${item.key}`);
|
||||
if (item.defaultValue !== undefined && (val === undefined || val === null || val === '')) {
|
||||
setValue(`variables.${item.key}`, item.defaultValue);
|
||||
}
|
||||
});
|
||||
}, [variableList]);
|
||||
|
||||
return (
|
||||
<Box py={3}>
|
||||
|
||||
@@ -58,7 +58,7 @@ import dynamic from 'next/dynamic';
|
||||
import type { StreamResponseType } from '@/web/common/api/fetch';
|
||||
import { useContextSelector } from 'use-context-selector';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { useCreation, useMemoizedFn, useThrottleFn } from 'ahooks';
|
||||
import { useCreation, useDebounceEffect, useMemoizedFn, useThrottleFn } from 'ahooks';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { mergeChatResponseData } from '@fastgpt/global/core/chat/utils';
|
||||
import { getWebReqUrl } from '@fastgpt/web/common/system/utils';
|
||||
@@ -380,10 +380,11 @@ const ChatBox = ({
|
||||
async ({ variables = {} }) => {
|
||||
if (!onStartChat) return;
|
||||
if (isChatting) {
|
||||
toast({
|
||||
title: t('chat:is_chatting'),
|
||||
status: 'warning'
|
||||
});
|
||||
!hideInUI &&
|
||||
toast({
|
||||
title: t('chat:is_chatting'),
|
||||
status: 'warning'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -804,7 +805,7 @@ const ChatBox = ({
|
||||
setQuestionGuide([]);
|
||||
setValue('chatStarted', false);
|
||||
abortRequest('leave');
|
||||
}, [abortRequest, setValue]);
|
||||
}, [chatId, appId, abortRequest, setValue]);
|
||||
|
||||
// Add listener
|
||||
useEffect(() => {
|
||||
@@ -834,27 +835,33 @@ const ChatBox = ({
|
||||
}, [isReady, resetInputVal, sendPrompt]);
|
||||
|
||||
// Auto send prompt
|
||||
useEffect(() => {
|
||||
if (
|
||||
isReady &&
|
||||
chatBoxData?.app?.chatConfig?.autoExecute?.open &&
|
||||
chatStarted &&
|
||||
chatRecords.length === 0 &&
|
||||
isChatRecordsLoaded
|
||||
) {
|
||||
sendPrompt({
|
||||
text: chatBoxData?.app?.chatConfig?.autoExecute?.defaultPrompt || 'AUTO_EXECUTE',
|
||||
hideInUI: true
|
||||
});
|
||||
useDebounceEffect(
|
||||
() => {
|
||||
if (
|
||||
isReady &&
|
||||
chatBoxData?.app?.chatConfig?.autoExecute?.open &&
|
||||
chatStarted &&
|
||||
chatRecords.length === 0 &&
|
||||
isChatRecordsLoaded
|
||||
) {
|
||||
sendPrompt({
|
||||
text: chatBoxData?.app?.chatConfig?.autoExecute?.defaultPrompt || 'AUTO_EXECUTE',
|
||||
hideInUI: true
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
isReady,
|
||||
chatStarted,
|
||||
chatRecords.length,
|
||||
isChatRecordsLoaded,
|
||||
sendPrompt,
|
||||
chatBoxData?.app?.chatConfig?.autoExecute
|
||||
],
|
||||
{
|
||||
wait: 500
|
||||
}
|
||||
}, [
|
||||
isReady,
|
||||
chatStarted,
|
||||
chatRecords.length,
|
||||
isChatRecordsLoaded,
|
||||
sendPrompt,
|
||||
chatBoxData?.app?.chatConfig?.autoExecute
|
||||
]);
|
||||
);
|
||||
|
||||
// output data
|
||||
useImperativeHandle(ChatBoxRef, () => ({
|
||||
|
||||
@@ -110,17 +110,15 @@ const RenderInput = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultFormValues = formatPluginInputs.reduce(
|
||||
(acc, input) => {
|
||||
acc[input.key] = input.defaultValue;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
);
|
||||
|
||||
reset({
|
||||
files: [],
|
||||
variables: defaultFormValues
|
||||
variables: formatPluginInputs.reduce(
|
||||
(acc, input) => {
|
||||
acc[input.key] = input.defaultValue;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
)
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -164,7 +162,7 @@ const RenderInput = () => {
|
||||
files: historyFileList
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [histories]);
|
||||
}, [histories, formatPluginInputs]);
|
||||
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
||||
|
||||
@@ -172,7 +172,6 @@ const RenderPluginInput = ({
|
||||
return (
|
||||
<Textarea
|
||||
value={value}
|
||||
defaultValue={input.defaultValue}
|
||||
onChange={onChange}
|
||||
isDisabled={isDisabled}
|
||||
placeholder={t(input.placeholder as any)}
|
||||
@@ -192,7 +191,6 @@ const RenderPluginInput = ({
|
||||
isInvalid={isInvalid}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
defaultValue={input.defaultValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -203,7 +201,6 @@ const RenderPluginInput = ({
|
||||
onChange={onChange}
|
||||
isDisabled={isDisabled}
|
||||
isInvalid={isInvalid}
|
||||
defaultChecked={!!input.defaultValue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -216,7 +213,6 @@ const RenderPluginInput = ({
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
isInvalid={isInvalid}
|
||||
defaultValue={input.defaultValue}
|
||||
/>
|
||||
);
|
||||
})();
|
||||
|
||||
@@ -2,12 +2,15 @@ import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { StandardSubLevelEnum, SubModeEnum } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import React, { useMemo } from 'react';
|
||||
import { standardSubLevelMap } from '@fastgpt/global/support/wallet/sub/constants';
|
||||
import { Box, Flex, Grid } from '@chakra-ui/react';
|
||||
import { Box, Flex, Grid, useDisclosure } from '@chakra-ui/react';
|
||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
import { getAiPointUsageCardRoute } from '@/web/support/wallet/sub/constants';
|
||||
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const AiPointsModal = dynamic(() =>
|
||||
import('@/pages/price/components/Points').then((mod) => mod.AiPointsModal)
|
||||
);
|
||||
|
||||
const StandardPlanContentList = ({
|
||||
level,
|
||||
@@ -18,7 +21,12 @@ const StandardPlanContentList = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { subPlans } = useSystemStore();
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
isOpen: isOpenAiPointsModal,
|
||||
onClose: onCloseAiPointsModal,
|
||||
onOpen: onOpenAiPointsModal
|
||||
} = useDisclosure();
|
||||
|
||||
const planContent = useMemo(() => {
|
||||
const plan = subPlans?.standard?.[level];
|
||||
@@ -95,9 +103,7 @@ const StandardPlanContentList = ({
|
||||
<QuestionTip
|
||||
ml={1}
|
||||
label={t('common:support.wallet.subscription.AI points click to read tip')}
|
||||
onClick={() => {
|
||||
router.push(getAiPointUsageCardRoute());
|
||||
}}
|
||||
onClick={onOpenAiPointsModal}
|
||||
></QuestionTip>
|
||||
</Flex>
|
||||
</Flex>
|
||||
@@ -121,6 +127,7 @@ const StandardPlanContentList = ({
|
||||
<Box color={'myGray.600'}>{t('common:support.wallet.subscription.web_site_sync')}</Box>
|
||||
</Flex>
|
||||
)}
|
||||
{isOpenAiPointsModal && <AiPointsModal onClose={onCloseAiPointsModal} />}
|
||||
</Grid>
|
||||
) : null;
|
||||
};
|
||||
|
||||
2
projects/app/src/global/core/chat/api.d.ts
vendored
2
projects/app/src/global/core/chat/api.d.ts
vendored
@@ -42,7 +42,7 @@ export type InitChatResponse = {
|
||||
appId: string;
|
||||
userAvatar?: string;
|
||||
title?: string;
|
||||
variables: Record<string, any>;
|
||||
variables?: Record<string, any>;
|
||||
app: {
|
||||
chatConfig?: AppChatConfigType;
|
||||
chatModels?: string[];
|
||||
|
||||
27
projects/app/src/pages/api/admin/initv4815.ts
Normal file
27
projects/app/src/pages/api/admin/initv4815.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
||||
|
||||
/* 初始化发布的版本 */
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
await authCert({ req, authRoot: true });
|
||||
|
||||
// scheduledTriggerConfig为 null 的,都转成 unExist
|
||||
return MongoApp.updateMany(
|
||||
{
|
||||
$or: [
|
||||
{ scheduledTriggerConfig: { $eq: null } },
|
||||
{ 'scheduledTriggerConfig.cronString': { $eq: '' } }
|
||||
]
|
||||
},
|
||||
{
|
||||
$unset: {
|
||||
scheduledTriggerConfig: '',
|
||||
scheduledTriggerNextTime: ''
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
@@ -35,12 +35,12 @@ async function handler(req: ApiRequestProps<CreateAppBody>) {
|
||||
}
|
||||
|
||||
// 凭证校验
|
||||
const { teamId, tmbId } = await authUserPer({ req, authToken: true, per: WritePermissionVal });
|
||||
if (parentId) {
|
||||
// if it is not a root app
|
||||
// check the parent folder permission
|
||||
await authApp({ req, appId: parentId, per: WritePermissionVal, authToken: true });
|
||||
}
|
||||
const [{ teamId, tmbId }] = await Promise.all([
|
||||
authUserPer({ req, authToken: true, per: WritePermissionVal }),
|
||||
...(parentId
|
||||
? [authApp({ req, appId: parentId, per: WritePermissionVal, authToken: true })]
|
||||
: [])
|
||||
]);
|
||||
|
||||
// 上限校验
|
||||
await checkTeamAppLimit(teamId);
|
||||
@@ -130,7 +130,8 @@ export const onCreateApp = async ({
|
||||
chatConfig,
|
||||
versionName: name,
|
||||
username,
|
||||
avatar: userAvatar
|
||||
avatar: userAvatar,
|
||||
isPublish: true
|
||||
}
|
||||
],
|
||||
{ session }
|
||||
|
||||
@@ -25,6 +25,16 @@ export type ListAppBody = {
|
||||
searchKey?: string;
|
||||
};
|
||||
|
||||
/*
|
||||
获取 APP 列表权限
|
||||
1. 校验 folder 权限和获取 team 权限(owner 单独处理)
|
||||
2. 获取 team 下所有 app 权限。获取我的所有组。并计算出我所有的app权限。
|
||||
3. 过滤我有的权限的 app,以及当前 parentId 的 app(由于权限继承问题,这里没法一次性根据 id 去获取)
|
||||
4. 根据过滤条件获取 app 列表
|
||||
5. 遍历搜索出来的 app,并赋予权限(继承的 app,使用 parent 的权限)
|
||||
6. 再根据 read 权限进行一次过滤。
|
||||
*/
|
||||
|
||||
async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemType[]> {
|
||||
const { parentId, type, getRecentlyChat, searchKey } = req.body;
|
||||
|
||||
@@ -75,6 +85,24 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
|
||||
);
|
||||
|
||||
const findAppsQuery = (() => {
|
||||
if (getRecentlyChat) {
|
||||
return {
|
||||
// get all chat app
|
||||
teamId,
|
||||
type: { $in: [AppTypeEnum.workflow, AppTypeEnum.simple, AppTypeEnum.plugin] }
|
||||
};
|
||||
}
|
||||
|
||||
// Filter apps by permission, if not owner, only get apps that I have permission to access
|
||||
const idList = { _id: { $in: myPerList.map((item) => item.resourceId) } };
|
||||
const appPerQuery = teamPer.isOwner
|
||||
? {}
|
||||
: parentId
|
||||
? {
|
||||
$or: [idList, parseParentIdInMongo(parentId)]
|
||||
}
|
||||
: idList;
|
||||
|
||||
const searchMatch = searchKey
|
||||
? {
|
||||
$or: [
|
||||
@@ -83,31 +111,17 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
|
||||
]
|
||||
}
|
||||
: {};
|
||||
// Filter apps by permission, if not owner, only get apps that I have permission to access
|
||||
const appIdQuery = teamPer.isOwner
|
||||
? {}
|
||||
: { _id: { $in: myPerList.map((item) => item.resourceId) } };
|
||||
|
||||
if (getRecentlyChat) {
|
||||
return {
|
||||
// get all chat app
|
||||
...appIdQuery,
|
||||
teamId,
|
||||
type: { $in: [AppTypeEnum.workflow, AppTypeEnum.simple, AppTypeEnum.plugin] },
|
||||
...searchMatch
|
||||
};
|
||||
}
|
||||
|
||||
if (searchKey) {
|
||||
return {
|
||||
...appIdQuery,
|
||||
...appPerQuery,
|
||||
teamId,
|
||||
...searchMatch
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...appIdQuery,
|
||||
...appPerQuery,
|
||||
teamId,
|
||||
...(type && (Array.isArray(type) ? { type: { $in: type } } : { type })),
|
||||
...parseParentIdInMongo(parentId)
|
||||
@@ -143,24 +157,30 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
|
||||
.map((item) => item.permission)
|
||||
);
|
||||
|
||||
// Count app collaborators
|
||||
const clbCount = perList.filter((item) => String(item.resourceId) === appId).length;
|
||||
|
||||
return {
|
||||
Per: new AppPermission({
|
||||
per: tmbPer ?? groupPer ?? AppDefaultPermissionVal,
|
||||
isOwner: String(app.tmbId) === String(tmbId) || teamPer.isOwner
|
||||
}),
|
||||
privateApp: AppFolderTypeList.includes(app.type) ? clbCount <= 1 : clbCount === 0
|
||||
};
|
||||
return new AppPermission({
|
||||
per: tmbPer ?? groupPer ?? AppDefaultPermissionVal,
|
||||
isOwner: String(app.tmbId) === String(tmbId) || teamPer.isOwner
|
||||
});
|
||||
};
|
||||
|
||||
// Inherit app
|
||||
if (app.inheritPermission && parentId && !AppFolderTypeList.includes(app.type)) {
|
||||
return getPer(String(parentId));
|
||||
} else {
|
||||
return getPer(String(app._id));
|
||||
const getClbCount = (appId: string) => {
|
||||
return perList.filter((item) => String(item.resourceId) === String(appId)).length;
|
||||
};
|
||||
|
||||
// Inherit app, check parent folder clb
|
||||
if (!AppFolderTypeList.includes(app.type) && app.parentId && app.inheritPermission) {
|
||||
return {
|
||||
Per: getPer(String(app.parentId)),
|
||||
privateApp: getClbCount(String(app.parentId)) <= 1
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
Per: getPer(String(app._id)),
|
||||
privateApp: AppFolderTypeList.includes(app.type)
|
||||
? getClbCount(String(app._id)) <= 1
|
||||
: getClbCount(String(app._id)) === 0
|
||||
};
|
||||
})();
|
||||
|
||||
return {
|
||||
|
||||
@@ -38,6 +38,7 @@ async function handler(
|
||||
type: AppTypeEnum.workflow,
|
||||
modules: app.modules,
|
||||
edges: app.edges,
|
||||
chatConfig: app.chatConfig,
|
||||
teamId: app.teamId,
|
||||
tmbId
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { NextApiResponse } from 'next';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
||||
@@ -52,12 +52,17 @@ async function handler(
|
||||
updateTime: new Date(),
|
||||
version: 'v2',
|
||||
// 只有发布才会更新定时器
|
||||
...(isPublish && {
|
||||
scheduledTriggerConfig: chatConfig?.scheduledTriggerConfig,
|
||||
scheduledTriggerNextTime: chatConfig?.scheduledTriggerConfig?.cronString
|
||||
? getNextTimeByCronStringAndTimezone(chatConfig.scheduledTriggerConfig)
|
||||
: null
|
||||
}),
|
||||
...(isPublish &&
|
||||
(chatConfig?.scheduledTriggerConfig?.cronString
|
||||
? {
|
||||
$set: {
|
||||
scheduledTriggerConfig: chatConfig.scheduledTriggerConfig,
|
||||
scheduledTriggerNextTime: getNextTimeByCronStringAndTimezone(
|
||||
chatConfig.scheduledTriggerConfig
|
||||
)
|
||||
}
|
||||
}
|
||||
: { $unset: { scheduledTriggerConfig: '', scheduledTriggerNextTime: '' } })),
|
||||
'pluginData.nodeVersion': _id
|
||||
},
|
||||
{
|
||||
|
||||
@@ -52,7 +52,7 @@ async function handler(
|
||||
appId,
|
||||
title: chat?.title,
|
||||
userAvatar: undefined,
|
||||
variables: chat?.variables || {},
|
||||
variables: chat?.variables,
|
||||
app: {
|
||||
chatConfig: getAppChatConfig({
|
||||
chatConfig,
|
||||
|
||||
@@ -43,7 +43,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
title: chat?.title,
|
||||
//@ts-ignore
|
||||
userAvatar: tmb?.userId?.avatar,
|
||||
variables: chat?.variables || {},
|
||||
variables: chat?.variables,
|
||||
app: {
|
||||
chatConfig: getAppChatConfig({
|
||||
chatConfig,
|
||||
|
||||
@@ -50,7 +50,7 @@ async function handler(req: ApiRequestProps<InitTeamChatProps>, res: NextApiResp
|
||||
appId,
|
||||
title: chat?.title,
|
||||
userAvatar: team?.avatar,
|
||||
variables: chat?.variables || {},
|
||||
variables: chat?.variables,
|
||||
app: {
|
||||
chatConfig: getAppChatConfig({
|
||||
chatConfig,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { NextAPI } from '@/service/middleware/entry';
|
||||
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
|
||||
import type { ApiRequestProps } from '@fastgpt/service/type/next';
|
||||
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
|
||||
import { authDataset } from '@fastgpt/service/support/permission/dataset/auth';
|
||||
|
||||
export type DatasetCreateQuery = {};
|
||||
export type DatasetCreateBody = CreateDatasetParams;
|
||||
@@ -29,12 +30,25 @@ async function handler(
|
||||
} = req.body;
|
||||
|
||||
// auth
|
||||
const { teamId, tmbId } = await authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
per: WritePermissionVal
|
||||
});
|
||||
const [{ teamId, tmbId }] = await Promise.all([
|
||||
authUserPer({
|
||||
req,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
per: WritePermissionVal
|
||||
}),
|
||||
...(parentId
|
||||
? [
|
||||
authDataset({
|
||||
req,
|
||||
datasetId: parentId,
|
||||
authToken: true,
|
||||
authApiKey: true,
|
||||
per: WritePermissionVal
|
||||
})
|
||||
]
|
||||
: [])
|
||||
]);
|
||||
|
||||
// check model valid
|
||||
const vectorModelStore = getVectorModel(vectorModel);
|
||||
|
||||
@@ -74,6 +74,16 @@ async function handler(req: ApiRequestProps<GetDatasetListBody>) {
|
||||
);
|
||||
|
||||
const findDatasetQuery = (() => {
|
||||
// Filter apps by permission, if not owner, only get apps that I have permission to access
|
||||
const idList = { _id: { $in: myPerList.map((item) => item.resourceId) } };
|
||||
const datasetPerQuery = teamPer.isOwner
|
||||
? {}
|
||||
: parentId
|
||||
? {
|
||||
$or: [idList, parseParentIdInMongo(parentId)]
|
||||
}
|
||||
: idList;
|
||||
|
||||
const searchMatch = searchKey
|
||||
? {
|
||||
$or: [
|
||||
@@ -82,21 +92,17 @@ async function handler(req: ApiRequestProps<GetDatasetListBody>) {
|
||||
]
|
||||
}
|
||||
: {};
|
||||
// Filter apps by permission, if not owner, only get apps that I have permission to access
|
||||
const appIdQuery = teamPer.isOwner
|
||||
? {}
|
||||
: { _id: { $in: myPerList.map((item) => item.resourceId) } };
|
||||
|
||||
if (searchKey) {
|
||||
return {
|
||||
...appIdQuery,
|
||||
...datasetPerQuery,
|
||||
teamId,
|
||||
...searchMatch
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...appIdQuery,
|
||||
...datasetPerQuery,
|
||||
teamId,
|
||||
...(type ? (Array.isArray(type) ? { type: { $in: type } } : { type }) : {}),
|
||||
...parseParentIdInMongo(parentId)
|
||||
@@ -121,23 +127,33 @@ async function handler(req: ApiRequestProps<GetDatasetListBody>) {
|
||||
.filter((item) => String(item.resourceId) === datasetId && !!item.groupId)
|
||||
.map((item) => item.permission)
|
||||
);
|
||||
|
||||
const clbCount = perList.filter((item) => String(item.resourceId) === datasetId).length;
|
||||
|
||||
return {
|
||||
Per: new DatasetPermission({
|
||||
per: tmbPer ?? groupPer ?? DatasetDefaultPermissionVal,
|
||||
isOwner: String(dataset.tmbId) === String(tmbId) || teamPer.isOwner
|
||||
}),
|
||||
privateDataset: dataset.type === 'folder' ? clbCount <= 1 : clbCount === 0
|
||||
};
|
||||
return new DatasetPermission({
|
||||
per: tmbPer ?? groupPer ?? DatasetDefaultPermissionVal,
|
||||
isOwner: String(dataset.tmbId) === String(tmbId) || teamPer.isOwner
|
||||
});
|
||||
};
|
||||
const getClbCount = (datasetId: string) => {
|
||||
return perList.filter((item) => String(item.resourceId) === String(datasetId)).length;
|
||||
};
|
||||
|
||||
// inherit
|
||||
if (dataset.inheritPermission && parentId && dataset.type !== DatasetTypeEnum.folder) {
|
||||
return getPer(String(parentId));
|
||||
} else {
|
||||
return getPer(String(dataset._id));
|
||||
if (
|
||||
dataset.inheritPermission &&
|
||||
dataset.parentId &&
|
||||
dataset.type !== DatasetTypeEnum.folder
|
||||
) {
|
||||
return {
|
||||
Per: getPer(String(dataset.parentId)),
|
||||
privateDataset: getClbCount(String(dataset.parentId)) <= 1
|
||||
};
|
||||
}
|
||||
return {
|
||||
Per: getPer(String(dataset._id)),
|
||||
privateDataset:
|
||||
dataset.type === DatasetTypeEnum.folder
|
||||
? getClbCount(String(dataset._id)) <= 1
|
||||
: getClbCount(String(dataset._id)) === 0
|
||||
};
|
||||
})();
|
||||
|
||||
return {
|
||||
@@ -148,21 +164,19 @@ async function handler(req: ApiRequestProps<GetDatasetListBody>) {
|
||||
})
|
||||
.filter((app) => app.permission.hasReadPer);
|
||||
|
||||
const data = await Promise.all(
|
||||
formatDatasets.map<DatasetListItemType>((item) => ({
|
||||
_id: item._id,
|
||||
avatar: item.avatar,
|
||||
name: item.name,
|
||||
intro: item.intro,
|
||||
type: item.type,
|
||||
permission: item.permission,
|
||||
vectorModel: getVectorModel(item.vectorModel),
|
||||
inheritPermission: item.inheritPermission,
|
||||
tmbId: item.tmbId,
|
||||
updateTime: item.updateTime,
|
||||
private: item.privateDataset
|
||||
}))
|
||||
);
|
||||
const data = formatDatasets.map<DatasetListItemType>((item) => ({
|
||||
_id: item._id,
|
||||
avatar: item.avatar,
|
||||
name: item.name,
|
||||
intro: item.intro,
|
||||
type: item.type,
|
||||
permission: item.permission,
|
||||
vectorModel: getVectorModel(item.vectorModel),
|
||||
inheritPermission: item.inheritPermission,
|
||||
tmbId: item.tmbId,
|
||||
updateTime: item.updateTime,
|
||||
private: item.privateDataset
|
||||
}));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -99,4 +99,4 @@ async function handler(req: NextApiRequest) {
|
||||
};
|
||||
}
|
||||
|
||||
export default NextAPI(useReqFrequencyLimit(1, 2), handler);
|
||||
export default NextAPI(useReqFrequencyLimit(1, 15), handler);
|
||||
|
||||
@@ -9,7 +9,11 @@ import {
|
||||
} from '@fastgpt/global/support/permission/constant';
|
||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
|
||||
import { DatasetTypeEnum, TrainingModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||
import {
|
||||
DatasetCollectionTypeEnum,
|
||||
DatasetTypeEnum,
|
||||
TrainingModeEnum
|
||||
} from '@fastgpt/global/core/dataset/constants';
|
||||
import { ClientSession } from 'mongoose';
|
||||
import { parseParentIdInMongo } from '@fastgpt/global/common/parentFolder/utils';
|
||||
import { mongoSessionRun } from '@fastgpt/service/common/mongo/sessionRun';
|
||||
@@ -22,6 +26,8 @@ import { authUserPer } from '@fastgpt/service/support/permission/user/auth';
|
||||
import { TeamWritePermissionVal } from '@fastgpt/global/support/permission/user/constant';
|
||||
import { DatasetErrEnum } from '@fastgpt/global/common/error/code/dataset';
|
||||
import { MongoDatasetTraining } from '@fastgpt/service/core/dataset/training/schema';
|
||||
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
|
||||
import { addDays } from 'date-fns';
|
||||
|
||||
export type DatasetUpdateQuery = {};
|
||||
export type DatasetUpdateResponse = any;
|
||||
@@ -51,7 +57,8 @@ async function handler(
|
||||
websiteConfig,
|
||||
externalReadUrl,
|
||||
apiServer,
|
||||
status
|
||||
status,
|
||||
autoSync
|
||||
} = req.body;
|
||||
|
||||
if (!id) {
|
||||
@@ -101,7 +108,7 @@ async function handler(
|
||||
agentModel: agentModel?.model
|
||||
});
|
||||
|
||||
const onUpdate = async (session?: ClientSession) => {
|
||||
const onUpdate = async (session: ClientSession) => {
|
||||
await MongoDataset.findByIdAndUpdate(
|
||||
id,
|
||||
{
|
||||
@@ -117,14 +124,21 @@ async function handler(
|
||||
...(!!apiServer?.authorization && {
|
||||
'apiServer.authorization': apiServer.authorization
|
||||
}),
|
||||
...(isMove && { inheritPermission: true })
|
||||
...(isMove && { inheritPermission: true }),
|
||||
...(typeof autoSync === 'boolean' && { autoSync })
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
await updateSyncSchedule({
|
||||
teamId: dataset.teamId,
|
||||
datasetId: dataset._id,
|
||||
autoSync,
|
||||
session
|
||||
});
|
||||
};
|
||||
|
||||
if (isMove) {
|
||||
await mongoSessionRun(async (session) => {
|
||||
await mongoSessionRun(async (session) => {
|
||||
if (isMove) {
|
||||
if (isFolder && dataset.inheritPermission) {
|
||||
const parentClbsAndGroups = await getResourceClbsAndGroups({
|
||||
teamId: dataset.teamId,
|
||||
@@ -149,17 +163,16 @@ async function handler(
|
||||
collaborators: parentClbsAndGroups,
|
||||
session
|
||||
});
|
||||
return onUpdate(session);
|
||||
}
|
||||
return onUpdate(session);
|
||||
});
|
||||
} else {
|
||||
return onUpdate();
|
||||
}
|
||||
} else {
|
||||
return onUpdate(session);
|
||||
}
|
||||
});
|
||||
}
|
||||
export default NextAPI(handler);
|
||||
|
||||
async function updateTraining({
|
||||
const updateTraining = async ({
|
||||
teamId,
|
||||
datasetId,
|
||||
agentModel
|
||||
@@ -167,7 +180,7 @@ async function updateTraining({
|
||||
teamId: string;
|
||||
datasetId: string;
|
||||
agentModel?: string;
|
||||
}) {
|
||||
}) => {
|
||||
if (!agentModel) return;
|
||||
|
||||
await MongoDatasetTraining.updateMany(
|
||||
@@ -184,4 +197,48 @@ async function updateTraining({
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const updateSyncSchedule = async ({
|
||||
teamId,
|
||||
datasetId,
|
||||
autoSync,
|
||||
session
|
||||
}: {
|
||||
teamId: string;
|
||||
datasetId: string;
|
||||
autoSync?: boolean;
|
||||
session: ClientSession;
|
||||
}) => {
|
||||
if (typeof autoSync !== 'boolean') return;
|
||||
|
||||
// Update all collection nextSyncTime
|
||||
if (autoSync) {
|
||||
await MongoDatasetCollection.updateMany(
|
||||
{
|
||||
teamId,
|
||||
datasetId,
|
||||
type: { $in: [DatasetCollectionTypeEnum.apiFile, DatasetCollectionTypeEnum.link] }
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
nextSyncTime: addDays(new Date(), 1)
|
||||
}
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
} else {
|
||||
await MongoDatasetCollection.updateMany(
|
||||
{
|
||||
teamId,
|
||||
datasetId
|
||||
},
|
||||
{
|
||||
$unset: {
|
||||
nextSyncTime: 1
|
||||
}
|
||||
},
|
||||
{ session }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,71 +1,63 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@fastgpt/service/common/response';
|
||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||
import { createJWT, setCookie } from '@fastgpt/service/support/permission/controller';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { getUserDetail } from '@fastgpt/service/support/user/controller';
|
||||
import type { PostLoginProps } from '@fastgpt/global/support/user/api.d';
|
||||
import { UserStatusEnum } from '@fastgpt/global/support/user/constant';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { useReqFrequencyLimit } from '@fastgpt/service/common/middle/reqFrequencyLimit';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { username, password } = req.body as PostLoginProps;
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { username, password } = req.body as PostLoginProps;
|
||||
|
||||
if (!username || !password) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
// 检测用户是否存在
|
||||
const authCert = await MongoUser.findOne(
|
||||
{
|
||||
username
|
||||
},
|
||||
'status'
|
||||
);
|
||||
if (!authCert) {
|
||||
throw new Error('用户未注册');
|
||||
}
|
||||
|
||||
if (authCert.status === UserStatusEnum.forbidden) {
|
||||
throw new Error('账号已停用,无法登录');
|
||||
}
|
||||
|
||||
const user = await MongoUser.findOne({
|
||||
username,
|
||||
password
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Error('密码错误');
|
||||
}
|
||||
|
||||
const userDetail = await getUserDetail({
|
||||
tmbId: user?.lastLoginTmbId,
|
||||
userId: user._id
|
||||
});
|
||||
|
||||
MongoUser.findByIdAndUpdate(user._id, {
|
||||
lastLoginTmbId: userDetail.team.tmbId
|
||||
});
|
||||
|
||||
const token = createJWT({
|
||||
...userDetail,
|
||||
isRoot: username === 'root'
|
||||
});
|
||||
|
||||
setCookie(res, token);
|
||||
|
||||
jsonRes(res, {
|
||||
data: {
|
||||
user: userDetail,
|
||||
token
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
if (!username || !password) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
// 检测用户是否存在
|
||||
const authCert = await MongoUser.findOne(
|
||||
{
|
||||
username
|
||||
},
|
||||
'status'
|
||||
);
|
||||
if (!authCert) {
|
||||
throw new Error('用户未注册');
|
||||
}
|
||||
|
||||
if (authCert.status === UserStatusEnum.forbidden) {
|
||||
throw new Error('账号已停用,无法登录');
|
||||
}
|
||||
|
||||
const user = await MongoUser.findOne({
|
||||
username,
|
||||
password
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Error('密码错误');
|
||||
}
|
||||
|
||||
const userDetail = await getUserDetail({
|
||||
tmbId: user?.lastLoginTmbId,
|
||||
userId: user._id
|
||||
});
|
||||
|
||||
MongoUser.findByIdAndUpdate(user._id, {
|
||||
lastLoginTmbId: userDetail.team.tmbId
|
||||
});
|
||||
|
||||
const token = createJWT({
|
||||
...userDetail,
|
||||
isRoot: username === 'root'
|
||||
});
|
||||
|
||||
setCookie(res, token);
|
||||
|
||||
return {
|
||||
user: userDetail,
|
||||
token
|
||||
};
|
||||
}
|
||||
|
||||
export default NextAPI(useReqFrequencyLimit(120, 10), handler);
|
||||
|
||||
@@ -8,16 +8,17 @@ import { authChatCrud } from '@/service/support/permission/auth/chat';
|
||||
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
|
||||
import { NextAPI } from '@/service/middleware/entry';
|
||||
import { aiTranscriptions } from '@fastgpt/service/core/ai/audio/transcriptions';
|
||||
import { useReqFrequencyLimit } from '@fastgpt/service/common/middle/reqFrequencyLimit';
|
||||
|
||||
const upload = getUploadModel({
|
||||
maxSize: 20
|
||||
maxSize: 5
|
||||
});
|
||||
|
||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
let filePaths: string[] = [];
|
||||
|
||||
try {
|
||||
const {
|
||||
let {
|
||||
file,
|
||||
data: { appId, duration, shareId, outLinkUid, teamId: spaceTeamId, teamToken }
|
||||
} = await upload.doUpload<
|
||||
@@ -42,9 +43,17 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
if (!file) {
|
||||
throw new Error('file not found');
|
||||
}
|
||||
if (duration === undefined) {
|
||||
throw new Error('duration not found');
|
||||
}
|
||||
duration = duration < 1 ? 1 : duration;
|
||||
|
||||
// auth role
|
||||
const { teamId, tmbId } = await authChatCrud({ req, authToken: true, ...req.body });
|
||||
const { teamId, tmbId } = await authChatCrud({
|
||||
req,
|
||||
authToken: true,
|
||||
...req.body
|
||||
});
|
||||
|
||||
// auth app
|
||||
// const app = await MongoApp.findById(appId, 'modules').lean();
|
||||
@@ -80,7 +89,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
removeFilesByPaths(filePaths);
|
||||
}
|
||||
|
||||
export default NextAPI(handler);
|
||||
export default NextAPI(useReqFrequencyLimit(1, 1), handler);
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
|
||||
@@ -48,7 +48,8 @@ const DetailLogsModal = ({ appId, chatId, onClose }: Props) => {
|
||||
|
||||
setChatBoxData(res);
|
||||
resetVariables({
|
||||
variables: res.variables
|
||||
variables: res.variables,
|
||||
variableList: res.app?.chatConfig?.variables
|
||||
});
|
||||
|
||||
return res;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Flex,
|
||||
Box,
|
||||
@@ -31,7 +31,8 @@ import { cardStyles } from '../constants';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import Tag from '@fastgpt/web/components/common/Tag';
|
||||
import { useMount } from 'ahooks';
|
||||
|
||||
const DetailLogsModal = dynamic(() => import('./DetailLogsModal'));
|
||||
|
||||
const Logs = () => {
|
||||
@@ -41,9 +42,9 @@ const Logs = () => {
|
||||
const appId = useContextSelector(AppContext, (v) => v.appId);
|
||||
const { teamMembers, loadAndGetTeamMembers } = useUserStore();
|
||||
|
||||
useEffect(() => {
|
||||
useMount(() => {
|
||||
loadAndGetTeamMembers();
|
||||
}, []);
|
||||
});
|
||||
|
||||
const [dateRange, setDateRange] = useState<DateRangeType>({
|
||||
from: addDays(new Date(), -7),
|
||||
@@ -140,11 +141,11 @@ const Logs = () => {
|
||||
) : (
|
||||
<HStack>
|
||||
<Avatar
|
||||
src={teamMembers.find((v) => v.tmbId === item.tmbId)?.avatar}
|
||||
src={teamMembers?.find((v) => v.tmbId === item.tmbId)?.avatar}
|
||||
w="1.25rem"
|
||||
/>
|
||||
<Box fontSize={'sm'} ml={1}>
|
||||
{teamMembers.find((v) => v.tmbId === item.tmbId)?.memberName}
|
||||
{teamMembers?.find((v) => v.tmbId === item.tmbId)?.memberName}
|
||||
</Box>
|
||||
</HStack>
|
||||
)}
|
||||
|
||||
@@ -97,9 +97,10 @@ const AppCard = ({ showSaveStatus, isSaved }: { showSaveStatus: boolean; isSaved
|
||||
appName: appDetail.name
|
||||
})}
|
||||
</MyBox>
|
||||
<Box w={'full'} h={'1px'} bg={'myGray.200'} my={1} />
|
||||
{appDetail.permission.hasWritePer && feConfigs?.show_team_chat && (
|
||||
<>
|
||||
<Box w={'full'} h={'1px'} bg={'myGray.200'} my={1} />
|
||||
|
||||
<MyBox
|
||||
display={'flex'}
|
||||
size={'md'}
|
||||
@@ -113,25 +114,28 @@ const AppCard = ({ showSaveStatus, isSaved }: { showSaveStatus: boolean; isSaved
|
||||
<MyIcon name={'core/dataset/tag'} w={'16px'} mr={2} />
|
||||
<Box fontSize={'sm'}>{t('app:Team_Tags')}</Box>
|
||||
</MyBox>
|
||||
<Box w={'full'} h={'1px'} bg={'myGray.200'} my={1} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{appDetail.permission.isOwner && (
|
||||
<MyBox
|
||||
display={'flex'}
|
||||
size={'md'}
|
||||
px={1}
|
||||
py={1.5}
|
||||
rounded={'4px'}
|
||||
color={'red.600'}
|
||||
_hover={{ bg: 'rgba(17, 24, 36, 0.05)' }}
|
||||
cursor={'pointer'}
|
||||
onClick={onDelApp}
|
||||
>
|
||||
<MyIcon name={'delete'} w={'16px'} mr={2} />
|
||||
<Box fontSize={'sm'}>{t('common:common.Delete')}</Box>
|
||||
</MyBox>
|
||||
<>
|
||||
<Box w={'full'} h={'1px'} bg={'myGray.200'} my={1} />
|
||||
|
||||
<MyBox
|
||||
display={'flex'}
|
||||
size={'md'}
|
||||
px={1}
|
||||
py={1.5}
|
||||
rounded={'4px'}
|
||||
color={'red.600'}
|
||||
_hover={{ bg: 'rgba(17, 24, 36, 0.05)' }}
|
||||
cursor={'pointer'}
|
||||
onClick={onDelApp}
|
||||
>
|
||||
<MyIcon name={'delete'} w={'16px'} mr={2} />
|
||||
<Box fontSize={'sm'}>{t('common:common.Delete')}</Box>
|
||||
</MyBox>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -657,10 +657,12 @@ const RenderList = React.memo(function RenderList({
|
||||
<Box mt={2} color={'myGray.500'} maxH={'100px'} overflow={'hidden'}>
|
||||
{t(template.intro as any) || t('common:core.workflow.Not intro')}
|
||||
</Box>
|
||||
<CostTooltip
|
||||
cost={template.currentCost}
|
||||
hasTokenFee={template.hasTokenFee}
|
||||
/>
|
||||
{type === TemplateTypeEnum.systemPlugin && (
|
||||
<CostTooltip
|
||||
cost={template.currentCost}
|
||||
hasTokenFee={template.hasTokenFee}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -10,8 +10,6 @@ import {
|
||||
NodePositionChange,
|
||||
XYPosition,
|
||||
useReactFlow,
|
||||
getNodesBounds,
|
||||
Rect,
|
||||
NodeRemoveChange,
|
||||
NodeSelectionChange,
|
||||
EdgeRemoveChange
|
||||
@@ -26,15 +24,12 @@ import { WorkflowContext } from '../../context';
|
||||
import { THelperLine } from '@fastgpt/global/core/workflow/type';
|
||||
import { NodeInputKeyEnum, NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import { useDebounceEffect, useMemoizedFn } from 'ahooks';
|
||||
import {
|
||||
Input_Template_Node_Height,
|
||||
Input_Template_Node_Width
|
||||
} from '@fastgpt/global/core/workflow/template/input';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { WorkflowNodeEdgeContext, WorkflowInitContext } from '../../context/workflowInitContext';
|
||||
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
|
||||
import { AppContext } from '../../../context';
|
||||
import { WorkflowEventContext } from '../../context/workflowEventContext';
|
||||
import { WorkflowStatusContext } from '../../context/workflowStatusContext';
|
||||
|
||||
/*
|
||||
Compute helper lines for snapping nodes to each other
|
||||
@@ -282,18 +277,22 @@ export const useWorkflow = () => {
|
||||
const edges = useContextSelector(WorkflowNodeEdgeContext, (state) => state.edges);
|
||||
const setEdges = useContextSelector(WorkflowNodeEdgeContext, (v) => v.setEdges);
|
||||
const onEdgesChange = useContextSelector(WorkflowNodeEdgeContext, (v) => v.onEdgesChange);
|
||||
const { setConnectingEdge, nodeList, onChangeNode, pushPastSnapshot } = useContextSelector(
|
||||
WorkflowContext,
|
||||
(v) => v
|
||||
);
|
||||
|
||||
const setConnectingEdge = useContextSelector(WorkflowContext, (v) => v.setConnectingEdge);
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const pushPastSnapshot = useContextSelector(WorkflowContext, (v) => v.pushPastSnapshot);
|
||||
|
||||
const setHoverEdgeId = useContextSelector(WorkflowEventContext, (v) => v.setHoverEdgeId);
|
||||
const setMenu = useContextSelector(WorkflowEventContext, (v) => v.setMenu);
|
||||
const resetParentNodeSizeAndPosition = useContextSelector(
|
||||
WorkflowStatusContext,
|
||||
(v) => v.resetParentNodeSizeAndPosition
|
||||
);
|
||||
|
||||
const { getIntersectingNodes } = useReactFlow();
|
||||
const { isDowningCtrl } = useKeyboard();
|
||||
|
||||
const { resetParentNodeSizeAndPosition } = useLoopNode();
|
||||
|
||||
/* helper line */
|
||||
const [helperLineHorizontal, setHelperLineHorizontal] = useState<THelperLine>();
|
||||
const [helperLineVertical, setHelperLineVertical] = useState<THelperLine>();
|
||||
@@ -669,73 +668,6 @@ export const useWorkflow = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useLoopNode = () => {
|
||||
const nodes = useContextSelector(WorkflowInitContext, (state) => state.nodes);
|
||||
const onNodesChange = useContextSelector(WorkflowNodeEdgeContext, (state) => state.onNodesChange);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const resetParentNodeSizeAndPosition = useMemoizedFn((parentId: string) => {
|
||||
const { childNodes, loopNode } = nodes.reduce(
|
||||
(acc, node) => {
|
||||
if (node.data.parentNodeId === parentId) {
|
||||
acc.childNodes.push(node);
|
||||
}
|
||||
if (node.id === parentId) {
|
||||
acc.loopNode = node;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ childNodes: [] as Node[], loopNode: undefined as Node<FlowNodeItemType> | undefined }
|
||||
);
|
||||
|
||||
if (!loopNode) return;
|
||||
|
||||
const rect = getNodesBounds(childNodes);
|
||||
// Calculate parent node size with minimum width/height constraints
|
||||
const width = Math.max(rect.width + 80, 840);
|
||||
const height = Math.max(rect.height + 80, 600);
|
||||
|
||||
const offsetHeight =
|
||||
loopNode.data.inputs.find((input) => input.key === NodeInputKeyEnum.loopNodeInputHeight)
|
||||
?.value ?? 83;
|
||||
|
||||
// Update parentNode size and position
|
||||
onChangeNode({
|
||||
nodeId: parentId,
|
||||
type: 'updateInput',
|
||||
key: NodeInputKeyEnum.nodeWidth,
|
||||
value: {
|
||||
...Input_Template_Node_Width,
|
||||
value: width
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
nodeId: parentId,
|
||||
type: 'updateInput',
|
||||
key: NodeInputKeyEnum.nodeHeight,
|
||||
value: {
|
||||
...Input_Template_Node_Height,
|
||||
value: height
|
||||
}
|
||||
});
|
||||
// Update parentNode position
|
||||
onNodesChange([
|
||||
{
|
||||
id: parentId,
|
||||
type: 'position',
|
||||
position: {
|
||||
x: rect.x - 70,
|
||||
y: rect.y - offsetHeight - 240
|
||||
}
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
return {
|
||||
resetParentNodeSizeAndPosition
|
||||
};
|
||||
};
|
||||
|
||||
export default function Dom() {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
When the childNodes of loopFlow change, it automatically calculates the rectangular width, height, and position of the childNodes,
|
||||
thereby further updating the width and height properties of the loop node.
|
||||
*/
|
||||
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import { Background, NodeProps } from 'reactflow';
|
||||
@@ -31,8 +30,8 @@ import { getWorkflowGlobalVariables } from '@/web/core/workflow/utils';
|
||||
import { AppContext } from '../../../../context';
|
||||
import { isValidArrayReferenceValue } from '@fastgpt/global/core/workflow/utils';
|
||||
import { ReferenceArrayValueType } from '@fastgpt/global/core/workflow/type/io';
|
||||
import { useLoopNode } from '../../hooks/useWorkflow';
|
||||
import { useSize } from 'ahooks';
|
||||
import { WorkflowStatusContext } from '../../../context/workflowStatusContext';
|
||||
|
||||
const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -40,8 +39,10 @@ const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const appDetail = useContextSelector(AppContext, (v) => v.appDetail);
|
||||
|
||||
const { resetParentNodeSizeAndPosition } = useLoopNode();
|
||||
const resetParentNodeSizeAndPosition = useContextSelector(
|
||||
WorkflowStatusContext,
|
||||
(v) => v.resetParentNodeSizeAndPosition
|
||||
);
|
||||
|
||||
const {
|
||||
nodeWidth,
|
||||
@@ -50,11 +51,11 @@ const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
loopNodeInputHeight = Input_Template_LOOP_NODE_OFFSET
|
||||
} = useMemo(() => {
|
||||
return {
|
||||
nodeWidth: Number(
|
||||
inputs.find((input) => input.key === NodeInputKeyEnum.nodeWidth)?.value?.toFixed(0)
|
||||
nodeWidth: Math.round(
|
||||
Number(inputs.find((input) => input.key === NodeInputKeyEnum.nodeWidth)?.value) || 500
|
||||
),
|
||||
nodeHeight: Number(
|
||||
inputs.find((input) => input.key === NodeInputKeyEnum.nodeHeight)?.value?.toFixed(0)
|
||||
nodeHeight: Math.round(
|
||||
Number(inputs.find((input) => input.key === NodeInputKeyEnum.nodeHeight)?.value) || 500
|
||||
),
|
||||
loopInputArray: inputs.find((input) => input.key === NodeInputKeyEnum.loopInputArray),
|
||||
loopNodeInputHeight: inputs.find(
|
||||
@@ -113,7 +114,7 @@ const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
return JSON.stringify(
|
||||
nodeList.filter((node) => node.parentNodeId === nodeId).map((node) => node.nodeId)
|
||||
);
|
||||
}, [nodeId, nodeList]);
|
||||
}, [nodeId, nodeList.length]);
|
||||
useEffect(() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
@@ -148,39 +149,54 @@ const NodeLoop = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
}, 50);
|
||||
}, [size?.height]);
|
||||
|
||||
const Render = useMemo(() => {
|
||||
const RenderInputDom = useMemo(() => {
|
||||
return (
|
||||
<NodeCard selected={selected} maxW="full" menuForbid={{ copy: true }} {...data}>
|
||||
<Container position={'relative'} flex={1}>
|
||||
<IOTitle text={t('common:common.Input')} />
|
||||
<Box mb={6} maxW={'500px'} ref={inputBoxRef}>
|
||||
<RenderInput nodeId={nodeId} flowInputList={inputs} />
|
||||
</Box>
|
||||
<FormLabel required fontWeight={'medium'} mb={3} color={'myGray.600'}>
|
||||
{t('workflow:loop_body')}
|
||||
</FormLabel>
|
||||
<Box
|
||||
flex={1}
|
||||
position={'relative'}
|
||||
border={'base'}
|
||||
bg={'myGray.100'}
|
||||
rounded={'8px'}
|
||||
{...(!isFolded && {
|
||||
minW: nodeWidth,
|
||||
minH: nodeHeight
|
||||
})}
|
||||
>
|
||||
<Background />
|
||||
</Box>
|
||||
</Container>
|
||||
<Container>
|
||||
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
</NodeCard>
|
||||
<Box mb={6} maxW={'500px'} ref={inputBoxRef}>
|
||||
<RenderInput nodeId={nodeId} flowInputList={inputs} />
|
||||
</Box>
|
||||
);
|
||||
}, [selected, isFolded, nodeWidth, nodeHeight, data, t, nodeId, inputs, outputs]);
|
||||
}, [inputs, nodeId]);
|
||||
const RenderChildrenNodes = useMemo(() => {
|
||||
return (
|
||||
<>
|
||||
<FormLabel required fontWeight={'medium'} mb={3} color={'myGray.600'}>
|
||||
{t('workflow:loop_body')}
|
||||
</FormLabel>
|
||||
<Box
|
||||
flex={1}
|
||||
position={'relative'}
|
||||
border={'base'}
|
||||
bg={'myGray.100'}
|
||||
rounded={'8px'}
|
||||
{...(!isFolded && {
|
||||
minW: nodeWidth,
|
||||
minH: nodeHeight
|
||||
})}
|
||||
>
|
||||
<Background />
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}, [isFolded, nodeHeight, nodeWidth, t]);
|
||||
|
||||
return Render;
|
||||
const MemoRenderOutput = useMemo(() => {
|
||||
return (
|
||||
<Container>
|
||||
<RenderOutput nodeId={nodeId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
);
|
||||
}, [nodeId, outputs]);
|
||||
|
||||
return (
|
||||
<NodeCard selected={selected} maxW="full" menuForbid={{ copy: true }} {...data}>
|
||||
<Container position={'relative'} flex={1}>
|
||||
<IOTitle text={t('common:common.Input')} />
|
||||
{RenderInputDom}
|
||||
{RenderChildrenNodes}
|
||||
</Container>
|
||||
{MemoRenderOutput}
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NodeLoop);
|
||||
|
||||
@@ -23,13 +23,15 @@ const typeMap = {
|
||||
[WorkflowIOValueTypeEnum.arrayString]: WorkflowIOValueTypeEnum.string,
|
||||
[WorkflowIOValueTypeEnum.arrayNumber]: WorkflowIOValueTypeEnum.number,
|
||||
[WorkflowIOValueTypeEnum.arrayBoolean]: WorkflowIOValueTypeEnum.boolean,
|
||||
[WorkflowIOValueTypeEnum.arrayObject]: WorkflowIOValueTypeEnum.object
|
||||
[WorkflowIOValueTypeEnum.arrayObject]: WorkflowIOValueTypeEnum.object,
|
||||
[WorkflowIOValueTypeEnum.arrayAny]: WorkflowIOValueTypeEnum.any
|
||||
};
|
||||
|
||||
const NodeLoopStart = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
const { nodeId, outputs } = data;
|
||||
const { nodeList, onChangeNode } = useContextSelector(WorkflowContext, (v) => v);
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const loopStartNode = useMemo(
|
||||
() => nodeList.find((node) => node.nodeId === nodeId),
|
||||
|
||||
@@ -687,26 +687,24 @@ const RenderBody = ({
|
||||
<RenderForm nodeId={nodeId} input={formBody} variables={variables} />
|
||||
)}
|
||||
{typeInput?.value === ContentTypes.json && (
|
||||
<JSONEditor
|
||||
<PromptEditor
|
||||
bg={'white'}
|
||||
defaultHeight={200}
|
||||
resize
|
||||
showOpenModal={false}
|
||||
variableLabels={variables}
|
||||
minH={200}
|
||||
value={jsonBody.value}
|
||||
placeholder={t('common:core.module.template.http body placeholder')}
|
||||
placeholder={t('workflow:http_body_placeholder')}
|
||||
onChange={(e) => {
|
||||
startSts(() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'updateInput',
|
||||
key: jsonBody.key,
|
||||
value: {
|
||||
...jsonBody,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'updateInput',
|
||||
key: jsonBody.key,
|
||||
value: {
|
||||
...jsonBody,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
variables={variables}
|
||||
/>
|
||||
)}
|
||||
{(typeInput?.value === ContentTypes.xml || typeInput?.value === ContentTypes.raw) && (
|
||||
@@ -714,16 +712,14 @@ const RenderBody = ({
|
||||
value={jsonBody.value}
|
||||
placeholder={t('common:textarea_variable_picker_tip')}
|
||||
onChange={(e) => {
|
||||
startSts(() => {
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'updateInput',
|
||||
key: jsonBody.key,
|
||||
value: {
|
||||
...jsonBody,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
nodeId,
|
||||
type: 'updateInput',
|
||||
key: jsonBody.key,
|
||||
value: {
|
||||
...jsonBody,
|
||||
value: e
|
||||
}
|
||||
});
|
||||
}}
|
||||
showOpenModal={false}
|
||||
|
||||
@@ -85,18 +85,28 @@ type Props = {
|
||||
const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props) => {
|
||||
const { feConfigs } = useSystemStore();
|
||||
|
||||
const filterInputs = useMemo(() => {
|
||||
const filterProInputs = useMemo(() => {
|
||||
return flowInputList.filter((input) => {
|
||||
if (input.isPro && !feConfigs?.isPlus) return false;
|
||||
return true;
|
||||
});
|
||||
}, [feConfigs?.isPlus, flowInputList]);
|
||||
|
||||
const filterInputs = useMemo(() => {
|
||||
return filterProInputs.filter((input) => {
|
||||
const renderType = input.renderTypeList?.[input.selectedTypeIndex || 0];
|
||||
const isDynamic = !!input.canEdit;
|
||||
|
||||
if (renderType === FlowNodeInputTypeEnum.hidden || isDynamic) return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
}, [feConfigs?.isPlus, flowInputList]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{filterInputs.map((input) => {
|
||||
const renderType = input.renderTypeList?.[input.selectedTypeIndex || 0];
|
||||
const isDynamic = !!input.canEdit;
|
||||
|
||||
const RenderComponent = (() => {
|
||||
if (renderType === FlowNodeInputTypeEnum.custom && CustomComponent?.[input.key]) {
|
||||
@@ -106,10 +116,10 @@ const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props)
|
||||
const Component = RenderList.find((item) => item.types.includes(renderType))?.Component;
|
||||
|
||||
if (!Component) return null;
|
||||
return <Component inputs={filterInputs} item={input} nodeId={nodeId} />;
|
||||
return <Component inputs={filterProInputs} item={input} nodeId={nodeId} />;
|
||||
})();
|
||||
|
||||
return renderType !== FlowNodeInputTypeEnum.hidden && !isDynamic ? (
|
||||
return (
|
||||
<Box key={input.key} _notLast={{ mb }} position={'relative'}>
|
||||
{!!input.label && !hideLabelTypeList.includes(renderType) && (
|
||||
<InputLabel nodeId={nodeId} input={input} />
|
||||
@@ -120,7 +130,7 @@ const RenderInput = ({ flowInputList, nodeId, CustomComponent, mb = 5 }: Props)
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
) : null;
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -115,7 +115,10 @@ export const useReference = ({
|
||||
|
||||
const Reference = ({ item, nodeId }: RenderInputProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { onChangeNode, nodeList } = useContextSelector(WorkflowContext, (v) => v);
|
||||
|
||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
|
||||
const isArray = item.valueType?.includes('array') ?? false;
|
||||
|
||||
const onSelect = useCallback(
|
||||
@@ -255,21 +258,24 @@ const MultipleReferenceSelector = ({
|
||||
}, [getSelectValue, value]);
|
||||
|
||||
useEffect(() => {
|
||||
const validList = formatList.filter((item) => item.nodeName && item.outputName);
|
||||
if (validList.length !== value?.length) {
|
||||
onSelect(validList.map((item) => item.rawValue));
|
||||
// Adapt array type from old version
|
||||
if (Array.isArray(value) && typeof value[0] === 'string') {
|
||||
// @ts-ignore
|
||||
onSelect([value]);
|
||||
}
|
||||
}, [formatList, onSelect, value]);
|
||||
|
||||
const invalidList = useMemo(() => {
|
||||
return formatList.filter((item) => item.nodeName && item.outputName);
|
||||
}, [formatList]);
|
||||
|
||||
const ArraySelector = useMemo(() => {
|
||||
return (
|
||||
<MultipleRowArraySelect
|
||||
label={
|
||||
formatList.length > 0 ? (
|
||||
invalidList.length > 0 ? (
|
||||
<Grid py={3} gridTemplateColumns={'1fr 1fr'} gap={2} fontSize={'sm'}>
|
||||
{formatList.map(({ nodeName, outputName }, index) => {
|
||||
if (!nodeName || !outputName) return null;
|
||||
|
||||
{invalidList.map(({ nodeName, outputName }, index) => {
|
||||
return (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
@@ -325,7 +331,7 @@ const MultipleReferenceSelector = ({
|
||||
popDirection={popDirection}
|
||||
/>
|
||||
);
|
||||
}, [formatList, list, onSelect, placeholder, popDirection, value]);
|
||||
}, [invalidList, list, onSelect, placeholder, popDirection, value]);
|
||||
|
||||
return ArraySelector;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createContext } from 'use-context-selector';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
|
||||
import { useMemoizedFn } from 'ahooks';
|
||||
import { useCreation, useMemoizedFn } from 'ahooks';
|
||||
import React, { Dispatch, SetStateAction, ReactNode, useEffect, useMemo } from 'react';
|
||||
import { Edge, EdgeChange, Node, NodeChange, useEdgesState, useNodesState } from 'reactflow';
|
||||
|
||||
@@ -50,7 +50,7 @@ const WorkflowInitContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [nodes = [], setNodes, onNodesChange] = useNodesState<FlowNodeItemType>([]);
|
||||
const getNodes = useMemoizedFn(() => nodes);
|
||||
const nodeListString = JSON.stringify(nodes.map((node) => node.data));
|
||||
const nodeList = useMemo(
|
||||
const nodeList = useCreation(
|
||||
() => JSON.parse(nodeListString) as FlowNodeItemType[],
|
||||
[nodeListString]
|
||||
);
|
||||
@@ -73,7 +73,7 @@ const WorkflowInitContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
: item
|
||||
)
|
||||
);
|
||||
}, [edges.length]);
|
||||
}, [nodeList, edges.length]);
|
||||
|
||||
const actionContextValue = useMemo(
|
||||
() => ({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useDebounceEffect } from 'ahooks';
|
||||
import { useDebounceEffect, useMemoizedFn } from 'ahooks';
|
||||
import React, { ReactNode, useMemo, useRef, useState } from 'react';
|
||||
import { createContext, useContextSelector } from 'use-context-selector';
|
||||
import { WorkflowInitContext, WorkflowNodeEdgeContext } from './workflowInitContext';
|
||||
@@ -7,10 +7,18 @@ import { AppContext } from '../../context';
|
||||
import { compareSnapshot } from '@/web/core/workflow/utils';
|
||||
import { useBeforeunload } from '@fastgpt/web/hooks/useBeforeunload';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { getNodesBounds, Node } from 'reactflow';
|
||||
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||
import {
|
||||
Input_Template_Node_Height,
|
||||
Input_Template_Node_Width
|
||||
} from '@fastgpt/global/core/workflow/template/input';
|
||||
|
||||
type WorkflowStatusContextType = {
|
||||
isSaved: boolean;
|
||||
leaveSaveSign: React.MutableRefObject<boolean>;
|
||||
resetParentNodeSizeAndPosition: (parentId: string) => void;
|
||||
};
|
||||
|
||||
export const WorkflowStatusContext = createContext<WorkflowStatusContextType>({
|
||||
@@ -75,12 +83,72 @@ const WorkflowStatusContextProvider = ({ children }: { children: ReactNode }) =>
|
||||
}
|
||||
});
|
||||
|
||||
const onNodesChange = useContextSelector(WorkflowNodeEdgeContext, (state) => state.onNodesChange);
|
||||
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
|
||||
const resetParentNodeSizeAndPosition = useMemoizedFn((parentId: string) => {
|
||||
const { childNodes, loopNode } = nodes.reduce(
|
||||
(acc, node) => {
|
||||
if (node.data.parentNodeId === parentId) {
|
||||
acc.childNodes.push(node);
|
||||
}
|
||||
if (node.id === parentId) {
|
||||
acc.loopNode = node;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ childNodes: [] as Node[], loopNode: undefined as Node<FlowNodeItemType> | undefined }
|
||||
);
|
||||
|
||||
if (!loopNode) return;
|
||||
|
||||
const rect = getNodesBounds(childNodes);
|
||||
// Calculate parent node size with minimum width/height constraints
|
||||
const width = Math.max(rect.width + 80, 840);
|
||||
const height = Math.max(rect.height + 80, 600);
|
||||
|
||||
const offsetHeight =
|
||||
loopNode.data.inputs.find((input) => input.key === NodeInputKeyEnum.loopNodeInputHeight)
|
||||
?.value ?? 83;
|
||||
|
||||
// Update parentNode size and position
|
||||
onChangeNode({
|
||||
nodeId: parentId,
|
||||
type: 'updateInput',
|
||||
key: NodeInputKeyEnum.nodeWidth,
|
||||
value: {
|
||||
...Input_Template_Node_Width,
|
||||
value: width
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
nodeId: parentId,
|
||||
type: 'updateInput',
|
||||
key: NodeInputKeyEnum.nodeHeight,
|
||||
value: {
|
||||
...Input_Template_Node_Height,
|
||||
value: height
|
||||
}
|
||||
});
|
||||
// Update parentNode position
|
||||
onNodesChange([
|
||||
{
|
||||
id: parentId,
|
||||
type: 'position',
|
||||
position: {
|
||||
x: Math.round(rect.x - 70),
|
||||
y: Math.round(rect.y - offsetHeight - 240)
|
||||
}
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
const contextValue = useMemo(() => {
|
||||
return {
|
||||
isSaved,
|
||||
leaveSaveSign
|
||||
leaveSaveSign,
|
||||
resetParentNodeSizeAndPosition
|
||||
};
|
||||
}, [isSaved]);
|
||||
}, [isSaved, resetParentNodeSizeAndPosition]);
|
||||
return (
|
||||
<WorkflowStatusContext.Provider value={contextValue}>{children}</WorkflowStatusContext.Provider>
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user