Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d2d0fffe9 | ||
|
|
c6e0b5a1e7 | ||
|
|
932aa28a1f | ||
|
|
9c59bc2c17 | ||
|
|
e145f63554 | ||
|
|
554b2ca8dc | ||
|
|
4e83840c14 | ||
|
|
a6c80684d1 | ||
|
|
a4db03a3b7 | ||
|
|
cba8f773fe | ||
|
|
bd93f28d6f | ||
|
|
2063cb6314 | ||
|
|
12acaf491c | ||
|
|
3688842cc7 | ||
|
|
398d131bac | ||
|
|
d5f188a1a4 | ||
|
|
1edca309c4 | ||
|
|
1470c37ef1 | ||
|
|
bdb1221d94 | ||
|
|
cac4b1d435 | ||
|
|
0ef3d40296 | ||
|
|
3cc6b8a17a | ||
|
|
681ec30c38 | ||
|
|
24cd1c98dc | ||
|
|
eaceabcc43 | ||
|
|
a7f9411dca | ||
|
|
657fa32217 | ||
|
|
12d6948ba7 | ||
|
|
83d54d046d | ||
|
|
c75f154728 | ||
|
|
96e7dd581e |
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
BIN
docSite/assets/imgs/official_account_faq.png
Normal file
BIN
docSite/assets/imgs/official_account_faq.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 386 KiB |
@@ -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. 嵌套工作流,工具调用下包含交互节点时,流程异常。
|
||||
|
||||
|
||||
26
docSite/content/zh-cn/docs/development/upgrading/499.md
Normal file
26
docSite/content/zh-cn/docs/development/upgrading/499.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: 'V4.9.9(进行中)'
|
||||
description: 'FastGPT V4.9.9 更新说明'
|
||||
icon: 'upgrade'
|
||||
draft: false
|
||||
toc: true
|
||||
weight: 791
|
||||
---
|
||||
|
||||
|
||||
## 🚀 新增内容
|
||||
|
||||
1. 切换 SessionId 来替代 JWT 实现登录鉴权,可控制最大登录客户端数量。
|
||||
2. 新的商业版 License 管理模式。
|
||||
3. 公众号调用,显示记录 chat 对话错误,方便排查。
|
||||
|
||||
## ⚙️ 优化
|
||||
|
||||
1. 优化工具调用,新工具的判断逻辑。
|
||||
2. 调整 Cite 引用提示词。
|
||||
|
||||
## 🐛 修复
|
||||
|
||||
1. 无法正常获取应用历史保存/发布记录。
|
||||
2. 成员创建 MCP 工具权限问题。
|
||||
3. 来源引用展示,存在 ID 传递错误,导致提示无权操作该文件。
|
||||
@@ -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": ""
|
||||
}'
|
||||
```
|
||||
@@ -185,3 +185,40 @@ curl --location --request GET '{{baseURL}}/v1/file/read?id=xx' \
|
||||
{{< /tabs >}}
|
||||
|
||||
|
||||
### 4. 获取文件详细信息(用于获取文件信息)
|
||||
|
||||
{{< tabs tabTotal="2" >}}
|
||||
{{< tab tabName="请求示例" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
id 为文件的 id。
|
||||
|
||||
```bash
|
||||
curl --location --request GET '{{baseURL}}/v1/file/detail?id=xx' \
|
||||
--header 'Authorization: Bearer {{authorization}}'
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
|
||||
{{< tab tabName="响应示例" >}}
|
||||
{{< markdownify >}}
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"success": true,
|
||||
"message": "",
|
||||
"data": {
|
||||
"id": "docs",
|
||||
"parentId": "",
|
||||
"name": "docs"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{{< /markdownify >}}
|
||||
{{< /tab >}}
|
||||
{{< /tabs >}}
|
||||
|
||||
|
||||
|
||||
@@ -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,13 @@ weight: 506
|
||||
|
||||
## FAQ
|
||||
|
||||
### 公众号没响应
|
||||
|
||||
检查应用对话日志,如果有对话日志,但是微信公众号无响应,则是白名单 IP未成功。
|
||||
添加白名单IP 后,通常需要等待几分钟微信更新。可以在对话日志中,找点错误日志。
|
||||
|
||||

|
||||
|
||||
### 如何新开一个聊天记录
|
||||
|
||||
如果你想重置你的聊天记录,可以给机器人发送 `Reset` 消息(注意大小写),机器人会新开一个聊天记录。
|
||||
如果你想重置你的聊天记录,可以给机器人发送 `Reset` 消息(注意大小写),机器人会新开一个聊天记录。
|
||||
|
||||
3
env.d.ts
vendored
3
env.d.ts
vendored
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
};
|
||||
|
||||
27
packages/global/common/system/types/index.d.ts
vendored
27
packages/global/common/system/types/index.d.ts
vendored
@@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)。"
|
||||
- 每段话至少包含一个引用,也可根据内容需要加入多个引用,按顺序排列。`;
|
||||
- 使用与问题相同的语言回答。`;
|
||||
};
|
||||
|
||||
@@ -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]];
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
20
packages/global/core/app/type.d.ts
vendored
20
packages/global/core/app/type.d.ts
vendored
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
2
packages/global/core/chat/type.d.ts
vendored
2
packages/global/core/chat/type.d.ts
vendored
@@ -112,6 +112,7 @@ export type ChatItemSchema = (UserChatItemType | SystemChatItemType | AIChatItem
|
||||
appId: string;
|
||||
time: Date;
|
||||
durationSeconds?: number;
|
||||
errorMsg?: string;
|
||||
};
|
||||
|
||||
export type AdminFbkType = {
|
||||
@@ -143,6 +144,7 @@ export type ChatSiteItemType = (UserChatItemType | SystemChatItemType | AIChatIt
|
||||
responseData?: ChatHistoryItemResType[];
|
||||
time?: Date;
|
||||
durationSeconds?: number;
|
||||
errorMsg?: string;
|
||||
} & ChatBoxInputType &
|
||||
ResponseTagItemType;
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -24,7 +24,6 @@ export const LoopStartNode: FlowNodeTemplateType = {
|
||||
unique: true,
|
||||
forbidDelete: true,
|
||||
showStatus: false,
|
||||
version: '4811',
|
||||
inputs: [
|
||||
{
|
||||
key: NodeInputKeyEnum.loopStartInput,
|
||||
|
||||
@@ -15,7 +15,6 @@ export const PluginConfigNode: FlowNodeTemplateType = {
|
||||
intro: '',
|
||||
unique: true,
|
||||
forbidDelete: true,
|
||||
version: '4811',
|
||||
inputs: [],
|
||||
outputs: []
|
||||
};
|
||||
|
||||
@@ -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: []
|
||||
};
|
||||
|
||||
@@ -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: []
|
||||
};
|
||||
|
||||
@@ -13,7 +13,6 @@ export const RunAppNode: FlowNodeTemplateType = {
|
||||
name: '',
|
||||
showStatus: false,
|
||||
isTool: false,
|
||||
version: '481',
|
||||
inputs: [], // [{key:'pluginId'},...]
|
||||
outputs: []
|
||||
};
|
||||
|
||||
@@ -13,7 +13,6 @@ export const RunPluginModule: FlowNodeTemplateType = {
|
||||
name: '',
|
||||
showStatus: false,
|
||||
isTool: true,
|
||||
version: '481',
|
||||
inputs: [], // [{key:'pluginId'},...]
|
||||
outputs: []
|
||||
};
|
||||
|
||||
@@ -13,7 +13,6 @@ export const RunToolNode: FlowNodeTemplateType = {
|
||||
name: '',
|
||||
showStatus: false,
|
||||
isTool: true,
|
||||
version: '4.9.6',
|
||||
inputs: [],
|
||||
outputs: []
|
||||
};
|
||||
|
||||
@@ -13,7 +13,6 @@ export const RunToolSetNode: FlowNodeTemplateType = {
|
||||
name: '',
|
||||
showStatus: false,
|
||||
isTool: true,
|
||||
version: '4.9.6',
|
||||
inputs: [],
|
||||
outputs: []
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: []
|
||||
};
|
||||
|
||||
@@ -15,7 +15,6 @@ export const SystemConfigNode: FlowNodeTemplateType = {
|
||||
intro: '',
|
||||
unique: true,
|
||||
forbidDelete: true,
|
||||
version: '481',
|
||||
inputs: [],
|
||||
outputs: []
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: []
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
4
packages/global/core/workflow/type/io.d.ts
vendored
4
packages/global/core/workflow/type/io.d.ts
vendored
@@ -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];
|
||||
|
||||
21
packages/global/core/workflow/type/node.d.ts
vendored
21
packages/global/core/workflow/type/node.d.ts
vendored
@@ -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;
|
||||
|
||||
1
packages/global/support/user/api.d.ts
vendored
1
packages/global/support/user/api.d.ts
vendored
@@ -9,6 +9,7 @@ import type { TeamMemberItemType } from './team/type';
|
||||
export type PostLoginProps = {
|
||||
username: string;
|
||||
password: string;
|
||||
code: string;
|
||||
};
|
||||
|
||||
export type OauthLoginProps = {
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
|
||||
10
packages/global/support/user/auth/type.d.ts
vendored
Normal file
10
packages/global/support/user/auth/type.d.ts
vendored
Normal 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;
|
||||
};
|
||||
1
packages/global/support/user/type.d.ts
vendored
1
packages/global/support/user/type.d.ts
vendored
@@ -14,6 +14,7 @@ export type UserModelSchema = {
|
||||
timezone: string;
|
||||
status: `${UserStatusEnum}`;
|
||||
lastLoginTmbId?: string;
|
||||
passwordUpdateTime?: Date;
|
||||
fastgpt_sem?: {
|
||||
keyword: string;
|
||||
};
|
||||
|
||||
@@ -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 };
|
||||
};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)));
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
2
packages/service/common/redis/type.d.ts
vendored
2
packages/service/common/redis/type.d.ts
vendored
@@ -1,5 +1,5 @@
|
||||
import type Redis from 'ioredis';
|
||||
|
||||
declare global {
|
||||
var redisCache: Redis | null;
|
||||
var redisClient: Redis | null;
|
||||
}
|
||||
|
||||
@@ -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('//')) {
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -57,14 +57,19 @@ export const addLog = {
|
||||
|
||||
level === LogLevelEnum.error && console.error(obj);
|
||||
|
||||
// store
|
||||
// store log
|
||||
if (level >= STORE_LOG_LEVEL && connectionMongo.connection.readyState === 1) {
|
||||
// store log
|
||||
getMongoLog().create({
|
||||
text: msg,
|
||||
level,
|
||||
metadata: obj
|
||||
});
|
||||
(async () => {
|
||||
try {
|
||||
await getMongoLog().create({
|
||||
text: msg,
|
||||
level,
|
||||
metadata: obj
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('store log error', error);
|
||||
}
|
||||
})();
|
||||
}
|
||||
},
|
||||
debug(msg: string, obj?: Record<string, any>) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -61,6 +61,7 @@ const ChatItemSchema = new Schema({
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
errorMsg: String,
|
||||
userGoodFeedback: {
|
||||
type: String
|
||||
},
|
||||
|
||||
@@ -32,6 +32,7 @@ type Props = {
|
||||
content: [UserChatItemType & { dataId?: string }, AIChatItemType & { dataId?: string }];
|
||||
metadata?: Record<string, any>;
|
||||
durationSeconds: number; //s
|
||||
errorMsg?: string;
|
||||
};
|
||||
|
||||
export async function saveChat({
|
||||
@@ -50,6 +51,7 @@ export async function saveChat({
|
||||
outLinkUid,
|
||||
content,
|
||||
durationSeconds,
|
||||
errorMsg,
|
||||
metadata = {}
|
||||
}: Props) {
|
||||
if (!chatId || chatId === 'NO_RECORD_HISTORIES') return;
|
||||
@@ -104,7 +106,8 @@ export async function saveChat({
|
||||
return {
|
||||
...item,
|
||||
[DispatchNodeResponseKeyEnum.nodeResponse]: nodeResponse,
|
||||
durationSeconds
|
||||
durationSeconds,
|
||||
errorMsg
|
||||
};
|
||||
}
|
||||
return item;
|
||||
|
||||
@@ -2,7 +2,9 @@ import type {
|
||||
APIFileListResponse,
|
||||
ApiFileReadContentResponse,
|
||||
APIFileReadResponse,
|
||||
APIFileServer
|
||||
ApiDatasetDetailResponse,
|
||||
APIFileServer,
|
||||
APIFileItem
|
||||
} from '@fastgpt/global/core/dataset/apiDataset';
|
||||
import axios, { type Method } from 'axios';
|
||||
import { addLog } from '../../../common/system/log';
|
||||
@@ -89,7 +91,7 @@ export const useApiDatasetRequest = ({ apiServer }: { apiServer: APIFileServer }
|
||||
`/v1/file/list`,
|
||||
{
|
||||
searchKey,
|
||||
parentId
|
||||
parentId: parentId || apiServer.basePath
|
||||
},
|
||||
'POST'
|
||||
);
|
||||
@@ -164,9 +166,34 @@ export const useApiDatasetRequest = ({ apiServer }: { apiServer: APIFileServer }
|
||||
return url;
|
||||
};
|
||||
|
||||
const getFileDetail = async ({
|
||||
apiFileId
|
||||
}: {
|
||||
apiFileId: string;
|
||||
}): Promise<ApiDatasetDetailResponse> => {
|
||||
const fileData = await request<ApiDatasetDetailResponse>(
|
||||
`/v1/file/detail`,
|
||||
{
|
||||
id: apiFileId
|
||||
},
|
||||
'GET'
|
||||
);
|
||||
|
||||
if (fileData) {
|
||||
return {
|
||||
id: fileData.id,
|
||||
name: fileData.name,
|
||||
parentId: fileData.parentId === null ? '' : fileData.parentId
|
||||
};
|
||||
}
|
||||
|
||||
return Promise.reject('File not found');
|
||||
};
|
||||
|
||||
return {
|
||||
getFileContent,
|
||||
listFiles,
|
||||
getFilePreviewUrl
|
||||
getFilePreviewUrl,
|
||||
getFileDetail
|
||||
};
|
||||
};
|
||||
|
||||
@@ -123,6 +123,7 @@ const DatasetSchema = new Schema({
|
||||
|
||||
try {
|
||||
DatasetSchema.index({ teamId: 1 });
|
||||
DatasetSchema.index({ type: 1 });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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 || ''
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
63
packages/service/support/user/auth/controller.ts
Normal file
63
packages/service/support/user/auth/controller.ts
Normal 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';
|
||||
});
|
||||
};
|
||||
41
packages/service/support/user/auth/schema.ts
Normal file
41
packages/service/support/user/auth/schema.ts
Normal 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);
|
||||
@@ -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: {
|
||||
|
||||
179
packages/service/support/user/session.ts
Normal file
179
packages/service/support/user/session.ts
Normal 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;
|
||||
};
|
||||
7
packages/service/type.d.ts
vendored
7
packages/service/type.d.ts
vendored
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user