Compare commits

...

26 Commits

Author SHA1 Message Date
Archer
554b2ca8dc perf: mcp tool type (#4820) 2025-05-15 18:14:32 +08:00
Archer
4e83840c14 perf: tool call check (#4818)
* i18n

* tool call

* fix: mcp create permission;Plugin unauth tip

* fix: mcp create permission;Plugin unauth tip

* fix: Cite modal permission

* remove invalide cite

* perf: prompt

* filter fulltext search

* fix: ts

* fix: ts

* fix: ts
2025-05-15 15:51:34 +08:00
heheer
a6c80684d1 fix version match (#4814) 2025-05-14 17:45:31 +08:00
Archer
a4db03a3b7 feat: session id (#4817)
* feat: session id

* feat: Add default index
2025-05-14 17:24:02 +08:00
Archer
cba8f773fe New license (#4809)
* feat: new-license

* perf: volumn watch

* Set use client
2025-05-14 13:55:09 +08:00
Archer
bd93f28d6f update doc (#4806) 2025-05-13 21:24:35 +08:00
Archer
2063cb6314 i18n (#4805)
* i18n

* version

* copy node
2025-05-13 18:58:57 +08:00
dreamer6680
12acaf491c change password rule (#4804)
* change password rule

* change password.tset.ts
2025-05-13 18:20:11 +08:00
heheer
3688842cc7 filter tool type version & fix unpublished version (#4803) 2025-05-13 17:58:51 +08:00
Archer
398d131bac fix api_dataset.md (#4791) (#4801)
Co-authored-by: dreamer6680 <1468683855@qq.com>
2025-05-13 12:28:50 +08:00
Archer
d5f188a1a4 doc (#4798)
* doc

* fix: i18n

* fix: scroll load
2025-05-13 12:16:32 +08:00
heheer
1edca309c4 remove system plugin node version (#4797) 2025-05-13 11:04:48 +08:00
Archer
1470c37ef1 Test media tag (#4796)
* feat: add html video tag convertion (#4784)

Co-authored-by: Zhenyi Wang <zhenyiwang@intl.zju.edu.cn>

* perf: media tag

---------

Co-authored-by: Zhenyi-Wang <47094597+Zhenyi-Wang@users.noreply.github.com>
Co-authored-by: Zhenyi Wang <zhenyiwang@intl.zju.edu.cn>
2025-05-13 10:46:49 +08:00
heheer
bdb1221d94 optimize editor default value code (#4794) 2025-05-12 23:52:22 +08:00
heheer
cac4b1d435 fix monaco editor default value (#4793)
* fix monaco editor default value

* fix
2025-05-12 23:09:15 +08:00
Archer
0ef3d40296 Test version (#4792)
* plugin node version select (#4760)

* plugin node version select

* type

* fix

* fix

* perf: version list

* fix node version (#4787)

* change my select

* fix-ui

* fix test

* add test

* fix

* remove invalid version field

* filter deprecated field

* fix: claude tool call

* fix: test

---------

Co-authored-by: heheer <heheer@sealos.io>
2025-05-12 22:27:01 +08:00
Theresa
3cc6b8a17a fix: improve handling of interactive node responses in workflow dispatch (#4786)
* fix: improve handling of interactive node responses in workflow dispatch

* fix: simplify interactive response handling in dispatch functions
2025-05-12 18:18:05 +08:00
Archer
681ec30c38 4.9.8 test (#4790)
* fix: doc url

* doc
2025-05-12 18:13:42 +08:00
Archer
24cd1c98dc Update official_account.md (#4789) 2025-05-12 17:01:18 +08:00
Archer
eaceabcc43 Update official_account.md (#4788) 2025-05-12 16:56:40 +08:00
dreamer6680
a7f9411dca feat: Update the system configuration type, add visibility controls for datasets and publishing channels (#4778) 2025-05-12 13:51:58 +08:00
Archer
657fa32217 feat: system config type;fix: retraining permission (#4772)
* feat: system config type

* fix: retraining permission
2025-05-08 22:09:55 +08:00
Archer
12d6948ba7 Feat: prelogin (#4773)
* add prelogin api (#4762)

* add prelogin api

* move type.d.ts

* perf: prelogin code

* doc

* fix: ts

---------

Co-authored-by: dreamer6680 <1468683855@qq.com>
2025-05-08 22:09:02 +08:00
Archer
83d54d046d perf: replace cite;perf: app card ui (#4768)
* perf: replace cite

* perf: app card ui

* fix: test
2025-05-08 13:35:08 +08:00
Archer
c75f154728 Password security policy (#4765)
* Psw (#4748)

* feat: 添加重置密码功能及相关接口

- 在用户模型中新增 passwordUpdateTime 字段以记录密码更新时间。
- 更新用户模式以支持密码更新时间的存储。
- 新增重置密码的模态框组件,允许用户重置密码。
- 实现重置密码的 API 接口,支持根据用户 ID 更新密码。
- 更新相关国际化文件,添加重置密码的提示信息。

* 更新国际化文件,添加重置密码相关提示信息,并优化重置密码模态框的实现。修复部分代码逻辑,确保用户体验流畅。

* 更新国际化文件,添加重置密码相关提示信息,优化重置密码模态框的实现,修复部分代码逻辑,确保用户体验流畅。新增获取用户密码更新时间的API接口,并调整相关逻辑以支持密码重置功能。

* update

* fix

* fix

* Added environment variables NEXT_PUBLIC_PASSWORD_UPDATETIME to support password update time configuration, update related logic to implement password mandatory update function, and optimize the implementation of reset password modal box to improve user experience.

* update index

* 更新用户密码重置功能,调整相关API接口,优化重置密码模态框的实现,确保用户体验流畅。修复部分代码逻辑,更新国际化提示信息。

* 删除获取用户密码更新时间的API接口,并在布局组件中移除不必要的重置密码模态框。优化代码结构,提升可维护性。

* update

* perf: reset expired password code

* perf: layout child components

* doc

* remove invalid env

* perf: update password code

---------

Co-authored-by: dreamer6680 <1468683855@qq.com>
2025-05-08 12:11:08 +08:00
Archer
96e7dd581e fix: json schema parse error;fix: retraining image reset (#4757)
* i18n

* fix: json schema parse error

* fix: retraining image reset

* update doc
2025-05-07 15:38:03 +08:00
223 changed files with 3738 additions and 2262 deletions

View File

@@ -132,15 +132,15 @@ services:
# fastgpt
sandbox:
container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt-sandbox:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.8 # 阿里云
networks:
- fastgpt
restart: always
fastgpt-mcp-server:
container_name: fastgpt-mcp-server
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.8 # 阿里云
ports:
- 3005:3000
networks:
@@ -150,8 +150,8 @@ services:
- FASTGPT_ENDPOINT=http://fastgpt:3000
fastgpt:
container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.8 # 阿里云
ports:
- 3000:3000
networks:

View File

@@ -109,15 +109,15 @@ services:
# fastgpt
sandbox:
container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt-sandbox:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.8 # 阿里云
networks:
- fastgpt
restart: always
fastgpt-mcp-server:
container_name: fastgpt-mcp-server
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.8 # 阿里云
ports:
- 3005:3000
networks:
@@ -127,8 +127,8 @@ services:
- FASTGPT_ENDPOINT=http://fastgpt:3000
fastgpt:
container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.8 # 阿里云
ports:
- 3000:3000
networks:

View File

@@ -96,15 +96,15 @@ services:
# fastgpt
sandbox:
container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt-sandbox:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.8 # 阿里云
networks:
- fastgpt
restart: always
fastgpt-mcp-server:
container_name: fastgpt-mcp-server
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.8 # 阿里云
ports:
- 3005:3000
networks:
@@ -114,8 +114,8 @@ services:
- FASTGPT_ENDPOINT=http://fastgpt:3000
fastgpt:
container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.8 # 阿里云
ports:
- 3000:3000
networks:

View File

@@ -72,15 +72,15 @@ services:
sandbox:
container_name: sandbox
image: ghcr.io/labring/fastgpt-sandbox:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt-sandbox:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.8 # 阿里云
networks:
- fastgpt
restart: always
fastgpt-mcp-server:
container_name: fastgpt-mcp-server
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.8 # 阿里云
ports:
- 3005:3000
networks:
@@ -90,8 +90,8 @@ services:
- FASTGPT_ENDPOINT=http://fastgpt:3000
fastgpt:
container_name: fastgpt
image: ghcr.io/labring/fastgpt:v4.9.7-fix2 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.7-fix2 # 阿里云
image: ghcr.io/labring/fastgpt:v4.9.8 # git
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.8 # 阿里云
ports:
- 3000:3000
networks:

View File

@@ -1,5 +1,5 @@
---
title: 'V4.9.8(进行中)'
title: 'V4.9.8'
description: 'FastGPT V4.9.8 更新说明'
icon: 'upgrade'
draft: false
@@ -7,6 +7,17 @@ toc: true
weight: 792
---
## 升级指南
### 1. 做好数据备份
### 2. 更新镜像 tag
- 更新 FastGPT 镜像 tag: v4.9.8
- 更新 FastGPT 商业版镜像 tag: v4.9.8
- mcp_server 无需更新
- Sandbox 无需更新
- AIProxy 无需更新
## 🚀 新增内容
@@ -14,14 +25,25 @@ weight: 792
2. 将所有内置任务,从非 stream 模式调整成 stream 模式,避免部分模型不支持非 stream 模式。如需覆盖,则可以在模型`额外 Body`参数中,强制指定`stream=false`
3. qwen3 模型预设
4. 语雀知识库支持设置根目录。
5. 可配置密码过期时间,过期后下次登录会强制要求修改密码。
6. 密码登录增加 preLogin 临时密钥校验。
7. 支持 Admin 后台配置发布渠道和第三方知识库的显示隐藏。
## ⚙️ 优化
1. Chat log list 优化,避免大数据时超出内存限制。
2. 预加载 token 计算 worker避免主任务中并发创建导致线程阻塞。
3. 工作流节点版本控制交互优化。
4. 网络获取以及 html2md 优化,支持视频和音频标签的转换。
## 🐛 修复
1. 应用列表/知识库列表,删除行权限展示问题。
2. 打开知识库搜索参数后,重排选项自动被打开。
3. LLM json_schema 模式 API 请求格式错误。
4. 重新训练时,图片过期索引未成功清除,导致图片会丢失。
5. 重新训练权限问题。
6. 文档链接地址。
7. Claude 工具调用,由于 index 为空,导致工具调用失败。
8. 嵌套工作流,工具调用下包含交互节点时,流程异常。

View File

@@ -0,0 +1,25 @@
---
title: 'V4.9.9(进行中)'
description: 'FastGPT V4.9.9 更新说明'
icon: 'upgrade'
draft: false
toc: true
weight: 791
---
## 🚀 新增内容
1. 切换 SessionId 来替代 JWT 实现登录鉴权,可控制最大登录客户端数量。
2. 新的商业版 License 管理模式。
## ⚙️ 优化
1. 优化工具调用,新工具的判断逻辑。
2. 调整 Cite 引用提示词。
## 🐛 修复
1. 无法正常获取应用历史保存/发布记录。
2. 成员创建 MCP 工具权限问题。
3. 来源引用展示,存在 ID 传递错误,导致提示无权操作该文件。

View File

@@ -43,7 +43,7 @@ type ResponseType = {
// 文件列表中,单项的文件类型
type FileListItem = {
id: string;
parentId: string | null;
parentId: string //也可能为 null 或者 undefined 类型;
name: string;
type: 'file' | 'folder';
updateTime: Date;
@@ -59,7 +59,7 @@ type FileListItem = {
{{< markdownify >}}
{{% alert icon=" " context="success" %}}
- parentId - 父级 id可选或者 null。
- parentId - 父级 id可选或者 null | undefined
- searchKey - 检索词,可选
{{% /alert %}}
@@ -68,7 +68,7 @@ curl --location --request POST '{{baseURL}}/v1/file/list' \
--header 'Authorization: Bearer {{authorization}}' \
--header 'Content-Type: application/json' \
--data-raw '{
"parentId": null,
"parentId": "",
"searchKey": ""
}'
```

View File

@@ -95,6 +95,10 @@ weight: 506
121.196.228.45
121.43.126.202
120.26.144.37
47.98.196.113
47.97.204.90
118.31.41.236
118.178.185.61
```
## 4. 获取AES Key选择加密方式
@@ -125,6 +129,11 @@ weight: 506
## FAQ
### 公众号没响应
检查应用对话日志,如果有对话日志,但是微信公众号无响应,则是白名单 IP未成功。
添加白名单IP 后,通常需要等待几分钟微信更新。
### 如何新开一个聊天记录
如果你想重置你的聊天记录,可以给机器人发送 `Reset` 消息(注意大小写),机器人会新开一个聊天记录。
如果你想重置你的聊天记录,可以给机器人发送 `Reset` 消息(注意大小写),机器人会新开一个聊天记录。

3
env.d.ts vendored
View File

@@ -4,7 +4,6 @@ declare global {
LOG_DEPTH: string;
DEFAULT_ROOT_PSW: string;
DB_MAX_LINK: string;
TOKEN_KEY: string;
FILE_TOKEN_KEY: string;
ROOT_KEY: string;
OPENAI_BASE_URL: string;
@@ -36,6 +35,8 @@ declare global {
SHOW_COUPON?: string;
CONFIG_JSON_PATH?: string;
PASSWORD_LOGIN_LOCK_SECONDS?: string;
PASSWORD_EXPIRED_MONTH?: string;
MAX_LOGIN_SESSION?: string;
}
}
}

View File

@@ -2,13 +2,28 @@ import { type ErrType } from '../errorCode';
import { i18nT } from '../../../../web/i18n/utils';
/* dataset: 509000 */
export enum SystemErrEnum {
communityVersionNumLimit = 'communityVersionNumLimit'
communityVersionNumLimit = 'communityVersionNumLimit',
licenseAppAmountLimit = 'licenseAppAmountLimit',
licenseDatasetAmountLimit = 'licenseDatasetAmountLimit',
licenseUserAmountLimit = 'licenseUserAmountLimit'
}
const systemErr = [
{
statusText: SystemErrEnum.communityVersionNumLimit,
message: i18nT('common:code_error.system_error.community_version_num_limit')
},
{
statusText: SystemErrEnum.licenseAppAmountLimit,
message: i18nT('common:code_error.system_error.license_app_amount_limit')
},
{
statusText: SystemErrEnum.licenseDatasetAmountLimit,
message: i18nT('common:code_error.system_error.license_dataset_amount_limit')
},
{
statusText: SystemErrEnum.licenseUserAmountLimit,
message: i18nT('common:code_error.system_error.license_user_amount_limit')
}
];

View File

@@ -5,7 +5,7 @@ export const checkPasswordRule = (password: string) => {
/[A-Z]/, // Contains uppercase letters
/[!@#$%^&*()_+=-]/ // Contains special characters
];
const validChars = /^[\dA-Za-z!@#$%^&*()_+=-]{6,100}$/;
const validChars = /^[\dA-Za-z!@#$%^&*()_+=-]{8,100}$/;
// Check length and valid characters
if (!validChars.test(password)) return false;

View File

@@ -1,7 +1,8 @@
export enum SystemConfigsTypeEnum {
fastgpt = 'fastgpt',
fastgptPro = 'fastgptPro',
systemMsgModal = 'systemMsgModal'
systemMsgModal = 'systemMsgModal',
license = 'license'
}
export const SystemConfigsTypeMap = {
@@ -13,5 +14,8 @@ export const SystemConfigsTypeMap = {
},
[SystemConfigsTypeEnum.systemMsgModal]: {
label: 'systemMsgModal'
},
[SystemConfigsTypeEnum.license]: {
label: 'license'
}
};

View File

@@ -64,6 +64,15 @@ export type FastGPTFeConfigsType = {
show_coupon?: boolean;
concatMd?: string;
show_dataset_feishu?: boolean;
show_dataset_yuque?: boolean;
show_publish_feishu?: boolean;
show_publish_dingtalk?: boolean;
show_publish_offiaccount?: boolean;
show_dataset_enhance?: boolean;
show_batch_eval?: boolean;
concatMd?: string;
docUrl?: string;
openAPIDocUrl?: string;
@@ -136,3 +145,21 @@ export type customPdfParseType = {
doc2xKey?: string;
price?: number;
};
export type LicenseDataType = {
startTime: string;
expiredTime: string;
company: string;
description?: string; // 描述
hosts?: string[]; // 管理端有效域名
maxUsers?: number; // 最大用户数,不填默认不上限
maxApps?: number; // 最大应用数,不填默认不上限
maxDatasets?: number; // 最大数据集数,不填默认不上限
functions: {
sso: boolean;
pay: boolean;
customTemplates: boolean;
datasetEnhance: boolean;
batchEval: boolean;
};
};

View File

@@ -2,6 +2,248 @@ import { type PromptTemplateItem } from '../type.d';
import { i18nT } from '../../../../web/i18n/utils';
import { getPromptByVersion } from './utils';
export const Prompt_userQuotePromptList: PromptTemplateItem[] = [
{
title: i18nT('app:template.standard_template'),
desc: '',
value: {
['4.9.7']: `## 任务描述
你是一个知识库回答助手,可以使用 <Cites></Cites> 中的内容作为你本次回答的参考。
同时,为了使回答结果更加可信并且可追溯,你需要在每段话结尾添加引用标记,标识参考了哪些内容。
## 追溯展示规则
- 使用 [id](CITE) 的格式来引用 <Cites></Cites> 中的知识,其中 CITE 是固定常量, id 为引文中的 id。
- 在 **每段话结尾** 自然地整合引用。例如: "Nginx是一款轻量级的Web服务器、反向代理服务器[67e517e74767063e882d6861](CITE)。"。
- 每段话**至少包含一个引用**,多个引用时按顺序排列,例如:"Nginx是一款轻量级的Web服务器、反向代理服务器[67e517e74767063e882d6861](CITE)[67e517e74767063e882d6862](CITE)。\n 它的特点是非常轻量[67e517e74767063e882d6863](CITE)。"
- 不要把示例作为知识点。
- 不要伪造 id返回的 id 必须都存在 <Cites></Cites> 中!
## 通用规则
- 如果你不清楚答案,你需要澄清。
- 避免提及你是从 <Cites></Cites> 获取的知识。
- 保持答案与 <Cites></Cites> 中描述的一致。
- 使用 Markdown 语法优化回答格式。尤其是图片、表格、序列号等内容,需严格完整输出。
- 使用与问题相同的语言回答。
<Cites>
{{quote}}
</Cites>
## 用户问题
{{question}}
## 回答
`
}
},
{
title: i18nT('app:template.qa_template'),
desc: '',
value: {
['4.9.7']: `## 任务描述
作为一个问答助手,你会使用 <QA></QA> 标记中的提供的数据对进行内容回答。
## 回答要求
- 选择其中一个或多个问答对进行回答。
- 回答的内容应尽可能与 <Answer></Answer> 中的内容一致。
- 如果没有相关的问答对,你需要澄清。
- 避免提及你是从 <QA></QA> 获取的知识,只需要回复答案。
- 使用与问题相同的语言回答。
<QA>
{{quote}}
</QA>
## 用户问题
{{question}}
## 回答
`
}
},
{
title: i18nT('app:template.standard_strict'),
desc: '',
value: {
['4.9.7']: `## 任务描述
你是一个知识库回答助手,可以使用 <Cites></Cites> 中的内容作为你本次回答的参考。
同时,为了使回答结果更加可信并且可追溯,你需要在每段话结尾添加引用标记,标识参考了哪些内容。
## 追溯展示规则
- 使用 [id](CITE) 的格式来引用 <Cites></Cites> 中的知识,其中 CITE 是固定常量, id 为引文中的 id。
- 在 **每段话结尾** 自然地整合引用。例如: "Nginx是一款轻量级的Web服务器、反向代理服务器[67e517e74767063e882d6861](CITE)。"。
- 每段话**至少包含一个引用**,多个引用时按顺序排列,例如:"Nginx是一款轻量级的Web服务器、反向代理服务器[67e517e74767063e882d6861](CITE)[67e517e74767063e882d6862](CITE)。\n 它的特点是非常轻量[67e517e74767063e882d6863](CITE)。"
- 不要把示例作为知识点。
- 不要伪造 id返回的 id 必须都存在 <Cites></Cites> 中!
## 通用规则
- 如果你不清楚答案,你需要澄清。
- 避免提及你是从 <Cites></Cites> 获取的知识。
- 保持答案与 <Cites></Cites> 中描述的一致。
- 使用 Markdown 语法优化回答格式。尤其是图片、表格、序列号等内容,需严格完整输出。
- 使用与问题相同的语言回答。
## 严格要求
你只能使用 <Cites></Cites> 标记中的内容作为参考,不能使用自身的知识,并且回答的内容需严格与 <Cites></Cites> 中的内容一致。
<Cites>
{{quote}}
</Cites>
## 用户问题
{{question}}
## 回答
`
}
},
{
title: i18nT('app:template.hard_strict'),
desc: '',
value: {
['4.9.7']: `## 任务描述
作为一个问答助手,你会使用 <QA></QA> 标记中的提供的数据对进行内容回答。
## 回答要求
- 选择其中一个或多个问答对进行回答。
- 回答的内容应尽可能与 <Answer></Answer> 中的内容一致。
- 如果没有相关的问答对,你需要澄清。
- 避免提及你是从 <QA></QA> 获取的知识,只需要回复答案。
- 使用与问题相同的语言回答。
## 严格要求
你只能使用 <QA></QA> 标记中的内容作为参考,不能使用自身的知识,并且回答的内容需严格与 <QA></QA> 中的内容一致。
<QA>
{{quote}}
</QA>
## 用户问题
{{question}}
## 回答
`
}
}
];
export const Prompt_systemQuotePromptList: PromptTemplateItem[] = [
{
title: i18nT('app:template.standard_template'),
desc: '',
value: {
['4.9.7']: `## 任务描述
你是一个知识库回答助手,可以使用 <Cites></Cites> 中的内容作为你本次回答的参考。
同时,为了使回答结果更加可信并且可追溯,你需要在每段话结尾添加引用标记,标识参考了哪些内容。
## 追溯展示规则
- 使用 [id](CITE) 的格式来引用 <Cites></Cites> 中的知识,其中 CITE 是固定常量, id 为引文中的 id。
- 在 **每段话结尾** 自然地整合引用。例如: "Nginx是一款轻量级的Web服务器、反向代理服务器[67e517e74767063e882d6861](CITE)。"。
- 每段话**至少包含一个引用**,多个引用时按顺序排列,例如:"Nginx是一款轻量级的Web服务器、反向代理服务器[67e517e74767063e882d6861](CITE)[67e517e74767063e882d6862](CITE)。\n 它的特点是非常轻量[67e517e74767063e882d6863](CITE)。"
- 不要把示例作为知识点。
- 不要伪造 id返回的 id 必须都存在 <Cites></Cites> 中!
## 通用规则
- 如果你不清楚答案,你需要澄清。
- 避免提及你是从 <Cites></Cites> 获取的知识。
- 保持答案与 <Cites></Cites> 中描述的一致。
- 使用 Markdown 语法优化回答格式。尤其是图片、表格、序列号等内容,需严格完整输出。
- 使用与问题相同的语言回答。
<Cites>
{{quote}}
</Cites>`
}
},
{
title: i18nT('app:template.qa_template'),
desc: '',
value: {
['4.9.8']: `## 任务描述
作为一个问答助手,你会使用 <QA></QA> 标记中的提供的数据对进行内容回答。
## 回答要求
- 选择其中一个或多个问答对进行回答。
- 回答的内容应尽可能与 <Answer></Answer> 中的内容一致。
- 如果没有相关的问答对,你需要澄清。
- 避免提及你是从 <QA></QA> 获取的知识,只需要回复答案。
- 使用与问题相同的语言回答。
<QA>
{{quote}}
</QA>`
}
},
{
title: i18nT('app:template.standard_strict'),
desc: '',
value: {
['4.9.7']: `## 任务描述
你是一个知识库回答助手,可以使用 <Cites></Cites> 中的内容作为你本次回答的参考。
同时,为了使回答结果更加可信并且可追溯,你需要在每段话结尾添加引用标记,标识参考了哪些内容。
## 追溯展示规则
- 使用 [id](CITE) 的格式来引用 <Cites></Cites> 中的知识,其中 CITE 是固定常量, id 为引文中的 id。
- 在 **每段话结尾** 自然地整合引用。例如: "Nginx是一款轻量级的Web服务器、反向代理服务器[67e517e74767063e882d6861](CITE)。"。
- 每段话**至少包含一个引用**,多个引用时按顺序排列,例如:"Nginx是一款轻量级的Web服务器、反向代理服务器[67e517e74767063e882d6861](CITE)[67e517e74767063e882d6862](CITE)。\n 它的特点是非常轻量[67e517e74767063e882d6863](CITE)。"
- 不要把示例作为知识点。
- 不要伪造 id返回的 id 必须都存在 <Cites></Cites> 中!
## 通用规则
- 如果你不清楚答案,你需要澄清。
- 避免提及你是从 <Cites></Cites> 获取的知识。
- 保持答案与 <Cites></Cites> 中描述的一致。
- 使用 Markdown 语法优化回答格式。尤其是图片、表格、序列号等内容,需严格完整输出。
- 使用与问题相同的语言回答。
## 严格要求
你只能使用 <Cites></Cites> 标记中的内容作为参考,不能使用自身的知识,并且回答的内容需严格与 <Cites></Cites> 中的内容一致。
<Cites>
{{quote}}
</Cites>`
}
},
{
title: i18nT('app:template.hard_strict'),
desc: '',
value: {
['4.9.7']: `## 任务描述
作为一个问答助手,你会使用 <QA></QA> 标记中的提供的数据对进行内容回答。
## 回答要求
- 选择其中一个或多个问答对进行回答。
- 回答的内容应尽可能与 <Answer></Answer> 中的内容一致。
- 如果没有相关的问答对,你需要澄清。
- 避免提及你是从 <QA></QA> 获取的知识,只需要回复答案。
- 使用与问题相同的语言回答。
## 严格要求
你只能使用 <QA></QA> 标记中的内容作为参考,不能使用自身的知识,并且回答的内容需严格与 <QA></QA> 中的内容一致。
<QA>
{{quote}}
</QA>`
}
}
];
export const Prompt_QuoteTemplateList: PromptTemplateItem[] = [
{
title: i18nT('app:template.standard_template'),
@@ -10,11 +252,6 @@ export const Prompt_QuoteTemplateList: PromptTemplateItem[] = [
['4.9.7']: `{
"id": "{{id}}",
"sourceName": "{{source}}",
"content": "{{q}}\n{{a}}"
}
`,
['4.9.2']: `{
"sourceName": "{{source}}",
"updateTime": "{{updateTime}}",
"content": "{{q}}\n{{a}}"
}
@@ -25,7 +262,7 @@ export const Prompt_QuoteTemplateList: PromptTemplateItem[] = [
title: i18nT('app:template.qa_template'),
desc: i18nT('app:template.qa_template_des'),
value: {
['4.9.2']: `<Question>
['4.9.7']: `<Question>
{{q}}
</Question>
<Answer>
@@ -40,11 +277,6 @@ export const Prompt_QuoteTemplateList: PromptTemplateItem[] = [
['4.9.7']: `{
"id": "{{id}}",
"sourceName": "{{source}}",
"content": "{{q}}\n{{a}}"
}
`,
['4.9.2']: `{
"sourceName": "{{source}}",
"updateTime": "{{updateTime}}",
"content": "{{q}}\n{{a}}"
}
@@ -55,7 +287,7 @@ export const Prompt_QuoteTemplateList: PromptTemplateItem[] = [
title: i18nT('app:template.hard_strict'),
desc: i18nT('app:template.hard_strict_des'),
value: {
['4.9.2']: `<Question>
['4.9.7']: `<Question>
{{q}}
</Question>
<Answer>
@@ -64,263 +296,12 @@ export const Prompt_QuoteTemplateList: PromptTemplateItem[] = [
}
}
];
export const getQuoteTemplate = (version?: string) => {
const defaultTemplate = Prompt_QuoteTemplateList[0].value;
return getPromptByVersion(version, defaultTemplate);
};
export const Prompt_userQuotePromptList: PromptTemplateItem[] = [
{
title: i18nT('app:template.standard_template'),
desc: '',
value: {
['4.9.7']: `使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
<Reference>
{{quote}}
</Reference>
回答要求:
- 如果你不清楚答案,你需要澄清。
- 避免提及你是从 <Reference></Reference> 获取的知识。
- 保持答案与 <Reference></Reference> 中描述的一致。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。
- 使用 [id](CITE) 格式来引用<Reference></Reference>中的知识,其中 CITE 是固定常量, id 为引文中的 id。
- 在每段结尾自然地整合引用。例如: "FastGPT 是一个基于大语言模型(LLM)的知识库问答系统[67e517e74767063e882d6861](CITE)。"
- 每段至少包含一个引用,也可根据内容需要加入多个引用,按顺序排列。`,
['4.9.2']: `使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
<Reference>
{{quote}}
</Reference>
回答要求:
- 如果你不清楚答案,你需要澄清。
- 避免提及你是从 <Reference></Reference> 获取的知识。
- 保持答案与 <Reference></Reference> 中描述的一致。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。
问题:"""{{question}}"""`
}
},
{
title: i18nT('app:template.qa_template'),
desc: '',
value: {
['4.9.2']: `使用 <QA></QA> 标记中的问答对进行回答。
<QA>
{{quote}}
</QA>
回答要求:
- 选择其中一个或多个问答对进行回答。
- 回答的内容应尽可能与 <答案></答案> 中的内容一致。
- 如果没有相关的问答对,你需要澄清。
- 避免提及你是从 QA 获取的知识,只需要回复答案。
问题:"""{{question}}"""`
}
},
{
title: i18nT('app:template.standard_strict'),
desc: '',
value: {
['4.9.7']: `忘记你已有的知识,仅使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
<Reference>
{{quote}}
</Reference>
思考流程:
1. 判断问题是否与 <Reference></Reference> 标记中的内容有关。
2. 如果有关,你按下面的要求回答。
3. 如果无关,你直接拒绝回答本次问题。
回答要求:
- 避免提及你是从 <Reference></Reference> 获取的知识。
- 保持答案与 <Reference></Reference> 中描述的一致。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。
- 使用 [id](CITE) 格式来引用<Reference></Reference>中的知识,其中 CITE 是固定常量, id 为引文中的 id。
- 在每段结尾自然地整合引用。例如: "FastGPT 是一个基于大语言模型(LLM)的知识库问答系统[67e517e74767063e882d6861](CITE)。"
- 每段至少包含一个引用,也可根据内容需要加入多个引用,按顺序排列。
问题:"""{{question}}"""`,
['4.9.2']: `忘记你已有的知识,仅使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
<Reference>
{{quote}}
</Reference>
思考流程:
1. 判断问题是否与 <Reference></Reference> 标记中的内容有关。
2. 如果有关,你按下面的要求回答。
3. 如果无关,你直接拒绝回答本次问题。
回答要求:
- 避免提及你是从 <Reference></Reference> 获取的知识。
- 保持答案与 <Reference></Reference> 中描述的一致。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。
问题:"""{{question}}"""`
}
},
{
title: i18nT('app:template.hard_strict'),
desc: '',
value: {
['4.9.2']: `忘记你已有的知识,仅使用 <QA></QA> 标记中的问答对进行回答。
<QA>
{{quote}}
</QA>
思考流程:
1. 判断问题是否与 <QA></QA> 标记中的内容有关。
2. 如果无关,你直接拒绝回答本次问题。
3. 判断是否有相近或相同的问题。
4. 如果有相同的问题,直接输出对应答案。
5. 如果只有相近的问题,请把相近的问题和答案一起输出。
回答要求:
- 如果没有相关的问答对,你需要澄清。
- 回答的内容应尽可能与 <QA></QA> 标记中的内容一致。
- 避免提及你是从 QA 获取的知识,只需要回复答案。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。
问题:"""{{question}}"""`
}
}
];
export const Prompt_systemQuotePromptList: PromptTemplateItem[] = [
{
title: i18nT('app:template.standard_template'),
desc: '',
value: {
['4.9.7']: `使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
<Reference>
{{quote}}
</Reference>
回答要求:
- 如果你不清楚答案,你需要澄清。
- 避免提及你是从 <Reference></Reference> 获取的知识。
- 保持答案与 <Reference></Reference> 中描述的一致。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。
- 使用 [id](CITE) 格式来引用<Reference></Reference>中的知识,其中 CITE 是固定常量, id 为引文中的 id。
- 在每段结尾自然地整合引用。例如: "FastGPT 是一个基于大语言模型(LLM)的知识库问答系统[67e517e74767063e882d6861](CITE)。"
- 每段至少包含一个引用,也可根据内容需要加入多个引用,按顺序排列。`,
['4.9.2']: `使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
<Reference>
{{quote}}
</Reference>
回答要求:
- 如果你不清楚答案,你需要澄清。
- 避免提及你是从 <Reference></Reference> 获取的知识。
- 保持答案与 <Reference></Reference> 中描述的一致。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。`
}
},
{
title: i18nT('app:template.qa_template'),
desc: '',
value: {
['4.9.2']: `使用 <QA></QA> 标记中的问答对进行回答。
<QA>
{{quote}}
</QA>
回答要求:
- 选择其中一个或多个问答对进行回答。
- 回答的内容应尽可能与 <答案></答案> 中的内容一致。
- 如果没有相关的问答对,你需要澄清。
- 避免提及你是从 QA 获取的知识,只需要回复答案。`
}
},
{
title: i18nT('app:template.standard_strict'),
desc: '',
value: {
['4.9.7']: `忘记你已有的知识,仅使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
<Reference>
{{quote}}
</Reference>
思考流程:
1. 判断问题是否与 <Reference></Reference> 标记中的内容有关。
2. 如果有关,你按下面的要求回答。
3. 如果无关,你直接拒绝回答本次问题。
回答要求:
- 避免提及你是从 <Reference></Reference> 获取的知识。
- 保持答案与 <Reference></Reference> 中描述的一致。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。
- 使用 [id](CITE) 格式来引用<Reference></Reference>中的知识,其中 CITE 是固定常量, id 为引文中的 id。
- 在每段结尾自然地整合引用。例如: "FastGPT 是一个基于大语言模型(LLM)的知识库问答系统[67e517e74767063e882d6861](CITE)。"
- 每段至少包含一个引用,也可根据内容需要加入多个引用,按顺序排列。
问题:"""{{question}}"""`,
['4.9.2']: `忘记你已有的知识,仅使用 <Reference></Reference> 标记中的内容作为本次对话的参考:
<Reference>
{{quote}}
</Reference>
思考流程:
1. 判断问题是否与 <Reference></Reference> 标记中的内容有关。
2. 如果有关,你按下面的要求回答。
3. 如果无关,你直接拒绝回答本次问题。
回答要求:
- 避免提及你是从 <Reference></Reference> 获取的知识。
- 保持答案与 <Reference></Reference> 中描述的一致。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。`
}
},
{
title: i18nT('app:template.hard_strict'),
desc: '',
value: {
['4.9.2']: `忘记你已有的知识,仅使用 <QA></QA> 标记中的问答对进行回答。
<QA>
{{quote}}
</QA>
思考流程:
1. 判断问题是否与 <QA></QA> 标记中的内容有关。
2. 如果无关,你直接拒绝回答本次问题。
3. 判断是否有相近或相同的问题。
4. 如果有相同的问题,直接输出对应答案。
5. 如果只有相近的问题,请把相近的问题和答案一起输出。
回答要求:
- 如果没有相关的问答对,你需要澄清。
- 回答的内容应尽可能与 <QA></QA> 标记中的内容一致。
- 避免提及你是从 QA 获取的知识,只需要回复答案。
- 使用 Markdown 语法优化回答格式。
- 使用与问题相同的语言回答。`
}
}
];
export const getQuotePrompt = (version?: string, role: 'user' | 'system' = 'user') => {
const quotePromptTemplates =
role === 'user' ? Prompt_userQuotePromptList : Prompt_systemQuotePromptList;
@@ -331,9 +312,9 @@ export const getQuotePrompt = (version?: string, role: 'user' | 'system' = 'user
};
// Document quote prompt
export const getDocumentQuotePrompt = (version: string) => {
export const getDocumentQuotePrompt = (version?: string) => {
const promptMap = {
['4.9.2']: `将 <FilesContent></FilesContent> 中的内容作为本次对话的参考:
['4.9.7']: `将 <FilesContent></FilesContent> 中的内容作为本次对话的参考:
<FilesContent>
{{quote}}
</FilesContent>

View File

@@ -1,14 +1,19 @@
export const getDatasetSearchToolResponsePrompt = () => {
return `## Role
你是一个知识库回答助手,可以 "quotes" 中的内容作为本次对话的参考。为了使回答结果更加可信并且可追溯,你需要在每段话结尾添加引用标记。
你是一个知识库回答助手,可以 "cites" 中的内容作为本次对话的参考。为了使回答结果更加可信并且可追溯,你需要在每段话结尾添加引用标记,标识参考了哪些内容
## Rules
## 追溯展示规则
- 使用 **[id](CITE)** 格式来引用 "cites" 中的知识,其中 CITE 是固定常量, id 为引文中的 id。
- 在 **每段话结尾** 自然地整合引用。例如: "Nginx是一款轻量级的Web服务器、反向代理服务器[67e517e74767063e882d6861](CITE)。"。
- 每段话**至少包含一个引用**,多个引用时按顺序排列,例如:"Nginx是一款轻量级的Web服务器、反向代理服务器[67e517e74767063e882d6861](CITE)[67e517e74767063e882d6862](CITE)。\n 它的特点是非常轻量[67e517e74767063e882d6863](CITE)。"
- 不要把示例作为知识点。
- 不要伪造 id返回的 id 必须都存在 cites 中!
## 通用规则
- 如果你不清楚答案,你需要澄清。
- 避免提及你是从 "quotes" 获取的知识。
- 保持答案与 "quotes" 中描述的一致。
- 避免提及你是从 "cites" 获取的知识。
- 保持答案与 "cites" 中描述的一致。
- 使用 Markdown 语法优化回答格式。尤其是图片、表格、序列号等内容,需严格完整输出。
- 使用与问题相同的语言回答。
- 使用 [id](CITE) 格式来引用 "quotes" 中的知识,其中 CITE 是固定常量, id 为引文中的 id。
- 在每段话结尾自然地整合引用。例如: "FastGPT 是一个基于大语言模型(LLM)的知识库问答系统[67e517e74767063e882d6861](CITE)。"
- 每段话至少包含一个引用,也可根据内容需要加入多个引用,按顺序排列。`;
- 使用与问题相同的语言回答。`;
};

View File

@@ -1,4 +1,5 @@
export const getPromptByVersion = (version?: string, promptMap: Record<string, string> = {}) => {
// 版本号大的在前面
const versions = Object.keys(promptMap).sort((a, b) => {
const [majorA, minorA, patchA] = a.split('.').map(Number);
const [majorB, minorB, patchB] = b.split('.').map(Number);
@@ -15,5 +16,5 @@ export const getPromptByVersion = (version?: string, promptMap: Record<string, s
if (version in promptMap) {
return promptMap[version];
}
return promptMap[versions[versions.length - 1]];
return promptMap[versions[0]];
};

View File

@@ -5,7 +5,7 @@ import {
FlowNodeTypeEnum
} from '../../workflow/node/constant';
import { nanoid } from 'nanoid';
import { type ToolType } from '../type';
import { type McpToolConfigType } from '../type';
import { i18nT } from '../../../../web/i18n/utils';
import { type RuntimeNodeItemType } from '../../workflow/runtime/type';
@@ -16,7 +16,7 @@ export const getMCPToolSetRuntimeNode = ({
avatar
}: {
url: string;
toolList: ToolType[];
toolList: McpToolConfigType[];
name?: string;
avatar?: string;
}): RuntimeNodeItemType => {
@@ -45,7 +45,7 @@ export const getMCPToolRuntimeNode = ({
url,
avatar = 'core/app/type/mcpToolsFill'
}: {
tool: ToolType;
tool: McpToolConfigType;
url: string;
avatar?: string;
}): RuntimeNodeItemType => {
@@ -65,7 +65,7 @@ export const getMCPToolRuntimeNode = ({
...Object.entries(tool.inputSchema?.properties || {}).map(([key, value]) => ({
key,
label: key,
valueType: value.type as WorkflowIOValueTypeEnum,
valueType: value.type as WorkflowIOValueTypeEnum, // TODO: 这里需要做一个映射
description: value.description,
toolDescription: value.description || key,
required: tool.inputSchema?.required?.includes(key) || false,

View File

@@ -16,16 +16,6 @@ import { FlowNodeInputTypeEnum } from '../../core/workflow/node/constant';
import type { WorkflowTemplateBasicType } from '@fastgpt/global/core/workflow/type';
import type { SourceMemberType } from '../../support/user/type';
export type ToolType = {
name: string;
description: string;
inputSchema: {
type: string;
properties?: Record<string, { type: string; description?: string }>;
required?: string[];
};
};
export type AppSchema = {
_id: string;
parentId?: ParentIdType;
@@ -117,6 +107,16 @@ export type AppSimpleEditFormType = {
chatConfig: AppChatConfigType;
};
export type McpToolConfigType = {
name: string;
description: string;
inputSchema: {
type: string;
properties?: Record<string, { type: string; description?: string }>;
required?: string[];
};
};
/* app chat config type */
export type AppChatConfigType = {
welcomeText?: string;

View File

@@ -9,6 +9,7 @@ import { type WorkflowTemplateBasicType } from '../workflow/type';
import { AppTypeEnum } from './constants';
import { AppErrEnum } from '../../common/error/code/app';
import { PluginErrEnum } from '../../common/error/code/plugin';
import { i18nT } from '../../../web/i18n/utils';
export const getDefaultAppForm = (): AppSimpleEditFormType => {
return {
@@ -189,7 +190,7 @@ export const getAppType = (config?: WorkflowTemplateBasicType | AppSimpleEditFor
return '';
};
export const checkAppUnExistError = (error?: string) => {
export const formatToolError = (error?: string) => {
const unExistError: Array<string> = [
AppErrEnum.unAuthApp,
AppErrEnum.unExist,
@@ -197,9 +198,9 @@ export const checkAppUnExistError = (error?: string) => {
PluginErrEnum.unExist
];
if (!!error && unExistError.includes(error)) {
return error;
if (error && unExistError.includes(error)) {
return i18nT('app:un_auth');
} else {
return undefined;
return error;
}
};

View File

@@ -218,7 +218,6 @@ export const FlowValueTypeMap: Record<
};
export const EDGE_TYPE = 'default';
export const defaultNodeVersion = '481';
export const chatHistoryValueDesc = `{
obj: System | Human | AI;
@@ -236,3 +235,10 @@ export const datasetQuoteValueDesc = `{
export const datasetSelectValueDesc = `{
datasetId: string;
}[]`;
export const AppNodeFlowNodeTypeMap: Record<any, boolean> = {
[FlowNodeTypeEnum.pluginModule]: true,
[FlowNodeTypeEnum.appModule]: true,
[FlowNodeTypeEnum.tool]: true,
[FlowNodeTypeEnum.toolSet]: true
};

View File

@@ -7,7 +7,7 @@ import type {
} from '../../chat/type';
import { NodeOutputItemType } from '../../chat/type';
import type { FlowNodeInputItemType, FlowNodeOutputItemType } from '../type/io.d';
import type { StoreNodeItemType } from '../type/node';
import type { NodeToolConfigType, StoreNodeItemType } from '../type/node';
import type { DispatchNodeResponseKeyEnum } from './constants';
import type { StoreEdgeItemType } from '../type/edge';
import type { NodeInputKeyEnum } from '../constants';
@@ -101,7 +101,10 @@ export type RuntimeNodeItemType = {
outputs: FlowNodeOutputItemType[];
pluginId?: string; // workflow id / plugin id
version: string;
version?: string;
// tool
toolConfig?: NodeToolConfigType;
};
export type RuntimeEdgeItemType = StoreEdgeItemType & {
@@ -114,7 +117,7 @@ export type DispatchNodeResponseType = {
runningTime?: number;
query?: string;
textOutput?: string;
error?: Record<string, any>;
error?: Record<string, any> | string;
customInputs?: Record<string, any>;
customOutputs?: Record<string, any>;
nodeInputs?: Record<string, any>;

View File

@@ -25,7 +25,6 @@ export const RunAppModule: FlowNodeTemplateType = {
name: i18nT('workflow:application_call'),
intro: i18nT('workflow:select_another_application_to_call'),
showStatus: true,
version: '481',
isTool: true,
inputs: [
{

View File

@@ -19,7 +19,6 @@ import {
Input_Template_UserChatInput,
Input_Template_File_Link
} from '../../input';
import { chatNodeSystemPromptTip, systemPromptTip } from '../../tip';
import { getHandleConfig } from '../../utils';
import { i18nT } from '../../../../../../web/i18n/utils';
@@ -54,7 +53,7 @@ export const AiChatModule: FlowNodeTemplateType = {
intro: i18nT('workflow:template.ai_chat_intro'),
showStatus: true,
isTool: true,
courseUrl: '/docs/guide/workbench/workflow/ai_chat/',
courseUrl: '/docs/guide/dashboard/workflow/ai_chat/',
version: '4.9.7',
inputs: [
Input_Template_SettingAiModel,
@@ -121,12 +120,7 @@ export const AiChatModule: FlowNodeTemplateType = {
valueType: WorkflowIOValueTypeEnum.string
},
// settings modal ---
{
...Input_Template_System_Prompt,
label: i18nT('common:core.ai.Prompt'),
description: systemPromptTip,
placeholder: chatNodeSystemPromptTip
},
Input_Template_System_Prompt,
Input_Template_History,
Input_Template_Dataset_Quote,
Input_Template_File_Link,

View File

@@ -17,8 +17,7 @@ export const AssignedAnswerModule: FlowNodeTemplateType = {
avatar: 'core/workflow/template/reply',
name: i18nT('workflow:assigned_reply'),
intro: i18nT('workflow:intro_assigned_reply'),
courseUrl: '/docs/guide/workbench/workflow/reply/',
version: '481',
courseUrl: '/docs/guide/dashboard/workflow/reply/',
isTool: true,
inputs: [
{

View File

@@ -31,7 +31,7 @@ export const ClassifyQuestionModule: FlowNodeTemplateType = {
intro: i18nT('workflow:intro_question_classification'),
showStatus: true,
version: '4.9.2',
courseUrl: '/docs/guide/workbench/workflow/question_classify/',
courseUrl: '/docs/guide/dashboard/workflow/question_classify/',
inputs: [
{
...Input_Template_SelectAIModel,

View File

@@ -26,7 +26,7 @@ export const ContextExtractModule: FlowNodeTemplateType = {
intro: i18nT('workflow:intro_text_content_extraction'),
showStatus: true,
isTool: true,
courseUrl: '/docs/guide/workbench/workflow/content_extract/',
courseUrl: '/docs/guide/dashboard/workflow/content_extract/',
version: '4.9.2',
inputs: [
{

View File

@@ -17,8 +17,7 @@ export const CustomFeedbackNode: FlowNodeTemplateType = {
avatar: 'core/workflow/template/customFeedback',
name: i18nT('workflow:custom_feedback'),
intro: i18nT('workflow:intro_custom_feedback'),
courseUrl: '/docs/guide/workbench/workflow/custom_feedback/',
version: '486',
courseUrl: '/docs/guide/dashboard/workflow/custom_feedback/',
inputs: [
{
key: NodeInputKeyEnum.textareaInput,

View File

@@ -42,8 +42,7 @@ export const DatasetConcatModule: FlowNodeTemplateType = {
intro: i18nT('workflow:intro_knowledge_base_search_merge'),
showStatus: false,
version: '486',
courseUrl: '/docs/guide/workbench/workflow/knowledge_base_search_merge/',
courseUrl: '/docs/guide/dashboard/workflow/knowledge_base_search_merge/',
inputs: [
{
key: NodeInputKeyEnum.datasetMaxTokens,

View File

@@ -30,7 +30,7 @@ export const DatasetSearchModule: FlowNodeTemplateType = {
intro: Dataset_SEARCH_DESC,
showStatus: true,
isTool: true,
courseUrl: '/docs/guide/workbench/workflow/dataset_search/',
courseUrl: '/docs/guide/dashboard/workflow/dataset_search/',
version: '4.9.2',
inputs: [
{

View File

@@ -27,8 +27,7 @@ export const HttpNode468: FlowNodeTemplateType = {
intro: i18nT('workflow:intro_http_request'),
showStatus: true,
isTool: true,
courseUrl: '/docs/guide/workbench/workflow/http/',
version: '481',
courseUrl: '/docs/guide/dashboard/workflow/http/',
inputs: [
{
...Input_Template_DynamicInput,

View File

@@ -23,8 +23,7 @@ export const IfElseNode: FlowNodeTemplateType = {
name: i18nT('workflow:condition_checker'),
intro: i18nT('workflow:execute_different_branches_based_on_conditions'),
showStatus: true,
courseUrl: '/docs/guide/workbench/workflow/tfswitch/',
version: '481',
courseUrl: '/docs/guide/dashboard/workflow/tfswitch/',
inputs: [
{
key: NodeInputKeyEnum.ifElseList,

View File

@@ -23,7 +23,6 @@ export const FormInputNode: FlowNodeTemplateType = {
name: i18nT('app:workflow.form_input'),
intro: i18nT(`app:workflow.form_input_tip`),
isTool: true,
version: '4811',
inputs: [
{
key: NodeInputKeyEnum.description,

View File

@@ -24,8 +24,7 @@ export const UserSelectNode: FlowNodeTemplateType = {
name: i18nT('app:workflow.user_select'),
intro: i18nT(`app:workflow.user_select_tip`),
isTool: true,
version: '489',
courseUrl: '/docs/guide/workbench/workflow/user-selection/',
courseUrl: '/docs/guide/dashboard/workflow/user-selection/',
inputs: [
{
key: NodeInputKeyEnum.description,

View File

@@ -32,8 +32,7 @@ export const LafModule: FlowNodeTemplateType = {
intro: i18nT('workflow:intro_laf_function_call'),
showStatus: true,
isTool: true,
courseUrl: '/docs/guide/workbench/workflow/laf/',
version: '481',
courseUrl: '/docs/guide/dashboard/workflow/laf/',
inputs: [
{
...Input_Template_DynamicInput,

View File

@@ -29,8 +29,7 @@ export const LoopNode: FlowNodeTemplateType = {
name: i18nT('workflow:loop'),
intro: i18nT('workflow:intro_loop'),
showStatus: true,
version: '4811',
courseUrl: '/docs/guide/workbench/workflow/loop/',
courseUrl: '/docs/guide/dashboard/workflow/loop/',
inputs: [
{
key: NodeInputKeyEnum.loopInputArray,

View File

@@ -19,7 +19,6 @@ export const LoopEndNode: FlowNodeTemplateType = {
avatar: 'core/workflow/template/loopEnd',
name: i18nT('workflow:loop_end'),
showStatus: false,
version: '4811',
inputs: [
{
key: NodeInputKeyEnum.loopEndInput,

View File

@@ -24,7 +24,6 @@ export const LoopStartNode: FlowNodeTemplateType = {
unique: true,
forbidDelete: true,
showStatus: false,
version: '4811',
inputs: [
{
key: NodeInputKeyEnum.loopStartInput,

View File

@@ -15,7 +15,6 @@ export const PluginConfigNode: FlowNodeTemplateType = {
intro: '',
unique: true,
forbidDelete: true,
version: '4811',
inputs: [],
outputs: []
};

View File

@@ -16,7 +16,6 @@ export const PluginInputModule: FlowNodeTemplateType = {
name: i18nT('workflow:plugin_input'),
intro: i18nT('workflow:intro_plugin_input'),
showStatus: false,
version: '481',
inputs: [],
outputs: []
};

View File

@@ -16,7 +16,6 @@ export const PluginOutputModule: FlowNodeTemplateType = {
name: i18nT('workflow:template.plugin_output'),
intro: i18nT('workflow:intro_custom_plugin_output'),
showStatus: false,
version: '481',
inputs: [],
outputs: []
};

View File

@@ -13,7 +13,6 @@ export const RunAppNode: FlowNodeTemplateType = {
name: '',
showStatus: false,
isTool: false,
version: '481',
inputs: [], // [{key:'pluginId'},...]
outputs: []
};

View File

@@ -13,7 +13,6 @@ export const RunPluginModule: FlowNodeTemplateType = {
name: '',
showStatus: false,
isTool: true,
version: '481',
inputs: [], // [{key:'pluginId'},...]
outputs: []
};

View File

@@ -13,7 +13,6 @@ export const RunToolNode: FlowNodeTemplateType = {
name: '',
showStatus: false,
isTool: true,
version: '4.9.6',
inputs: [],
outputs: []
};

View File

@@ -13,7 +13,6 @@ export const RunToolSetNode: FlowNodeTemplateType = {
name: '',
showStatus: false,
isTool: true,
version: '4.9.6',
inputs: [],
outputs: []
};

View File

@@ -26,8 +26,7 @@ export const CodeNode: FlowNodeTemplateType = {
name: i18nT('workflow:code_execution'),
intro: i18nT('workflow:execute_a_simple_script_code_usually_for_complex_data_processing'),
showStatus: true,
courseUrl: '/docs/guide/workbench/workflow/sandbox/',
version: '482',
courseUrl: '/docs/guide/dashboard/workflow/sandbox/',
inputs: [
{
...Input_Template_DynamicInput,

View File

@@ -13,7 +13,6 @@ export const StopToolNode: FlowNodeTemplateType = {
avatar: 'core/workflow/template/stopTool',
name: i18nT('workflow:tool_call_termination'),
intro: i18nT('workflow:intro_tool_call_termination'),
version: '481',
inputs: [],
outputs: []
};

View File

@@ -15,7 +15,6 @@ export const SystemConfigNode: FlowNodeTemplateType = {
intro: '',
unique: true,
forbidDelete: true,
version: '481',
inputs: [],
outputs: []
};

View File

@@ -23,8 +23,7 @@ export const TextEditorNode: FlowNodeTemplateType = {
avatar: 'core/workflow/template/textConcat',
name: i18nT('workflow:text_concatenation'),
intro: i18nT('workflow:intro_text_concatenation'),
courseUrl: '/docs/guide/workbench/workflow/text_editor/',
version: '4813',
courseUrl: '/docs/guide/dashboard/workflow/text_editor/',
inputs: [
{
key: NodeInputKeyEnum.textareaInput,

View File

@@ -13,7 +13,6 @@ export const ToolParamsNode: FlowNodeTemplateType = {
avatar: 'core/workflow/template/toolParams',
name: i18nT('workflow:tool_custom_field'),
intro: i18nT('workflow:intro_tool_params_config'),
version: '4811',
isTool: true,
inputs: [],
outputs: []

View File

@@ -32,7 +32,7 @@ export const ToolModule: FlowNodeTemplateType = {
name: i18nT('workflow:template.tool_call'),
intro: i18nT('workflow:template.tool_call_intro'),
showStatus: true,
courseUrl: '/docs/guide/workbench/workflow/tool/',
courseUrl: '/docs/guide/dashboard/workflow/tool/',
version: '4.9.2',
inputs: [
{

View File

@@ -19,8 +19,7 @@ export const VariableUpdateNode: FlowNodeTemplateType = {
intro: i18nT('workflow:update_specified_node_output_or_global_variable'),
showStatus: false,
isTool: true,
version: '481',
courseUrl: '/docs/guide/workbench/workflow/variable_update/',
courseUrl: '/docs/guide/dashboard/workflow/variable_update/',
inputs: [
{
key: NodeInputKeyEnum.updateList,

View File

@@ -30,7 +30,6 @@ export const WorkflowStart: FlowNodeTemplateType = {
intro: '',
forbidDelete: true,
unique: true,
version: '481',
inputs: [{ ...Input_Template_UserChatInput, toolDescription: i18nT('workflow:user_question') }],
outputs: [
{

View File

@@ -37,7 +37,10 @@ export type WorkflowTemplateType = {
intro?: string;
author?: string;
courseUrl?: string;
version: string;
version?: string;
versionLabel?: string;
isLatestVersion?: boolean;
showStatus?: boolean;
weight?: number;

View File

@@ -63,6 +63,8 @@ export type FlowNodeInputItemType = InputComponentPropsType & {
canSelectFile?: boolean;
canSelectImg?: boolean;
maxFiles?: number;
deprecated?: boolean;
};
export type FlowNodeOutputItemType = {
@@ -86,6 +88,8 @@ export type FlowNodeOutputItemType = {
// component params
customFieldConfig?: CustomFieldConfigType;
deprecated?: boolean;
};
export type ReferenceItemValueType = [string, string | undefined];

View File

@@ -20,11 +20,17 @@ import { RuntimeNodeItemType } from '../runtime/type';
import { PluginTypeEnum } from '../../plugin/constants';
import { RuntimeEdgeItemType, StoreEdgeItemType } from './edge';
import { NextApiResponse } from 'next';
import { AppDetailType, AppSchema } from '../../app/type';
import type { AppDetailType, AppSchema, McpToolConfigType } from '../../app/type';
import type { ParentIdType } from 'common/parentFolder/type';
import { AppTypeEnum } from 'core/app/constants';
import { AppTypeEnum } from '../../app/constants';
import type { WorkflowInteractiveResponseType } from '../template/system/interactive/type';
export type NodeToolConfigType = {
mcpTool?: McpToolConfigType & {
url: string;
};
};
export type FlowNodeCommonType = {
parentNodeId?: string;
flowNodeType: FlowNodeTypeEnum; // render node card
@@ -34,7 +40,10 @@ export type FlowNodeCommonType = {
name: string;
intro?: string; // template list intro
showStatus?: boolean; // chatting response step status
version: string;
version?: string;
versionLabel?: string; // Just ui show
isLatestVersion?: boolean; // Just ui show
// data
inputs: FlowNodeInputItemType[];
@@ -43,12 +52,14 @@ export type FlowNodeCommonType = {
// plugin data
pluginId?: string;
isFolder?: boolean;
// pluginType?: AppTypeEnum;
pluginData?: PluginDataType;
// tool data
toolData?: NodeToolConfigType;
};
export type PluginDataType = {
version: string;
version?: string;
diagram?: string;
userGuide?: string;
courseUrl?: string;

View File

@@ -9,6 +9,7 @@ import type { TeamMemberItemType } from './team/type';
export type PostLoginProps = {
username: string;
password: string;
code: string;
};
export type OauthLoginProps = {

View File

@@ -3,7 +3,8 @@ export enum UserAuthTypeEnum {
findPassword = 'findPassword',
wxLogin = 'wxLogin',
bindNotification = 'bindNotification',
captcha = 'captcha'
captcha = 'captcha',
login = 'login'
}
export const userAuthTypeMap = {
@@ -11,5 +12,6 @@ export const userAuthTypeMap = {
[UserAuthTypeEnum.findPassword]: 'findPassword',
[UserAuthTypeEnum.wxLogin]: 'wxLogin',
[UserAuthTypeEnum.bindNotification]: 'bindNotification',
[UserAuthTypeEnum.captcha]: 'captcha'
[UserAuthTypeEnum.captcha]: 'captcha',
[UserAuthTypeEnum.login]: 'login'
};

View File

@@ -0,0 +1,10 @@
import type { UserAuthTypeEnum } from '@fastgpt/global/support/user/auth/constants';
export type UserAuthSchemaType = {
key: string;
type: `${UserAuthTypeEnum}`;
code?: string;
openid?: string;
createTime: Date;
expiredTime: Date;
};

View File

@@ -14,6 +14,7 @@ export type UserModelSchema = {
timezone: string;
status: `${UserStatusEnum}`;
lastLoginTmbId?: string;
passwordUpdateTime?: Date;
fastgpt_sem?: {
keyword: string;
};

View File

@@ -149,7 +149,7 @@ export const readRawContentByFileBuffer = async ({
return await systemParse();
})();
addLog.debug(`Parse file success, time: ${Date.now() - start}ms. Uploading file image.`);
addLog.debug(`Parse file success, time: ${Date.now() - start}ms. `);
// markdown data format
if (imageList) {
@@ -185,7 +185,7 @@ export const readRawContentByFileBuffer = async ({
}
}
addLog.debug(`Upload file image success, time: ${Date.now() - start}ms`);
addLog.debug(`Upload file success, time: ${Date.now() - start}ms`);
return { rawText, formatText, imageList };
};

View File

@@ -1,5 +1,5 @@
import { jsonRes } from '../response';
import type { NextApiResponse } from 'next';
import type { NextApiRequest, NextApiResponse } from 'next';
import { withNextCors } from './cors';
import { type ApiRequestProps } from '../../type/next';
import { addLog } from '../system/log';
@@ -9,14 +9,21 @@ export type NextApiHandler<T = any> = (
res: NextApiResponse<T>
) => unknown | Promise<unknown>;
export const NextEntry = ({ beforeCallback = [] }: { beforeCallback?: Promise<any>[] }) => {
export const NextEntry = ({
beforeCallback = []
}: {
beforeCallback?: ((req: NextApiRequest, res: NextApiResponse) => Promise<any>)[];
}) => {
return (...args: NextApiHandler[]): NextApiHandler => {
return async function api(req: ApiRequestProps, res: NextApiResponse) {
const start = Date.now();
addLog.debug(`Request start ${req.url}`);
try {
await Promise.all([withNextCors(req, res), ...beforeCallback]);
await Promise.all([
withNextCors(req, res),
...beforeCallback.map((item) => item(req, res))
]);
let response = null;
for await (const handler of args) {

View File

@@ -1,7 +1,10 @@
import { getGlobalRedisCacheConnection } from './index';
import { getGlobalRedisConnection } from './index';
import { addLog } from '../system/log';
import { retryFn } from '@fastgpt/global/common/system/utils';
const redisPrefix = 'cache:';
const getCacheKey = (key: string) => `${redisPrefix}${key}`;
export enum CacheKeyEnum {
team_vector_count = 'team_vector_count'
}
@@ -13,12 +16,12 @@ export const setRedisCache = async (
) => {
return await retryFn(async () => {
try {
const redis = getGlobalRedisCacheConnection();
const redis = getGlobalRedisConnection();
if (expireSeconds) {
await redis.set(key, data, 'EX', expireSeconds);
await redis.set(getCacheKey(key), data, 'EX', expireSeconds);
} else {
await redis.set(key, data);
await redis.set(getCacheKey(key), data);
}
} catch (error) {
addLog.error('Set cache error:', error);
@@ -28,11 +31,11 @@ export const setRedisCache = async (
};
export const getRedisCache = async (key: string) => {
const redis = getGlobalRedisCacheConnection();
return await retryFn(() => redis.get(key));
const redis = getGlobalRedisConnection();
return await retryFn(() => redis.get(getCacheKey(key)));
};
export const delRedisCache = async (key: string) => {
const redis = getGlobalRedisCacheConnection();
await retryFn(() => redis.del(key));
const redis = getGlobalRedisConnection();
await retryFn(() => redis.del(getCacheKey(key)));
};

View File

@@ -27,17 +27,26 @@ export const newWorkerRedisConnection = () => {
return redis;
};
export const getGlobalRedisCacheConnection = () => {
if (global.redisCache) return global.redisCache;
export const FASTGPT_REDIS_PREFIX = 'fastgpt:';
export const getGlobalRedisConnection = () => {
if (global.redisClient) return global.redisClient;
global.redisCache = new Redis(REDIS_URL, { keyPrefix: 'fastgpt:cache:' });
global.redisClient = new Redis(REDIS_URL, { keyPrefix: FASTGPT_REDIS_PREFIX });
global.redisCache.on('connect', () => {
global.redisClient.on('connect', () => {
addLog.info('Redis connected');
});
global.redisCache.on('error', (error) => {
global.redisClient.on('error', (error) => {
addLog.error('Redis connection error', error);
});
return global.redisCache;
return global.redisClient;
};
export const getAllKeysByPrefix = async (key: string) => {
const redis = getGlobalRedisConnection();
const keys = (await redis.keys(`${FASTGPT_REDIS_PREFIX}${key}:*`)).map((key) =>
key.replace(FASTGPT_REDIS_PREFIX, '')
);
return keys;
};

View File

@@ -1,5 +1,5 @@
import type Redis from 'ioredis';
declare global {
var redisCache: Redis | null;
var redisClient: Redis | null;
}

View File

@@ -42,7 +42,7 @@ export const cheerioToHtml = ({
}
}
});
selectDom.find('img').each((i, el) => {
selectDom.find('img, video, source, audio, iframe').each((i, el) => {
const src = $(el).attr('src');
if (src) {
if (src.startsWith('//')) {

View File

@@ -2,26 +2,44 @@ import { SystemConfigsTypeEnum } from '@fastgpt/global/common/system/config/cons
import { MongoSystemConfigs } from './schema';
import { type FastGPTConfigFileType } from '@fastgpt/global/common/system/types';
import { FastGPTProUrl } from '../constants';
import { type LicenseDataType } from '@fastgpt/global/common/system/types';
export const getFastGPTConfigFromDB = async () => {
export const getFastGPTConfigFromDB = async (): Promise<{
fastgptConfig: FastGPTConfigFileType;
licenseData?: LicenseDataType;
}> => {
if (!FastGPTProUrl) {
return {
config: {} as FastGPTConfigFileType
fastgptConfig: {} as FastGPTConfigFileType
};
}
const res = await MongoSystemConfigs.findOne({
type: SystemConfigsTypeEnum.fastgpt
}).sort({
createTime: -1
});
const [fastgptConfig, licenseConfig] = await Promise.all([
MongoSystemConfigs.findOne({
type: SystemConfigsTypeEnum.fastgpt
}).sort({
createTime: -1
}),
MongoSystemConfigs.findOne({
type: SystemConfigsTypeEnum.license
}).sort({
createTime: -1
})
]);
const config = res?.value || {};
const config = fastgptConfig?.value || {};
const licenseData = licenseConfig?.value?.data as LicenseDataType | undefined;
const fastgptConfigTime = fastgptConfig?.createTime.getTime().toString();
const licenseConfigTime = licenseConfig?.createTime.getTime().toString();
// 利用配置文件的创建时间(更新时间)来做缓存,如果前端命中缓存,则不需要再返回配置文件
global.systemInitBufferId = res ? res.createTime.getTime().toString() : undefined;
global.systemInitBufferId = fastgptConfigTime
? `${fastgptConfigTime}-${licenseConfigTime}`
: undefined;
return {
config: config as FastGPTConfigFileType
fastgptConfig: config as FastGPTConfigFileType,
licenseData
};
};

View File

@@ -11,6 +11,7 @@ import type {
import { getLLMModel } from './model';
import { getLLMDefaultUsage } from '@fastgpt/global/core/ai/constants';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import json5 from 'json5';
/*
Count response max token
@@ -54,8 +55,6 @@ type InferCompletionsBody<T> = T extends { stream: true }
export const llmCompletionsBodyFormat = <T extends CompletionsBodyType>(
body: T & {
response_format?: any;
json_schema?: string;
stop?: string;
},
model: string | LLMModelItemType
@@ -65,8 +64,26 @@ export const llmCompletionsBodyFormat = <T extends CompletionsBodyType>(
return body as unknown as InferCompletionsBody<T>;
}
const response_format = body.response_format;
const json_schema = body.json_schema ?? undefined;
const response_format = (() => {
if (!body.response_format?.type) return undefined;
if (body.response_format.type === 'json_schema') {
try {
return {
type: 'json_schema',
json_schema: json5.parse(body.response_format?.json_schema as unknown as string)
};
} catch (error) {
throw new Error('Json schema error');
}
}
if (body.response_format.type) {
return {
type: body.response_format.type
};
}
return undefined;
})();
const stop = body.stop ?? undefined;
const requestBody: T = {
@@ -80,12 +97,7 @@ export const llmCompletionsBodyFormat = <T extends CompletionsBodyType>(
})
: undefined,
...modelData?.defaultConfig,
response_format: response_format
? {
type: response_format,
json_schema
}
: undefined,
response_format,
stop: stop?.split('|')
};
@@ -123,12 +135,14 @@ export const llmStreamResponseToAnswerText = async (
// Tool calls
if (responseChoice?.tool_calls?.length) {
responseChoice.tool_calls.forEach((toolCall) => {
const index = toolCall.index;
responseChoice.tool_calls.forEach((toolCall, i) => {
const index = toolCall.index ?? i;
if (toolCall.id || callingTool) {
// 有 id代表新 call 工具
if (toolCall.id) {
// Call new tool
const hasNewTool = toolCall?.function?.name || callingTool;
if (hasNewTool) {
// 有 function name代表新 call 工具
if (toolCall?.function?.name) {
callingTool = {
name: toolCall.function?.name || '',
arguments: toolCall.function?.arguments || ''
@@ -209,7 +223,9 @@ export const parseReasoningContent = (text: string): [string, string] => {
};
export const removeDatasetCiteText = (text: string, retainDatasetCite: boolean) => {
return retainDatasetCite ? text : text.replace(/\[([a-f0-9]{24})\]\(CITE\)/g, '');
return retainDatasetCite
? text.replace(/\[id\]\(CITE\)/g, '')
: text.replace(/\[([a-f0-9]{24})\](?:\([^\)]*\)?)?/g, '').replace(/\[id\]\(CITE\)/g, '');
};
// Parse llm stream part

View File

@@ -1,7 +1,7 @@
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import { type ToolType } from '@fastgpt/global/core/app/type';
import { type McpToolConfigType } from '@fastgpt/global/core/app/type';
import { addLog } from '../../common/system/log';
import { retryFn } from '@fastgpt/global/common/system/utils';
@@ -41,7 +41,7 @@ export class MCPClient {
* Get available tools list
* @returns List of tools
*/
public async getTools(): Promise<ToolType[]> {
public async getTools(): Promise<McpToolConfigType[]> {
try {
const client = await this.getConnection();
const response = await client.listTools();

View File

@@ -1,5 +1,5 @@
import { type FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node.d';
import { FlowNodeTypeEnum, defaultNodeVersion } from '@fastgpt/global/core/workflow/node/constant';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import {
appData2FlowNodeIO,
pluginData2FlowNodeIO,
@@ -14,10 +14,15 @@ import { cloneDeep } from 'lodash';
import { MongoApp } from '../schema';
import { type SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type';
import { getSystemPluginTemplates } from '../../../../plugins/register';
import { getAppLatestVersion, getAppVersionById } from '../version/controller';
import {
checkIsLatestVersion,
getAppLatestVersion,
getAppVersionById
} from '../version/controller';
import { type PluginRuntimeType } from '@fastgpt/global/core/plugin/type';
import { MongoSystemPlugin } from './systemPluginSchema';
import { PluginErrEnum } from '@fastgpt/global/common/error/code/plugin';
import { Types } from 'mongoose';
/*
plugin id rule:
@@ -90,20 +95,28 @@ const getSystemPluginTemplateById = async (
/* Format plugin to workflow preview node data */
export async function getChildAppPreviewNode({
id
appId,
versionId
}: {
id: string;
appId: string;
versionId?: string;
}): Promise<FlowNodeTemplateType> {
const app: ChildAppType = await (async () => {
const { source, pluginId } = await splitCombinePluginId(id);
const { source, pluginId } = await splitCombinePluginId(appId);
if (source === PluginSourceEnum.personal) {
const item = await MongoApp.findById(id).lean();
const item = await MongoApp.findById(appId).lean();
if (!item) return Promise.reject('plugin not found');
const version = await getAppLatestVersion(id, item);
const version = await getAppVersionById({ appId, versionId, app: item });
if (!version.versionId) return Promise.reject('App version not found');
const isLatest =
version.versionId && Types.ObjectId.isValid(version.versionId)
? await checkIsLatestVersion({
appId,
versionId: version.versionId
})
: true;
return {
id: String(item._id),
@@ -118,7 +131,11 @@ export async function getChildAppPreviewNode({
chatConfig: version.chatConfig
},
templateType: FlowNodeTemplateTypeEnum.teamApp,
version: version.versionId,
versionLabel: version?.versionName || '',
isLatestVersion: isLatest,
originCost: 0,
currentCost: 0,
hasTokenFee: false,
@@ -175,7 +192,11 @@ export async function getChildAppPreviewNode({
userGuide: app.userGuide,
showStatus: app.showStatus,
isTool: true,
version: app.version,
versionLabel: app.versionLabel,
isLatestVersion: app.isLatestVersion,
sourceHandle: isToolSet
? getHandleConfig(false, false, false, false)
: getHandleConfig(true, true, true, true),
@@ -224,7 +245,7 @@ export async function getChildAppRuntimeById(
templateType: FlowNodeTemplateTypeEnum.teamApp,
// 用不到
version: item?.pluginData?.nodeVersion || defaultNodeVersion,
version: item?.pluginData?.nodeVersion,
originCost: 0,
currentCost: 0,
hasTokenFee: false,

View File

@@ -119,6 +119,7 @@ const AppSchema = new Schema({
defaultPermission: Number
});
AppSchema.index({ type: 1 });
AppSchema.index({ teamId: 1, updateTime: -1 });
AppSchema.index({ teamId: 1, type: 1 });
AppSchema.index(

View File

@@ -1,8 +1,14 @@
import { MongoDataset } from '../dataset/schema';
import { getEmbeddingModel } from '../ai/model';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import {
AppNodeFlowNodeTypeMap,
FlowNodeTypeEnum
} from '@fastgpt/global/core/workflow/node/constant';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import { MongoAppVersion } from './version/schema';
import { checkIsLatestVersion } from './version/controller';
import { Types } from '../../common/mongo';
export async function listAppDatasetDataByTeamIdAndDatasetIds({
teamId,
@@ -35,6 +41,46 @@ export async function rewriteAppWorkflowToDetail({
}) {
const datasetIdSet = new Set<string>();
// Add node(App Type) versionlabel and latest sign
const appNodes = nodes.filter((node) => AppNodeFlowNodeTypeMap[node.flowNodeType]);
const versionIds = appNodes
.filter((node) => node.version && Types.ObjectId.isValid(node.version))
.map((node) => node.version);
if (versionIds.length > 0) {
const versionDataList = await MongoAppVersion.find(
{
_id: { $in: versionIds }
},
'_id versionName appId time'
).lean();
const versionMap: Record<string, any> = {};
const isLatestChecks = await Promise.all(
versionDataList.map(async (version) => {
const isLatest = await checkIsLatestVersion({
appId: version.appId,
versionId: version._id
});
return { versionId: String(version._id), isLatest };
})
);
const isLatestMap = new Map(isLatestChecks.map((item) => [item.versionId, item.isLatest]));
versionDataList.forEach((version) => {
versionMap[String(version._id)] = version;
});
appNodes.forEach((node) => {
if (!node.version) return;
const versionData = versionMap[String(node.version)];
if (versionData) {
node.versionLabel = versionData.versionName;
node.isLatestVersion = isLatestMap.get(String(node.version)) || false;
}
});
}
// Get all dataset ids from nodes
nodes.forEach((node) => {
if (node.flowNodeType !== FlowNodeTypeEnum.datasetSearchNode) return;

View File

@@ -15,6 +15,7 @@ export const getAppLatestVersion = async (appId: string, app?: AppSchema) => {
if (version) {
return {
versionId: version._id,
versionName: version.versionName,
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig || app?.chatConfig || {}
@@ -22,6 +23,7 @@ export const getAppLatestVersion = async (appId: string, app?: AppSchema) => {
}
return {
versionId: app?.pluginData?.nodeVersion,
versionName: app?.name,
nodes: app?.modules || [],
edges: app?.edges || [],
chatConfig: app?.chatConfig || {}
@@ -47,6 +49,7 @@ export const getAppVersionById = async ({
if (version) {
return {
versionId: version._id,
versionName: version.versionName,
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig || app?.chatConfig || {}
@@ -57,3 +60,22 @@ export const getAppVersionById = async ({
// If the version does not exist, the latest version is returned
return getAppLatestVersion(appId, app);
};
export const checkIsLatestVersion = async ({
appId,
versionId
}: {
appId: string;
versionId: string;
}) => {
const version = await MongoAppVersion.findOne(
{
appId,
isPublish: true,
_id: { $gt: versionId }
},
'_id'
).lean();
return !version;
};

View File

@@ -123,6 +123,7 @@ const DatasetSchema = new Schema({
try {
DatasetSchema.index({ teamId: 1 });
DatasetSchema.index({ type: 1 });
} catch (error) {
console.log(error);
}

View File

@@ -474,7 +474,7 @@ export async function searchDatasetData(
).lean()
]);
const set = new Map<string, number>();
const set = new Set<string>();
const formatResult = results
.map((item, index) => {
const collection = collections.find((col) => String(col._id) === String(item.collectionId));
@@ -507,7 +507,7 @@ export async function searchDatasetData(
.filter((item) => {
if (!item) return false;
if (set.has(item.id)) return false;
set.set(item.id, 1);
set.add(item.id);
return true;
})
.map((item, index) => {
@@ -648,7 +648,17 @@ export async function searchDatasetData(
]
};
})
.filter(Boolean) as SearchDataResponseItemType[],
.filter((item) => {
if (!item) return false;
return true;
})
.map((item, index) => {
if (!item) return;
return {
...item,
score: item.score.map((item) => ({ ...item, index }))
};
}) as SearchDataResponseItemType[],
tokenLen: 0
};
};

View File

@@ -235,8 +235,10 @@ export const runToolWithFunctionCall = async (
max_tokens,
top_p: aiChatTopP,
stop: aiChatStopSign,
response_format: aiChatResponseFormat,
json_schema: aiChatJsonSchema
response_format: {
type: aiChatResponseFormat as any,
json_schema: aiChatJsonSchema
}
},
toolModel
);

View File

@@ -47,6 +47,7 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
query,
requestOrigin,
chatConfig,
lastInteractive,
runningUserInfo,
externalProvider,
params: {
@@ -85,18 +86,7 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
});
// Check interactive entry
const interactiveResponse = (() => {
const lastHistory = chatHistories[chatHistories.length - 1];
if (isEntry && lastHistory?.obj === ChatRoleEnum.AI) {
const lastValue = lastHistory.value[lastHistory.value.length - 1];
if (
lastValue?.type === ChatItemValueTypeEnum.interactive &&
lastValue.interactive?.toolParams
) {
return lastValue.interactive;
}
}
})();
const interactiveResponse = lastInteractive;
props.node.isEntry = false;
const hasReadFilesTool = toolNodes.some(
(item) => item.flowNodeType === FlowNodeTypeEnum.readFiles

View File

@@ -243,8 +243,10 @@ export const runToolWithPromptCall = async (
max_tokens,
top_p: aiChatTopP,
stop: aiChatStopSign,
response_format: aiChatResponseFormat,
json_schema: aiChatJsonSchema
response_format: {
type: aiChatResponseFormat as any,
json_schema: aiChatJsonSchema
}
},
toolModel
);

View File

@@ -296,8 +296,10 @@ export const runToolWithToolChoice = async (
max_tokens,
top_p: aiChatTopP,
stop: aiChatStopSign,
response_format: aiChatResponseFormat,
json_schema: aiChatJsonSchema
response_format: {
type: aiChatResponseFormat as any,
json_schema: aiChatJsonSchema
}
},
toolModel
);
@@ -721,13 +723,14 @@ async function streamResponse({
}
// Parse tool calls
if (responseChoice?.tool_calls?.length) {
responseChoice.tool_calls.forEach((toolCall) => {
const index = toolCall.index;
responseChoice.tool_calls.forEach((toolCall, i) => {
const index = toolCall.index ?? i;
// Call new tool
if (toolCall.id || callingTool) {
// 有 id代表新 call 工具
if (toolCall.id) {
const hasNewTool = toolCall?.function?.name || callingTool;
if (hasNewTool) {
// 有 function name代表新 call 工具
if (toolCall?.function?.name) {
callingTool = {
name: toolCall.function?.name || '',
arguments: toolCall.function?.arguments || ''

View File

@@ -192,8 +192,10 @@ export const dispatchChatCompletion = async (props: ChatProps): Promise<ChatResp
max_tokens,
top_p: aiChatTopP,
stop: aiChatStopSign,
response_format: aiChatResponseFormat as any,
json_schema: aiChatJsonSchema
response_format: {
type: aiChatResponseFormat as any,
json_schema: aiChatJsonSchema
}
},
modelConstantsData
);
@@ -462,7 +464,7 @@ async function getChatMessages({
aiChatQuoteRole: AiChatQuoteRoleType; // user: replace user prompt; system: replace system prompt
datasetQuotePrompt?: string;
datasetQuoteText: string;
version: string;
version?: string;
useDatasetQuote: boolean;
histories: ChatItemType[];

View File

@@ -268,7 +268,7 @@ export async function dispatchDatasetSearch(
nodeDispatchUsages,
[DispatchNodeResponseKeyEnum.toolResponses]: {
prompt: getDatasetSearchToolResponsePrompt(),
quotes: searchRes.map((item) => ({
cites: searchRes.map((item) => ({
id: item.id,
sourceName: item.sourceName,
updateTime: item.updateTime,

View File

@@ -11,6 +11,7 @@ import type {
SystemVariablesType
} from '@fastgpt/global/core/workflow/runtime/type';
import type { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type.d';
import type { FlowNodeOutputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import type {
AIChatItemValueItemType,
ChatHistoryItemResType,
@@ -549,7 +550,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
// Skip some special key
if (
[NodeInputKeyEnum.childrenNodeIdList, NodeInputKeyEnum.httpJsonBody].includes(
input.key as any
input.key as NodeInputKeyEnum
)
) {
params[input.key] = input.value;

View File

@@ -8,7 +8,7 @@
"@xmldom/xmldom": "^0.8.10",
"@zilliz/milvus2-sdk-node": "2.4.2",
"axios": "^1.8.2",
"bullmq": "^5.44.0",
"bullmq": "^5.52.2",
"chalk": "^5.3.0",
"cheerio": "1.0.0-rc.12",
"cookie": "^0.7.1",

View File

@@ -20,6 +20,7 @@ import { type MemberGroupSchemaType } from '@fastgpt/global/support/permission/m
import { type TeamMemberSchema } from '@fastgpt/global/support/user/team/type';
import { type OrgSchemaType } from '@fastgpt/global/support/user/team/org/type';
import { getOrgIdSetWithParentByTmbId } from './org/controllers';
import { authUserSession } from '../user/session';
/** get resource permission for a team member
* If there is no permission for the team member, it will return undefined
@@ -213,51 +214,6 @@ export const delResourcePermission = ({
};
/* 下面代码等迁移 */
/* create token */
export function createJWT(user: {
_id?: string;
team?: { teamId?: string; tmbId: string };
isRoot?: boolean;
}) {
const key = process.env.TOKEN_KEY as string;
const token = jwt.sign(
{
userId: String(user._id),
teamId: String(user.team?.teamId),
tmbId: String(user.team?.tmbId),
isRoot: user.isRoot,
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7
},
key
);
return token;
}
// auth token
export function authJWT(token: string) {
return new Promise<{
userId: string;
teamId: string;
tmbId: string;
isRoot: boolean;
}>((resolve, reject) => {
const key = process.env.TOKEN_KEY as string;
jwt.verify(token, key, (err, decoded: any) => {
if (err || !decoded?.userId) {
reject(ERROR_ENUM.unAuthorization);
return;
}
resolve({
userId: decoded.userId,
teamId: decoded.teamId || '',
tmbId: decoded.tmbId,
isRoot: decoded.isRoot
});
});
});
}
export async function parseHeaderCert({
req,
@@ -275,7 +231,7 @@ export async function parseHeaderCert({
return Promise.reject(ERROR_ENUM.unAuthorization);
}
return await authJWT(cookieToken);
return authUserSession(cookieToken);
}
// from authorization get apikey
async function parseAuthorization(authorization?: string) {
@@ -345,6 +301,7 @@ export async function parseHeaderCert({
if (authToken && (token || cookie)) {
// user token(from fastgpt web)
const res = await authCookieToken(cookie, token);
return {
uid: res.userId,
teamId: res.teamId,

View File

@@ -54,23 +54,50 @@ export const checkTeamDatasetLimit = async (teamId: string) => {
})
]);
// User check
if (standardConstants && datasetCount >= standardConstants.maxDatasetAmount) {
return Promise.reject(TeamErrEnum.datasetAmountNotEnough);
}
// System check
if (global?.licenseData?.maxDatasets && typeof global?.licenseData?.maxDatasets === 'number') {
const totalDatasets = await MongoDataset.countDocuments({
type: { $ne: DatasetTypeEnum.folder }
});
if (totalDatasets >= global.licenseData.maxDatasets) {
return Promise.reject(SystemErrEnum.licenseDatasetAmountLimit);
}
}
// Open source check
if (!global.feConfigs.isPlus && datasetCount >= 30) {
return Promise.reject(SystemErrEnum.communityVersionNumLimit);
}
};
export const checkTeamAppLimit = async (teamId: string, amount = 1) => {
const [{ standardConstants }, appCount] = await Promise.all([
getTeamStandPlan({ teamId }),
MongoApp.countDocuments({
teamId,
type: { $in: [AppTypeEnum.simple, AppTypeEnum.workflow, AppTypeEnum.plugin] }
type: {
$in: [AppTypeEnum.simple, AppTypeEnum.workflow, AppTypeEnum.plugin, AppTypeEnum.tool]
}
})
]);
if (standardConstants && appCount + amount >= standardConstants.maxAppAmount) {
return Promise.reject(TeamErrEnum.appAmountNotEnough);
}
// System check
if (global?.licenseData?.maxApps && typeof global?.licenseData?.maxApps === 'number') {
const totalApps = await MongoApp.countDocuments({
type: {
$in: [AppTypeEnum.simple, AppTypeEnum.workflow, AppTypeEnum.plugin, AppTypeEnum.tool]
}
});
if (totalApps >= global.licenseData.maxApps) {
return Promise.reject(SystemErrEnum.licenseAppAmountLimit);
}
}
};

View File

@@ -0,0 +1,63 @@
import type { UserAuthTypeEnum } from '@fastgpt/global/support/user/auth/constants';
import { MongoUserAuth } from './schema';
import { i18nT } from '../../../../web/i18n/utils';
import { mongoSessionRun } from '../../../common/mongo/sessionRun';
export const addAuthCode = async ({
key,
code,
openid,
type,
expiredTime
}: {
key: string;
code?: string;
openid?: string;
type: `${UserAuthTypeEnum}`;
expiredTime?: Date;
}) => {
return MongoUserAuth.updateOne(
{
key,
type
},
{
code,
openid,
expiredTime
},
{
upsert: true
}
);
};
export const authCode = async ({
key,
type,
code
}: {
key: string;
type: `${UserAuthTypeEnum}`;
code: string;
}) => {
return mongoSessionRun(async (session) => {
const result = await MongoUserAuth.findOne(
{
key,
type,
code: { $regex: new RegExp(`^${code}$`, 'i') }
},
undefined,
{ session }
);
if (!result) {
return Promise.reject(i18nT('common:error.code_error'));
}
await result.deleteOne({ session });
return 'SUCCESS';
});
};

View File

@@ -0,0 +1,41 @@
import { connectionMongo, getMongoModel } from '../../../common/mongo';
const { Schema } = connectionMongo;
import type { UserAuthSchemaType } from '@fastgpt/global/support/user/auth/type';
import { userAuthTypeMap } from '@fastgpt/global/support/user/auth/constants';
import { addMinutes } from 'date-fns';
const UserAuthSchema = new Schema({
key: {
type: String,
required: true
},
code: {
// auth code
type: String,
length: 6
},
// wx openid
openid: String,
type: {
type: String,
enum: Object.keys(userAuthTypeMap),
required: true
},
createTime: {
type: Date,
default: () => new Date()
},
expiredTime: {
type: Date,
default: () => addMinutes(new Date(), 5)
}
});
try {
UserAuthSchema.index({ key: 1, type: 1 });
UserAuthSchema.index({ expiredTime: 1 }, { expireAfterSeconds: 0 });
} catch (error) {
console.log(error);
}
export const MongoUserAuth = getMongoModel<UserAuthSchemaType>('auth_codes', UserAuthSchema);

View File

@@ -3,6 +3,7 @@ const { Schema } = connectionMongo;
import { hashStr } from '@fastgpt/global/common/string/tools';
import type { UserModelSchema } from '@fastgpt/global/support/user/type';
import { UserStatusEnum, userStatusMap } from '@fastgpt/global/support/user/constant';
import { TeamMemberCollectionName } from '@fastgpt/global/support/user/team/constant';
export const userCollectionName = 'users';
@@ -18,9 +19,7 @@ const UserSchema = new Schema({
required: true,
unique: true // 唯一
},
phonePrefix: {
type: Number
},
phonePrefix: Number,
password: {
type: String,
required: true,
@@ -28,13 +27,14 @@ const UserSchema = new Schema({
get: (val: string) => hashStr(val),
select: false
},
passwordUpdateTime: Date,
createTime: {
type: Date,
default: () => new Date()
},
promotionRate: {
type: Number,
default: 15
default: 0
},
openaiAccount: {
type: {
@@ -47,7 +47,8 @@ const UserSchema = new Schema({
default: 'Asia/Shanghai'
},
lastLoginTmbId: {
type: Schema.Types.ObjectId
type: Schema.Types.ObjectId,
ref: TeamMemberCollectionName
},
inviterId: {

View File

@@ -0,0 +1,179 @@
import { retryFn } from '@fastgpt/global/common/system/utils';
import { getAllKeysByPrefix, getGlobalRedisConnection } from '../../common/redis';
import { addLog } from '../../common/system/log';
import { ERROR_ENUM } from '@fastgpt/global/common/error/errorCode';
import { getNanoid } from '@fastgpt/global/common/string/tools';
const redisPrefix = 'session:';
const getSessionKey = (key: string) => `${redisPrefix}${key}`;
type SessionType = {
userId: string;
teamId: string;
tmbId: string;
isRoot?: boolean;
createdAt: number;
ip?: string | null;
};
/* Session manager */
const setSession = async ({
key,
data,
expireSeconds
}: {
key: string;
data: SessionType;
expireSeconds: number;
}) => {
return await retryFn(async () => {
try {
const redis = getGlobalRedisConnection();
const formatKey = getSessionKey(key);
// 使用 hmset 存储对象字段
await redis.hmset(formatKey, {
userId: data.userId,
teamId: data.teamId,
tmbId: data.tmbId,
isRoot: data.isRoot ? '1' : '0',
createdAt: data.createdAt.toString(),
ip: data.ip
});
// 设置过期时间
if (expireSeconds) {
await redis.expire(formatKey, expireSeconds);
}
} catch (error) {
addLog.error('Set session error:', error);
return Promise.reject(error);
}
});
};
const delSession = (key: string) => {
const redis = getGlobalRedisConnection();
retryFn(() => redis.del(getSessionKey(key)));
};
const getSession = async (key: string): Promise<SessionType> => {
const formatKey = getSessionKey(key);
const redis = getGlobalRedisConnection();
// 使用 hgetall 获取所有字段
const data = await retryFn(() => redis.hgetall(formatKey));
if (!data || Object.keys(data).length === 0) {
return Promise.reject(ERROR_ENUM.unAuthorization);
}
try {
return {
userId: data.userId,
teamId: data.teamId,
tmbId: data.tmbId,
isRoot: data.isRoot === '1',
createdAt: parseInt(data.createdAt),
ip: data.ip
};
} catch (error) {
addLog.error('Parse session error:', error);
delSession(formatKey);
return Promise.reject(ERROR_ENUM.unAuthorization);
}
};
export const delUserAllSession = async (userId: string, whileList?: string[]) => {
const formatWhileList = whileList?.map((item) => getSessionKey(item));
const redis = getGlobalRedisConnection();
const keys = (await getAllKeysByPrefix(`${redisPrefix}${userId}`)).filter(
(item) => !formatWhileList?.includes(item)
);
if (keys.length > 0) {
await redis.del(keys);
}
};
// 会根据创建时间,删除超出客户端登录限制的 session
const delRedundantSession = async (userId: string) => {
// 至少为 1默认为 10
let maxSession = process.env.MAX_LOGIN_SESSION ? Number(process.env.MAX_LOGIN_SESSION) : 10;
if (maxSession < 1) {
maxSession = 1;
}
const redis = getGlobalRedisConnection();
const keys = await getAllKeysByPrefix(`${redisPrefix}${userId}`);
if (keys.length <= maxSession) {
return;
}
// 获取所有会话的创建时间
const sessionList = await Promise.all(
keys.map(async (key) => {
try {
const data = await redis.hgetall(key);
if (!data || Object.keys(data).length === 0) return null;
return {
key,
createdAt: parseInt(data.createdAt)
};
} catch (error) {
return null;
}
})
);
// 过滤掉无效数据并按创建时间排序
const validSessions = sessionList.filter(Boolean) as { key: string; createdAt: number }[];
validSessions.sort((a, b) => a.createdAt - b.createdAt);
// 删除最早创建的会话
const delKeys = validSessions.slice(0, validSessions.length - maxSession).map((item) => item.key);
if (delKeys.length > 0) {
await redis.del(delKeys);
}
};
export const createUserSession = async ({
userId,
teamId,
tmbId,
isRoot,
ip
}: {
userId: string;
teamId: string;
tmbId: string;
isRoot?: boolean;
ip?: string | null;
}) => {
const key = `${String(userId)}:${getNanoid(32)}`;
await setSession({
key,
data: {
userId: String(userId),
teamId: String(teamId),
tmbId: String(tmbId),
isRoot,
createdAt: new Date().getTime(),
ip
},
expireSeconds: 7 * 24 * 60 * 60
});
delRedundantSession(userId);
return key;
};
export const authUserSession = async (key: string): Promise<SessionType> => {
const data = await getSession(key);
return data;
};

View File

@@ -1,4 +1,8 @@
import type { FastGPTFeConfigsType, SystemEnvType } from '@fastgpt/global/common/system/types';
import type {
FastGPTFeConfigsType,
LicenseDataType,
SystemEnvType
} from '@fastgpt/global/common/system/types';
import {
TTSModelType,
RerankModelItemType,
@@ -17,6 +21,7 @@ declare global {
var feConfigs: FastGPTFeConfigsType;
var systemEnv: SystemEnvType;
var subPlans: SubPlanType | undefined;
var licenseData: LicenseDataType | undefined;
var workerPoll: Record<WorkerNameEnum, WorkerPool>;
}

View File

@@ -43,6 +43,24 @@ export const html2md = (
turndownService.remove(['i', 'script', 'iframe', 'style']);
turndownService.use(turndownPluginGfm.gfm);
// add custom handling for media tag
turndownService.addRule('media', {
filter: ['video', 'source', 'audio'],
replacement: function (content, node) {
const mediaNode = node as HTMLVideoElement | HTMLAudioElement | HTMLSourceElement;
const src = mediaNode.getAttribute('src');
const sources = mediaNode.getElementsByTagName('source');
const firstSourceSrc = sources.length > 0 ? sources[0].getAttribute('src') : null;
const mediaSrc = src || firstSourceSrc;
if (mediaSrc) {
return `[${mediaSrc}](${mediaSrc}) `;
}
return content;
}
});
// Base64 img to id, otherwise it will occupy memory when going to md
const { processedHtml, images } = processBase64Images(html);
const md = turndownService.turndown(processedHtml);

View File

@@ -6,7 +6,6 @@ import MyTooltip from '../MyTooltip';
type Props = FlexProps & {
icon: string;
size?: string;
onClick?: () => void;
hoverColor?: string;
hoverBg?: string;
hoverBorderColor?: string;
@@ -41,9 +40,9 @@ const MyIconButton = ({
color: hoverColor,
borderColor: hoverBorderColor
}}
onClick={() => {
onClick={(e) => {
if (isLoading) return;
onClick?.();
onClick?.(e);
}}
sx={{ userSelect: 'none' }}
{...props}

View File

@@ -15,7 +15,6 @@ import {
useDisclosure,
MenuButton,
Box,
css,
Flex,
Input
} from '@chakra-ui/react';
@@ -25,6 +24,7 @@ import { useRequest2 } from '../../../hooks/useRequest';
import MyDivider from '../MyDivider';
import type { useScrollPagination } from '../../../hooks/useScrollPagination';
import Avatar from '../Avatar';
import EmptyTip from '../EmptyTip';
/** 选择组件 Props 类型
* value: 选中的值
@@ -32,13 +32,16 @@ import Avatar from '../Avatar';
* list: 列表数据
* isLoading: 是否加载中
* ScrollData: 分页滚动数据控制器 [useScrollPagination] 的返回值
* customOnOpen: 自定义打开回调
* customOnClose: 自定义关闭回调
* */
export type SelectProps<T = any> = Omit<ButtonProps, 'onChange'> & {
value?: T;
valueLabel?: string | React.ReactNode;
placeholder?: string;
isSearch?: boolean;
list: {
alias?: string;
alias?: string | React.ReactNode;
icon?: string;
iconSize?: string;
label: string | React.ReactNode;
@@ -49,18 +52,36 @@ export type SelectProps<T = any> = Omit<ButtonProps, 'onChange'> & {
isLoading?: boolean;
onChange?: (val: T) => any | Promise<any>;
ScrollData?: ReturnType<typeof useScrollPagination>['ScrollData'];
customOnOpen?: () => void;
customOnClose?: () => void;
};
export const menuItemStyles: MenuItemProps = {
borderRadius: 'sm',
py: 2,
display: 'flex',
alignItems: 'center',
_hover: {
backgroundColor: 'myGray.100'
},
_notLast: {
mb: 1
}
};
const MySelect = <T = any,>(
{
placeholder,
value,
valueLabel,
isSearch = false,
width = '100%',
list = [],
onChange,
isLoading = false,
ScrollData,
customOnOpen,
customOnClose,
...props
}: SelectProps<T>,
ref: ForwardedRef<{
@@ -72,21 +93,19 @@ const MySelect = <T = any,>(
const SelectedItemRef = useRef<HTMLDivElement>(null);
const SearchInputRef = useRef<HTMLInputElement>(null);
const menuItemStyles: MenuItemProps = {
borderRadius: 'sm',
py: 2,
display: 'flex',
alignItems: 'center',
_hover: {
backgroundColor: 'myGray.100'
},
_notLast: {
mb: 1
}
};
const { isOpen, onOpen, onClose } = useDisclosure();
const { isOpen, onOpen: defaultOnOpen, onClose: defaultOnClose } = useDisclosure();
const selectItem = useMemo(() => list.find((item) => item.value === value), [list, value]);
const onOpen = () => {
defaultOnOpen();
customOnOpen?.();
};
const onClose = () => {
defaultOnClose();
customOnClose?.();
};
const [search, setSearch] = useState('');
const filterList = useMemo(() => {
if (!isSearch || !search) {
@@ -105,6 +124,7 @@ const MySelect = <T = any,>(
}
}));
// Auto scroll
useEffect(() => {
if (isOpen && MenuListRef.current && SelectedItemRef.current) {
const menu = MenuListRef.current;
@@ -117,51 +137,57 @@ const MySelect = <T = any,>(
}
}, [isSearch, isOpen]);
const { runAsync: onclickChange, loading } = useRequest2((val: T) => onChange?.(val));
const { runAsync: onClickChange, loading } = useRequest2((val: T) => onChange?.(val));
const ListRender = useMemo(() => {
return (
<>
{filterList.map((item, i) => (
<Box key={i}>
<MenuItem
{...menuItemStyles}
{...(value === item.value
? {
ref: SelectedItemRef,
color: 'primary.700',
bg: 'myGray.100',
fontWeight: '600'
{filterList.length > 0 ? (
filterList.map((item, i) => (
<Box key={i}>
<MenuItem
{...menuItemStyles}
{...(value === item.value
? {
ref: SelectedItemRef,
color: 'primary.700',
bg: 'myGray.100',
fontWeight: '600'
}
: {
color: 'myGray.900'
})}
onClick={() => {
if (value !== item.value) {
onClickChange(item.value);
}
: {
color: 'myGray.900'
})}
onClick={() => {
if (value !== item.value) {
onclickChange(item.value);
}
}}
whiteSpace={'pre-wrap'}
fontSize={'sm'}
display={'block'}
mb={0.5}
>
<Flex alignItems={'center'}>
{item.icon && <Avatar mr={2} src={item.icon as any} w={item.iconSize ?? '1rem'} />}
{item.label}
</Flex>
{item.description && (
<Box color={'myGray.500'} fontSize={'xs'}>
{item.description}
</Box>
)}
</MenuItem>
{item.showBorder && <MyDivider my={2} />}
</Box>
))}
}}
whiteSpace={'pre-wrap'}
fontSize={'sm'}
display={'block'}
mb={0.5}
>
<Flex alignItems={'center'}>
{item.icon && (
<Avatar mr={2} src={item.icon as any} w={item.iconSize ?? '1rem'} />
)}
{item.label}
</Flex>
{item.description && (
<Box color={'myGray.500'} fontSize={'xs'}>
{item.description}
</Box>
)}
</MenuItem>
{item.showBorder && <MyDivider my={2} />}
</Box>
))
) : (
<EmptyTip py={0} />
)}
</>
);
}, [filterList, value]);
}, [filterList, onClickChange, value]);
const isSelecting = loading || isLoading;
@@ -200,36 +226,48 @@ const MySelect = <T = any,>(
: {})}
{...props}
>
<Flex alignItems={'center'}>
{isSelecting && <MyIcon mr={2} name={'common/loading'} w={'1rem'} />}
{isSearch && isOpen ? (
<Input
ref={SearchInputRef}
autoFocus
variant={'unstyled'}
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder={
selectItem?.alias ||
(typeof selectItem?.label === 'string' ? selectItem?.label : placeholder)
}
size={'sm'}
w={'100%'}
color={'myGray.700'}
onBlur={() => {
setTimeout(() => {
SearchInputRef?.current?.focus();
}, 0);
}}
/>
) : (
<>
{selectItem?.icon && (
<Avatar mr={2} src={selectItem.icon as any} w={selectItem.iconSize ?? '1rem'} />
)}
{selectItem?.alias || selectItem?.label || placeholder}
</>
)}
<Flex alignItems={'center'} justifyContent="space-between" w="100%">
<Flex alignItems={'center'}>
{isSelecting && <MyIcon mr={2} name={'common/loading'} w={'1rem'} />}
{valueLabel ? (
<>{valueLabel}</>
) : (
<>
{isSearch && isOpen ? (
<Input
ref={SearchInputRef}
autoFocus
variant={'unstyled'}
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder={
(typeof selectItem?.alias === 'string' ? selectItem?.alias : '') ||
(typeof selectItem?.label === 'string' ? selectItem?.label : placeholder)
}
size={'sm'}
w={'100%'}
color={'myGray.700'}
onBlur={() => {
setTimeout(() => {
SearchInputRef?.current?.focus();
}, 0);
}}
/>
) : (
<>
{selectItem?.icon && (
<Avatar
mr={2}
src={selectItem.icon as any}
w={selectItem.iconSize ?? '1rem'}
/>
)}
{selectItem?.alias || selectItem?.label || placeholder}
</>
)}
</>
)}
</Flex>
</Flex>
</MenuButton>
@@ -252,7 +290,7 @@ const MySelect = <T = any,>(
'0px 2px 4px rgba(161, 167, 179, 0.25), 0px 0px 1px rgba(121, 141, 159, 0.25);'
}
zIndex={99}
maxH={'40vh'}
maxH={'45vh'}
overflowY={'auto'}
>
{ScrollData ? <ScrollData>{ListRender}</ScrollData> : ListRender}

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useCallback, useRef, useState } from 'react';
import React, { useEffect, useCallback, useRef, useState, useMemo } from 'react';
import Editor, { type Monaco, loader, useMonaco } from '@monaco-editor/react';
import { Box, type BoxProps } from '@chakra-ui/react';
import MyIcon from '../../Icon';
@@ -169,10 +169,26 @@ const JSONEditor = ({
document.addEventListener('mouseup', handleMouseUp);
}, []);
const formatedValue = useMemo(() => {
if (typeof value === 'string') {
return value;
}
if (value === undefined || value === null) {
return '';
}
if (typeof value === 'object') {
return JSON.stringify(value, null, 2);
}
return String(value);
}, [value]);
const onBlur = useCallback(() => {
if (!value) return;
if (!formatedValue) return;
// replace {{xx}} to true
const replaceValue = value?.replace(/{{(.*?)}}/g, 'true');
const replaceValue = formatedValue?.replace(/{{(.*?)}}/g, 'true');
try {
JSON.parse(replaceValue);
} catch (error) {
@@ -181,7 +197,8 @@ const JSONEditor = ({
title: t('common:json_parse_error')
});
}
}, [value]);
}, [formatedValue, toast, t]);
const beforeMount = useCallback((monaco: Monaco) => {
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: false,
@@ -241,7 +258,7 @@ const JSONEditor = ({
theme="JSONEditorTheme"
beforeMount={beforeMount}
defaultValue={defaultValue}
value={value}
value={formatedValue}
onChange={(e) => {
onChange?.(e || '');
if (!e) {

View File

@@ -9,12 +9,16 @@ export type UserBoxProps = {
sourceMember: SourceMemberType;
avatarSize?: string;
} & StackProps;
function UserBox({ sourceMember, avatarSize = '1.25rem', ...props }: UserBoxProps) {
const { t } = useTranslation();
return (
<HStack space="1" {...props}>
<Avatar src={sourceMember.avatar} w={avatarSize} />
<Box>{sourceMember.name}</Box>
<Box maxW={'150px'} whiteSpace={'nowrap'} overflow={'hidden'}>
{sourceMember.name}
</Box>
{sourceMember.status === 'leave' && <Tag color="gray">{t('common:user_leaved')}</Tag>}
</HStack>
);

View File

@@ -190,6 +190,7 @@ export function useScrollPagination<
params = {},
EmptyTip,
showErrorToast = true,
disalbed = false,
...props
}: {
scrollLoadType?: 'top' | 'bottom';
@@ -198,6 +199,7 @@ export function useScrollPagination<
params?: Record<string, any>;
EmptyTip?: React.JSX.Element;
showErrorToast?: boolean;
disalbed?: boolean;
} & Parameters<typeof useRequest2>[1]
) {
const { t } = useTranslation();
@@ -345,6 +347,7 @@ export function useScrollPagination<
// Reload data
useRequest2(
async () => {
if (disalbed) return;
loadData(true);
},
{

View File

@@ -33,6 +33,7 @@
"organization_name": "Organization name",
"payment_method": "Payment method",
"payway_coupon": "Redeem code",
"rerank": "Rerank",
"save": "save",
"save_failed": "Save exception",
"save_success": "Saved successfully",

View File

@@ -47,7 +47,7 @@
"package_usage_rules": "Package usage rules: The system will give priority to using more advanced packages, and the original unused packages will take effect later.",
"password": "Password",
"password_mismatch": "Password Inconsistency: Two passwords are inconsistent",
"password_tip": "Password must be at least 6 characters long and contain at least two combinations: numbers, letters, or special characters",
"password_tip": "Password must be at least 8 characters long and contain at least two combinations: numbers, letters, or special characters",
"password_update_error": "Exception when changing password",
"password_update_success": "Password changed successfully",
"pending_usage": "To be used",
@@ -55,6 +55,7 @@
"please_bind_notification_receiving_path": "Please bind the notification receiving method first",
"purchase_extra_package": "Upgrade",
"reminder_create_bound_notification_account": "Remind the creator to bind the notification account",
"reset_password": "reset password",
"resource_usage": "Usages",
"select_avatar": "Click to select avatar",
"standard_package_and_extra_resource_package": "Includes standard and extra plans",

View File

@@ -77,7 +77,7 @@
"owner": "owner",
"permission": "Permissions",
"permission_apikeyCreate": "Create API Key",
"permission_apikeyCreate_Tip": "Can create global APIKeys",
"permission_apikeyCreate_Tip": "You can create global APIKey and MCP services",
"permission_appCreate": "Create Application",
"permission_appCreate_tip": "Can create applications in the root directory (creation permissions in folders are controlled by the folder)",
"permission_datasetCreate": "Create Knowledge Base",

View File

@@ -1,4 +1,6 @@
{
"Click_to_delete_this_field": "Click to delete this field",
"Filed_is_deprecated": "This field is deprecated",
"MCP_tools_list_is_empty": "MCP tool not resolved",
"MCP_tools_parse_failed": "Failed to parse MCP address",
"MCP_tools_url": "MCP Address",
@@ -6,7 +8,6 @@
"MCP_tools_url_placeholder": "After filling in the MCP address, click Analysis",
"Role_setting": "Permission",
"Run": "Execute",
"team_tags_set": "Team tags",
"Team_Tags": "Team tags",
"ai_point_price": "Billing",
"ai_settings": "AI Configuration",
@@ -17,7 +18,6 @@
"app.modules.click to update": "Click to Refresh",
"app.modules.has new version": "New Version Available",
"app.modules.not_found": "Not Found",
"app.modules.not_found_tips": "This component cannot be found in the system, please delete it, otherwise the process will not run normally",
"app.version_current": "Current Version",
"app.version_initial": "Initial Version",
"app.version_name_tips": "Version name cannot be empty",
@@ -106,6 +106,7 @@
"no_mcp_tools_list": "No data yet, the MCP address needs to be parsed first",
"node_not_intro": "This node is not introduced",
"not_json_file": "Please select a JSON file",
"not_the_newest": "Not the latest",
"oaste_curl_string": "Enter CURL code",
"open_auto_execute": "Enable automatic execution",
"open_vision_function_tip": "Models with icon switches have image recognition capabilities. \nAfter being turned on, the model will parse the pictures in the file link and automatically parse the pictures in the user's question (user question ≤ 500 words).",
@@ -138,6 +139,7 @@
"stop_sign_placeholder": "Multiple serial numbers are separated by |, for example: aaa|stop",
"stream_response": "Stream",
"stream_response_tip": "Turning this switch off forces the model to use non-streaming mode and will not output content directly. \nIn the output of the AI reply, the content output by this model can be obtained for secondary processing.",
"team_tags_set": "Team tags",
"temperature": "Temperature",
"temperature_tip": "Range 0~10. \nThe larger the value, the more divergent the models answer is; the smaller the value, the more rigorous the answer.",
"template.hard_strict": "Strict Q&A template",
@@ -190,6 +192,7 @@
"type.error.Workflow data is empty": "No workflow data was obtained",
"type.error.workflowresponseempty": "Response content is empty",
"type_not_recognized": "App type not recognized",
"un_auth": "No permission",
"upload_file_max_amount": "Maximum File Quantity",
"upload_file_max_amount_tip": "Maximum number of files uploaded in a single round of conversation",
"variable.select type_desc": "You can define a global variable that does not need to be filled in by the user.\n\nThe value of this variable can come from the API interface, the Query of the shared link, or assigned through the [Variable Update] module.",

View File

@@ -1,7 +1,27 @@
{
"Action": "Action",
"Add": "Add",
"Add_new_input": "Add new input",
"All": "All",
"App": "Application",
"Cancel": "Cancel",
"Choose": "Choose",
"Click_to_expand": "Click to expand",
"Close": "Close",
"Code": "Code",
"Config": "Configuration",
"Confirm": "Confirm",
"Continue_Adding": "Continue adding",
"Copy": "Copy",
"Creating": "Creating",
"Delete": "Delete",
"Detail": "Detail",
"Documents": "Documents",
"Done": "Done",
"Download": "Download",
"Edit": "Edit",
"Error": "Error",
"Exit": "Exit",
"Export": "Export",
"FAQ.ai_point_a": "Each time an AI model is called, a certain amount of AI points will be consumed. \nFor specific calculation standards, please refer to the \"AI integral calculation standards\" above. \nThe system will give priority to the actual usage returned by the model manufacturer. If it is empty, the calculation method of GPT3.5 is used for estimation. 1Token≈0.7 Chinese characters ≈0.9 English words, and the characters that appear continuously may be considered as 1 Tokens.",
"FAQ.ai_point_expire_a": "Yes, they will expire. After the current package expires, the AI points will be reset to the new package's AI points. Annual package AI points are valid for one year, not monthly.",
@@ -9,7 +29,7 @@
"FAQ.ai_point_q": "What are AI points?",
"FAQ.check_subscription_a": "Go to Account - Personal Information - Package Details - Usage. You can view the effective and expiration dates of your subscribed packages. After the paid package expires, it will automatically switch to the free version.",
"FAQ.check_subscription_q": "Where can I view my subscribed packages?",
"FAQ.dataset_compute_a": "1 Dataset storage equals 1 Dataset index. A piece of Dataset data can contain one or more Dataset indexes. In enhanced training, 1 piece of data generates 5 indexes.",
"FAQ.dataset_compute_a": "1 knowledge base storage is equal to 1 knowledge base index. \nA single chunked data usually corresponds to multiple indexes. You can see \"n group indexes\" in a single knowledge base collection.",
"FAQ.dataset_compute_q": "How is Dataset storage calculated?",
"FAQ.dataset_index_a": "No, but if the Dataset index exceeds the limit, you cannot insert or update Dataset content.",
"FAQ.dataset_index_q": "Will the Dataset index be deleted if it exceeds the limit?",
@@ -19,36 +39,77 @@
"FAQ.package_overlay_q": "Can additional resource packs be stacked?",
"FAQ.switch_package_a": "The package usage rule is to prioritize the use of higher-level packages. Therefore, if the newly purchased package is higher than the current package, the new package will take effect immediately; otherwise, the current package will continue to be used.",
"FAQ.switch_package_q": "Will the subscription package be switched?",
"File": "File",
"Finish": "Finish",
"Folder": "Folder",
"FullScreen": "FullScreen",
"FullScreenLight": "FullScreenLight",
"Import": "Import",
"Input": "Input",
"Instructions": "Instruction",
"Intro": "Introduction",
"Loading": "Loading...",
"Login": "Login",
"More": "More",
"Move": "Move",
"Name": "Name",
"None": "None",
"OK": "OK",
"Open": "Open",
"Operation": "Operation",
"Other": "Other",
"Output": "Output",
"Params": "Parameters",
"Parse": "Analysis",
"Permission": "Permission",
"Permission_tip": "Individual permissions are greater than group permissions",
"Preview": "Preview",
"Remove": "Remove",
"Rename": "Rename",
"Required_input": "Required",
"Reset": "Reset",
"Restart": "Restart",
"Resume": "Resume",
"Role": "Permission",
"Run": "Run",
"Running": "Running",
"Save": "Save",
"Save_and_exit": "Save and Exit",
"Search": "Search",
"Select_all": "Select all",
"Setting": "Setting",
"Status": "Status",
"Submit": "Submit",
"Success": "Success",
"Team": "Team",
"UnKnow": "Unknown",
"Unlimited": "Unlimited",
"Update": "Update",
"Username": "Username",
"Waiting": "Waiting",
"Warning": "Warning",
"Website": "Website",
"action_confirm": "Confirm",
"add_new": "add_new",
"add_new_param": "Add new param",
"add_success": "Added Successfully",
"all_quotes": "All quotes",
"templateTags.Image_generation": "Image generation",
"templateTags.Office_services": "Office Services",
"templateTags.Roleplay": "role play",
"templateTags.Web_search": "Search online",
"templateTags.Writing": "Writing",
"all_result": "Full Results",
"app_not_version": "This application has not been published, please publish it first",
"back": "Back",
"base_config": "Basic Configuration",
"bill_already_processed": "Order has been processed",
"bill_expired": "Order expired",
"bill_not_pay_processed": "Non-online orders",
"button.extra_dataset_size_tip": "You are purchasing [Extra Knowledge Base Capacity]",
"button.extra_points_tip": "You are purchasing [Extra AI Points]",
"can_copy_content_tip": "It is not possible to copy automatically using the browser, please manually copy the following content",
"choosable": "Choosable",
"chose_condition": "Choose Condition",
"chosen": "Chosen",
"classification": "Classification",
"click_drag_tip": "Click to Drag",
"click_select_avatar": "Click to Select Avatar",
"click_to_copy": "Click to copy",
"click_to_resume": "Click to Resume",
"code_editor": "Code Editor",
@@ -87,6 +148,9 @@
"code_error.plugin_error.not_exist": "Plugin Does Not Exist",
"code_error.plugin_error.un_auth": "Unauthorized to Operate This Plugin",
"code_error.system_error.community_version_num_limit": "Exceeded Open Source Version Limit, Please Upgrade to Commercial Version: https://tryfastgpt.ai",
"code_error.system_error.license_app_amount_limit": "Exceed the maximum number of applications in the system",
"code_error.system_error.license_dataset_amount_limit": "Exceed the maximum number of knowledge bases in the system",
"code_error.system_error.license_user_amount_limit": "Exceed the maximum number of users in the system",
"code_error.team_error.ai_points_not_enough": "Insufficient AI Points",
"code_error.team_error.app_amount_not_enough": "Application Limit Reached",
"code_error.team_error.cannot_delete_default_group": "Cannot delete default group",
@@ -116,163 +180,21 @@
"code_error.user_error.balance_not_enough": "Insufficient Account Balance",
"code_error.user_error.bin_visitor_guest": "You Are Currently a Guest, Unauthorized to Operate",
"code_error.user_error.un_auth_user": "User Not Found",
"commercial_function_tip": "Please Upgrade to the Commercial Version to Use This Feature: https://doc.fastgpt.cn/docs/commercial/intro/",
"Action": "Action",
"Add": "Add",
"Add Success": "Added Successfully",
"Add_new_input": "Add new input",
"All": "All",
"Cancel": "Cancel",
"Choose": "Choose",
"Close": "Close",
"Code": "Code",
"Config": "Configuration",
"Confirm": "Confirm",
"comfirn_create": "Confirm Creation",
"comfirm_import": "comfirm_import",
"confirm_move": "Move Here",
"confirm_update": "confirm_update",
"comfirm_leave_page": "Confirm to Leave This Page?",
"Continue_Adding": "Continue adding",
"Copy": "Copy",
"copy_successful": "Copied Successfully",
"create_failed": "Creation Failed",
"create_success": "Created Successfully",
"create_time": "Creation Time",
"Creating": "Creating",
"custom_title": "Custom Title",
"Delete": "Delete",
"delete_failed": "Deletion Failed",
"delete_success": "Deleted Successfully",
"delete_warning": "Deletion Warning",
"delete_folder": "Delete Folder",
"Detail": "Detail",
"Documents": "Documents",
"Done": "Done",
"Edit": "Edit",
"Error": "Error",
"Exit": "Exit",
"exit_directly": "exit_directly",
"expired_time": "Expiration Time",
"File": "File",
"Finish": "Finish",
"FullScreen": "FullScreen",
"FullScreenLight": "FullScreenLight",
"Import": "Import",
"import_failed": "Import Failed",
"import_success": "Imported Successfully",
"Input": "Input",
"folder_description": "Folder Description",
"input_name": "Enter a Name",
"Intro": "Introduction",
"last_step": "Previous",
"last_use_time": "Last Use Time",
"load_failed": "load_failed",
"Loading": "Loading...",
"More": "More",
"no_select_data": "No Data Available",
"next_step": "Next",
"no_more_data": "No More Data",
"not_open": "Not Open",
"OK": "OK",
"Open": "Open",
"Other": "Other",
"Output": "Output",
"Params": "Parameters",
"Parse": "Analysis",
"psw_inconsistency": "Passwords Do Not Match",
"Permission": "Permission",
"Permission_tip": "Individual permissions are greater than group permissions",
"Please Input Name": "Please Enter a Name",
"Preview": "Preview",
"read_doc": "Read Document",
"Remove": "Remove",
"Rename": "Rename",
"request_error": "request_error",
"Reset": "Reset",
"Restart": "Restart",
"Role": "Permission",
"root_folder": "Root Folder",
"Run": "Run",
"Save": "Save",
"save_failed": "save_failed",
"save_success": "Saved Successfully",
"Save_and_exit": "Save and Exit",
"Search": "Search",
"select_file_failed": "File Selection Failed",
"select_template": "Select Template",
"set_avatar": "Click to set_avatar",
"Setting": "Setting",
"Status": "Status",
"submit_failed": "Submission Failed",
"Success": "Success",
"sync_success": "Synced Successfully",
"Team": "Team",
"un_used": "Unused",
"UnKnow": "Unknown",
"unknow_source": "Unknown Source",
"Unlimited": "Unlimited",
"Update": "Update",
"update_failed": "Update Failed",
"update_success": "Updated Successfully",
"Username": "Username",
"Waiting": "Waiting",
"Warning": "Warning",
"Website": "Website",
"all_result": "Full Results",
"click_select_avatar": "Click to Select Avatar",
"base_config": "Basic Configuration",
"choosable": "Choosable",
"action_confirm": "Confirm",
"copy_to_clipboard": "Copy to Clipboard",
"read_course": "Read Course",
"error.unKnow": "An Unexpected Error Occurred",
"export_to_json": "Export to JSON",
"failed": "Failed",
"click_drag_tip": "Click to Drag",
"move_success": "Moved Successfully",
"move_to": "Move to",
"no_child_folder": "No Subdirectories, Place Here",
"open_folder": "Open Folder",
"folder.empty": "No More Items in This Directory",
"folder.open_dataset": "Open Dataset",
"have_done": "Completed",
"input.Repeat Value": "Duplicate Value",
"is_requesting": "Requesting...",
"json_parse_error": "Possible JSON Error, Please Check Carefully",
"json_config": "JSON Configuration",
"link.UnValid": "Invalid Link",
"month": "Month",
"name_is_empty": "Name Cannot Be Empty",
"no_intro": "No Introduction Available",
"not_support": "Not Supported",
"page_center": "Page Center",
"redo_tip": "Redo ctrl shift z",
"redo_tip_mac": "Redo ⌘ shift z",
"request_end": "All Loaded",
"request_more": "Click to Load More",
"speech_error_tip": "Speech to Text Failed",
"speech_not_support": "Your Browser Does Not Support Speech Input",
"submit_success": "Submitted Successfully",
"submitted": "Submitted",
"support": "Support",
"system_help_chatbot": "Help Chatbot",
"use_helper": "Use Helper",
"ui.textarea.Magnifying": "Magnifying",
"undo_tip": "Undo ctrl z",
"undo_tip_mac": "Undo ⌘ z ",
"upload_file": "Upload File",
"zoomin_tip": "Zoom Out ctrl -",
"zoomin_tip_mac": "Zoom Out ⌘ -",
"zoomout_tip": "Zoom In ctrl +",
"zoomout_tip_mac": "Zoom In ⌘ +",
"comfirn_create": "Confirm Creation",
"commercial_function_tip": "Please Upgrade to the Commercial Version to Use This Feature: https://doc.fastgpt.cn/docs/commercial/intro/",
"comon.Continue_Adding": "Continue Adding",
"compliance.chat": "The content is generated by third-party AI and cannot be guaranteed to be true and accurate. It is for reference only.",
"compliance.compliance.dataset": "Please ensure that your content strictly complies with relevant laws and regulations and avoid containing any illegal or infringing content. \nPlease be careful when uploading materials that may contain sensitive information.",
"compliance.dataset": "Please ensure that your content strictly complies with relevant laws and regulations and avoid containing any illegal or infringing content. \nPlease be careful when uploading materials that may contain sensitive information.",
"confirm_choice": "Confirm Choice",
"confirm_move": "Move Here",
"confirm_update": "confirm_update",
"contact_way": "Notification Received",
"contribute_app_template": "Contribute Template",
"copy_successful": "Copied Successfully",
"copy_to_clipboard": "Copy to Clipboard",
"core.Chat": "Chat",
"core.ai.Max context": "Max Context",
"core.ai.Model": "Model",
@@ -492,7 +414,6 @@
"core.chat.response.user_select_result": "User Selection Result",
"core.chat.retry": "Regenerate",
"core.chat.tts.Stop Speech": "Stop",
"core.tip.leave page": "Content has been modified, confirm to leave the page?",
"core.dataset.Choose Dataset": "Associate Dataset",
"core.dataset.Collection": "Dataset",
"core.dataset.Create dataset": "Create a {{name}}",
@@ -567,7 +488,6 @@
"core.dataset.import.Custom text desc": "Manually enter a piece of text as a dataset",
"core.dataset.import.Data process params": "Data Processing Parameters",
"core.dataset.import.Down load csv template": "Click to Download CSV Template",
"core.dataset.import.import_success": "Import Successful, Please Wait for Training",
"core.dataset.import.Link name": "Web Link",
"core.dataset.import.Link name placeholder": "Only supports static links. If the data is empty after uploading, the link may not be readable\nEach line one, up to 10 links at a time",
"core.dataset.import.Local file": "Local File",
@@ -588,6 +508,7 @@
"core.dataset.import.Upload status": "Status",
"core.dataset.import.Web link": "Web Link",
"core.dataset.import.Web link desc": "Read static web page content as a dataset",
"core.dataset.import.import_success": "Import Successful, Please Wait for Training",
"core.dataset.link": "Link",
"core.dataset.search.Dataset Search Params": "Dataset Search Configuration",
"core.dataset.search.Empty result response": "Empty Search Response",
@@ -769,6 +690,7 @@
"core.plugin.Get Plugin Module Detail Failed": "Failed to Retrieve Plugin Information",
"core.plugin.Http plugin intro placeholder": "For display only, no actual effect",
"core.plugin.cost": "Points Consumption:",
"core.tip.leave page": "Content has been modified, confirm to leave the page?",
"core.view_chat_detail": "View Chat Details",
"core.workflow.Can not delete node": "This Node Cannot Be Deleted",
"core.workflow.Change input type tip": "Changing the input type will clear the filled values, please confirm!",
@@ -822,12 +744,17 @@
"core.workflow.value": "Value",
"core.workflow.variable": "Variable",
"create": "Create",
"create_failed": "Creation Failed",
"create_success": "Created Successfully",
"create_time": "Creation Time",
"cron_job_run_app": "Scheduled Task",
"custom_title": "Custom Title",
"data_index_custom": "Custom index",
"data_index_default": "Default index",
"data_index_image": "Image Index",
"data_index_question": "Inferred question index",
"data_index_summary": "Summary Index",
"data_not_found": "Data can't be found",
"dataset.Confirm move the folder": "Confirm to Move to This Directory",
"dataset.Confirm to delete the data": "Confirm to Delete This Data?",
"dataset.Confirm to delete the file": "Confirm to Delete This File and All Its Data?",
@@ -872,6 +799,10 @@
"dataset_text_model_tip": "Used for text processing in the knowledge base preprocessing stage, such as automatic supplementary indexing, Q&A pair extraction.",
"deep_rag_search": "In-depth search",
"delete_api": "Are you sure you want to delete this API key? \nAfter deletion, the key will become invalid immediately and the corresponding conversation log will not be deleted. Please confirm!",
"delete_failed": "Deletion Failed",
"delete_folder": "Delete Folder",
"delete_success": "Deleted Successfully",
"delete_warning": "Deletion Warning",
"embedding_model_not_config": "No index model is detected",
"error.Create failed": "Create failed",
"error.code_error": "Verification code error",
@@ -881,6 +812,7 @@
"error.missingParams": "Insufficient parameters",
"error.send_auth_code_too_frequently": "Please do not obtain verification code frequently",
"error.too_many_request": "Too many request",
"error.unKnow": "An Unexpected Error Occurred",
"error.upload_file_error_filename": "{{name}} Upload Failed",
"error.upload_image_error": "File upload failed",
"error.username_empty": "Account cannot be empty",
@@ -890,13 +822,23 @@
"error_llm_not_config": "Unconfigured file understanding model",
"error_un_permission": "No permission to operate",
"error_vlm_not_config": "Image comprehension model not configured",
"exit_directly": "exit_directly",
"expired_time": "Expiration Time",
"export_to_json": "Export to JSON",
"extraction_results": "Extraction Results",
"failed": "Failed",
"field_name": "Field Name",
"folder.empty": "No More Items in This Directory",
"folder.open_dataset": "Open Dataset",
"folder_description": "Folder Description",
"free": "Free",
"get_QR_failed": "Failed to Get QR Code",
"get_app_failed": "Failed to Retrieve App",
"get_laf_failed": "Failed to Retrieve Laf Function List",
"has_verification": "Verified, Click to Unbind",
"have_done": "Completed",
"import_failed": "Import Failed",
"import_success": "Imported Successfully",
"info.buy_extra": "Buy Extra Package",
"info.csv_download": "Click to Download Batch Test Template",
"info.csv_message": "Read the first column of the CSV file for batch testing, supporting up to 100 groups of data at a time.",
@@ -908,14 +850,23 @@
"info.open_api_notice": "You can fill in the relevant keys of OpenAI/OneAPI. If you fill in this content, the 'AI Chat', 'Question Classification', and 'Content Extraction' on the online platform will use the key you filled in and will not be charged. Please check if your key has access to the corresponding model. GPT models can choose FastAI.",
"info.open_api_placeholder": "Request address, default is the official OpenAI. You can fill in the transit address, 'v1' will not be automatically completed",
"info.resource": "Resource Usage",
"input.Repeat Value": "Duplicate Value",
"input_name": "Enter a Name",
"invalid_variable": "Invalid Variable",
"is_open": "Is Open",
"is_requesting": "Requesting...",
"is_using": "In Use",
"item_description": "Field Description",
"item_name": "Field Name",
"json_config": "JSON Configuration",
"json_parse_error": "Possible JSON Error, Please Check Carefully",
"just_now": "just",
"key_repetition": "Key Repetition",
"last_step": "Previous",
"last_use_time": "Last Use Time",
"link.UnValid": "Invalid Link",
"llm_model_not_config": "No language model was detected",
"load_failed": "load_failed",
"max_quote_tokens": "Quote cap",
"max_quote_tokens_tips": "The maximum number of tokens in a single search, about 1 character in Chinese = 1.7 tokens, and about 1 character in English = 1 token",
"mcp_server": "MCP Services",
@@ -948,7 +899,11 @@
"model_sparkdesk": "SprkDesk",
"model_stepfun": "StepFun",
"model_yi": "Yi",
"month": "Month",
"move.confirm": "Confirm move",
"move_success": "Moved Successfully",
"move_to": "Move to",
"name_is_empty": "Name Cannot Be Empty",
"navbar.Account": "Account",
"navbar.Chat": "Chat",
"navbar.Datasets": "Dataset",
@@ -956,13 +911,22 @@
"navbar.Toolkit": "Toolkit",
"navbar.Tools": "Tools",
"new_create": "Create New",
"next_step": "Next",
"no": "No",
"no_child_folder": "No Subdirectories, Place Here",
"no_intro": "No Introduction Available",
"no_laf_env": "System Not Configured with Laf Environment",
"no_more_data": "No More Data",
"no_pay_way": "There is no suitable payment channel in the system",
"no_select_data": "No Data Available",
"not_model_config": "No related model configured",
"not_open": "Not Open",
"not_permission": "The current subscription package does not support team operation logs",
"not_support": "Not Supported",
"not_yet_introduced": "No Introduction Yet",
"open_folder": "Open Folder",
"option": "Option",
"page_center": "Page Center",
"pay.amount": "Amount",
"pay.error_desc": "There was a problem when converting payment routes",
"pay.noclose": "After payment is completed, please wait for the system to update automatically",
@@ -1001,6 +965,7 @@
"permission.manager": "administrator",
"permission.read": "Read permission",
"permission.write": "write permission",
"please_input_name": "Please Enter a Name",
"plugin.App": "Select App",
"plugin.Currentapp": "Current App",
"plugin.Description": "Description",
@@ -1025,14 +990,34 @@
"plugin.path": "Path",
"price_over_wx_limit": "Exceed payment provider limit: WeChat Pay only supports less than 6,000 yuan",
"prompt_input_placeholder": "Please enter the prompt word",
"psw_inconsistency": "Passwords Do Not Match",
"question_feedback": "Work order",
"read_course": "Read Course",
"read_doc": "Read Document",
"read_quote": "View citations",
"redo_tip": "Redo ctrl shift z",
"redo_tip_mac": "Redo ⌘ shift z",
"request_end": "All Loaded",
"request_error": "request_error",
"request_more": "Click to Load More",
"required": "Required",
"rerank_weight": "Rearrange weights",
"resume_failed": "Resume Failed",
"root_folder": "Root Folder",
"save_failed": "save_failed",
"save_success": "Saved Successfully",
"scan_code": "Scan the QR code to pay",
"select_file_failed": "File Selection Failed",
"select_reference_variable": "Select Reference Variable",
"select_template": "Select Template",
"set_avatar": "Click to set_avatar",
"share_link": "Share Link",
"speech_error_tip": "Speech to Text Failed",
"speech_not_support": "Your Browser Does Not Support Speech Input",
"submit_failed": "Submission Failed",
"submit_success": "Submitted Successfully",
"submitted": "Submitted",
"support": "Support",
"support.account.Individuation": "Personalization",
"support.inform.Read": "Read",
"support.openapi.Api baseurl": "API Base URL",
@@ -1210,18 +1195,34 @@
"support.wallet.usage.Usage Detail": "Usage Details",
"support.wallet.usage.Whisper": "Voice Input",
"sync_link": "Sync Link",
"sync_success": "Synced Successfully",
"system.Concat us": "Contact Us",
"system.Help Document": "Help Document",
"system_help_chatbot": "Help Chatbot",
"tag_list": "Tag List",
"team_tag": "Team Tag",
"templateTags.Image_generation": "Image generation",
"templateTags.Office_services": "Office Services",
"templateTags.Roleplay": "role play",
"templateTags.Web_search": "Search online",
"templateTags.Writing": "Writing",
"template_market": "Template Market",
"textarea_variable_picker_tip": "Enter \"/\" to select a variable",
"ui.textarea.Magnifying": "Magnifying",
"un_used": "Unused",
"unauth_token": "The certificate has expired, please log in again",
"undo_tip": "Undo ctrl z",
"undo_tip_mac": "Undo ⌘ z ",
"unit.character": "Character",
"unit.minute": "Minute",
"unit.seconds": "Second",
"unknow_source": "Unknown Source",
"unusable_variable": "No Usable Variables",
"update_failed": "Update Failed",
"update_success": "Updated Successfully",
"upload_file": "Upload File",
"upload_file_error": "File Upload Failed",
"use_helper": "Use Helper",
"user.Account": "Account",
"user.Amount of earnings": "Earnings (¥)",
"user.Amount of inviter": "Total Number of Invites",
@@ -1235,11 +1236,13 @@
"user.Laf Account Setting": "Laf Account Configuration",
"user.Language": "Language",
"user.Member Name": "Nickname",
"user.No_right_to_reset_password": "You do not have the right to reset the password",
"user.Notification Receive": "Notification Receive",
"user.Notification Receive Bind": "Please bind the notification receive method first",
"user.Old password is error": "Old Password is Incorrect",
"user.OpenAI Account Setting": "OpenAI Account Configuration",
"user.Password": "Password",
"user.Password has no change": "New password is the same as the old password",
"user.Pay": "Recharge",
"user.Promotion": "Promotion",
"user.Promotion Rate": "Cashback Rate",
@@ -1254,12 +1257,16 @@
"user.Update password successful": "Password Updated Successfully",
"user.apikey.key": "API Key",
"user.confirm_password": "Confirm Password",
"user.init_password": "Please initialize password",
"user.new_password": "New Password",
"user.no_invite_records": "No Invite Records",
"user.no_notice": "No Notices",
"user.no_usage_records": "No Usage Records",
"user.old_password": "Old Password",
"user.password_message": "Password must be at least 4 characters and at most 60 characters",
"user.password_tip": "Password must be at least 8 characters long and contain at least two combinations: numbers, letters, or special characters",
"user.reset_password": "Reset Password",
"user.reset_password_tip": "The initial password is not set/the password has not been modified for a long time, please reset the password",
"user.team.Balance": "Team Balance",
"user.team.Check Team": "Switch",
"user.team.Confirm Invite": "Confirm Invite",
@@ -1299,5 +1306,9 @@
"xx_search_result": "{{key}} Search Results",
"yes": "Yes",
"yesterday": "yesterday",
"yesterday_detail_time": "Yesterday {{time}}"
"yesterday_detail_time": "Yesterday {{time}}",
"zoomin_tip": "Zoom Out ctrl -",
"zoomin_tip_mac": "Zoom Out ⌘ -",
"zoomout_tip": "Zoom In ctrl +",
"zoomout_tip_mac": "Zoom In ⌘ +"
}

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