Compare commits
13 Commits
gru/projec
...
test-html
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7a722a609 | ||
|
|
0f866fc552 | ||
|
|
05c7ba4483 | ||
|
|
fa80ce3a77 | ||
|
|
830358aa72 | ||
|
|
02b214b3ec | ||
|
|
a171c7b11c | ||
|
|
802de11363 | ||
|
|
b4ecfb0b79 | ||
|
|
331b851a78 | ||
|
|
50d235c42a | ||
|
|
9838593451 | ||
|
|
c25cd48e72 |
@@ -132,15 +132,15 @@ services:
|
|||||||
# fastgpt
|
# fastgpt
|
||||||
sandbox:
|
sandbox:
|
||||||
container_name: sandbox
|
container_name: sandbox
|
||||||
image: ghcr.io/labring/fastgpt-sandbox:v4.9.9 # git
|
image: ghcr.io/labring/fastgpt-sandbox:v4.9.10-fix2 # git
|
||||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.9 # 阿里云
|
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.10-fix2 # 阿里云
|
||||||
networks:
|
networks:
|
||||||
- fastgpt
|
- fastgpt
|
||||||
restart: always
|
restart: always
|
||||||
fastgpt-mcp-server:
|
fastgpt-mcp-server:
|
||||||
container_name: fastgpt-mcp-server
|
container_name: fastgpt-mcp-server
|
||||||
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.9 # git
|
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.10-fix2 # git
|
||||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.9 # 阿里云
|
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.10-fix2 # 阿里云
|
||||||
ports:
|
ports:
|
||||||
- 3005:3000
|
- 3005:3000
|
||||||
networks:
|
networks:
|
||||||
@@ -150,8 +150,8 @@ services:
|
|||||||
- FASTGPT_ENDPOINT=http://fastgpt:3000
|
- FASTGPT_ENDPOINT=http://fastgpt:3000
|
||||||
fastgpt:
|
fastgpt:
|
||||||
container_name: fastgpt
|
container_name: fastgpt
|
||||||
image: ghcr.io/labring/fastgpt:v4.9.9 # git
|
image: ghcr.io/labring/fastgpt:v4.9.10-fix2 # git
|
||||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.9 # 阿里云
|
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.10-fix2 # 阿里云
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -109,15 +109,15 @@ services:
|
|||||||
# fastgpt
|
# fastgpt
|
||||||
sandbox:
|
sandbox:
|
||||||
container_name: sandbox
|
container_name: sandbox
|
||||||
image: ghcr.io/labring/fastgpt-sandbox:v4.9.9 # git
|
image: ghcr.io/labring/fastgpt-sandbox:v4.9.10-fix2 # git
|
||||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.9 # 阿里云
|
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.10-fix2 # 阿里云
|
||||||
networks:
|
networks:
|
||||||
- fastgpt
|
- fastgpt
|
||||||
restart: always
|
restart: always
|
||||||
fastgpt-mcp-server:
|
fastgpt-mcp-server:
|
||||||
container_name: fastgpt-mcp-server
|
container_name: fastgpt-mcp-server
|
||||||
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.9 # git
|
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.10-fix2 # git
|
||||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.9 # 阿里云
|
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.10-fix2 # 阿里云
|
||||||
ports:
|
ports:
|
||||||
- 3005:3000
|
- 3005:3000
|
||||||
networks:
|
networks:
|
||||||
@@ -127,8 +127,8 @@ services:
|
|||||||
- FASTGPT_ENDPOINT=http://fastgpt:3000
|
- FASTGPT_ENDPOINT=http://fastgpt:3000
|
||||||
fastgpt:
|
fastgpt:
|
||||||
container_name: fastgpt
|
container_name: fastgpt
|
||||||
image: ghcr.io/labring/fastgpt:v4.9.9 # git
|
image: ghcr.io/labring/fastgpt:v4.9.10-fix2 # git
|
||||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.9 # 阿里云
|
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.10-fix2 # 阿里云
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -96,15 +96,15 @@ services:
|
|||||||
# fastgpt
|
# fastgpt
|
||||||
sandbox:
|
sandbox:
|
||||||
container_name: sandbox
|
container_name: sandbox
|
||||||
image: ghcr.io/labring/fastgpt-sandbox:v4.9.9 # git
|
image: ghcr.io/labring/fastgpt-sandbox:v4.9.10-fix2 # git
|
||||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.9 # 阿里云
|
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.10-fix2 # 阿里云
|
||||||
networks:
|
networks:
|
||||||
- fastgpt
|
- fastgpt
|
||||||
restart: always
|
restart: always
|
||||||
fastgpt-mcp-server:
|
fastgpt-mcp-server:
|
||||||
container_name: fastgpt-mcp-server
|
container_name: fastgpt-mcp-server
|
||||||
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.9 # git
|
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.10-fix2 # git
|
||||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.9 # 阿里云
|
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.10-fix2 # 阿里云
|
||||||
ports:
|
ports:
|
||||||
- 3005:3000
|
- 3005:3000
|
||||||
networks:
|
networks:
|
||||||
@@ -114,8 +114,8 @@ services:
|
|||||||
- FASTGPT_ENDPOINT=http://fastgpt:3000
|
- FASTGPT_ENDPOINT=http://fastgpt:3000
|
||||||
fastgpt:
|
fastgpt:
|
||||||
container_name: fastgpt
|
container_name: fastgpt
|
||||||
image: ghcr.io/labring/fastgpt:v4.9.9 # git
|
image: ghcr.io/labring/fastgpt:v4.9.10-fix2 # git
|
||||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.9 # 阿里云
|
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.10-fix2 # 阿里云
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -72,15 +72,15 @@ services:
|
|||||||
|
|
||||||
sandbox:
|
sandbox:
|
||||||
container_name: sandbox
|
container_name: sandbox
|
||||||
image: ghcr.io/labring/fastgpt-sandbox:v4.9.9 # git
|
image: ghcr.io/labring/fastgpt-sandbox:v4.9.10-fix2 # git
|
||||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.9 # 阿里云
|
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-sandbox:v4.9.10-fix2 # 阿里云
|
||||||
networks:
|
networks:
|
||||||
- fastgpt
|
- fastgpt
|
||||||
restart: always
|
restart: always
|
||||||
fastgpt-mcp-server:
|
fastgpt-mcp-server:
|
||||||
container_name: fastgpt-mcp-server
|
container_name: fastgpt-mcp-server
|
||||||
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.9 # git
|
image: ghcr.io/labring/fastgpt-mcp_server:v4.9.10-fix2 # git
|
||||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.9 # 阿里云
|
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt-mcp_server:v4.9.10-fix2 # 阿里云
|
||||||
ports:
|
ports:
|
||||||
- 3005:3000
|
- 3005:3000
|
||||||
networks:
|
networks:
|
||||||
@@ -90,8 +90,8 @@ services:
|
|||||||
- FASTGPT_ENDPOINT=http://fastgpt:3000
|
- FASTGPT_ENDPOINT=http://fastgpt:3000
|
||||||
fastgpt:
|
fastgpt:
|
||||||
container_name: fastgpt
|
container_name: fastgpt
|
||||||
image: ghcr.io/labring/fastgpt:v4.9.9 # git
|
image: ghcr.io/labring/fastgpt:v4.9.10-fix2 # git
|
||||||
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.9 # 阿里云
|
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/fastgpt:v4.9.10-fix2 # 阿里云
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: 'V4.9.10(进行中)'
|
title: 'V4.9.10'
|
||||||
description: 'FastGPT V4.9.10 更新说明'
|
description: 'FastGPT V4.9.10 更新说明'
|
||||||
icon: 'upgrade'
|
icon: 'upgrade'
|
||||||
draft: false
|
draft: false
|
||||||
@@ -7,13 +7,28 @@ toc: true
|
|||||||
weight: 790
|
weight: 790
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 升级指南
|
||||||
|
|
||||||
|
重要提示:本次更新会重新构建全文索引,构建期间,全文检索结果会为空,4c16g 700 万组全文索引大致消耗 25 分钟。如需无缝升级,需自行做表同步工程。
|
||||||
|
|
||||||
|
### 1. 做好数据备份
|
||||||
|
|
||||||
|
### 2. 更新镜像 tag
|
||||||
|
|
||||||
|
- 更新 FastGPT 镜像 tag: v4.9.10-fix2
|
||||||
|
- 更新 FastGPT 商业版镜像 tag: v4.9.10-fix2
|
||||||
|
- mcp_server 无需更新
|
||||||
|
- Sandbox 无需更新
|
||||||
|
- AIProxy 无需更新
|
||||||
|
|
||||||
## 🚀 新增内容
|
## 🚀 新增内容
|
||||||
|
|
||||||
1. 支持 PG 设置`systemEnv.hnswMaxScanTuples`参数,提高迭代搜索的数据总量。
|
1. 支持 PG 设置`systemEnv.hnswMaxScanTuples`参数,提高迭代搜索的数据总量。
|
||||||
2. 工作流调整为单向接入和接出,支持快速的添加下一步节点。
|
2. 知识库预处理参数增加 “分块条件”,可控制某些情况下不进行分块处理。
|
||||||
3. 开放飞书和语雀知识库到开源版。
|
3. 知识库预处理参数增加 “段落优先” 模式,可控制最大段落深度。原“长度优先”模式,不再内嵌段落优先逻辑。
|
||||||
4. gemini 和 claude 最新模型预设。
|
4. 工作流调整为单向接入和接出,支持快速的添加下一步节点。
|
||||||
|
5. 开放飞书和语雀知识库到开源版。
|
||||||
|
6. gemini 和 claude 最新模型预设。
|
||||||
|
|
||||||
## ⚙️ 优化
|
## ⚙️ 优化
|
||||||
|
|
||||||
@@ -32,3 +47,4 @@ weight: 790
|
|||||||
4. 知识库 indexSize 参数未生效。
|
4. 知识库 indexSize 参数未生效。
|
||||||
5. 工作流嵌套 2 层后,获取预览引用、上下文不正确。
|
5. 工作流嵌套 2 层后,获取预览引用、上下文不正确。
|
||||||
6. xlsx 转成 Markdown 时候,前面会多出一个空格。
|
6. xlsx 转成 Markdown 时候,前面会多出一个空格。
|
||||||
|
7. 读取 Markdown 文件时,Base64 图片未进行额外抓换保存。
|
||||||
25
docSite/content/zh-cn/docs/development/upgrading/4911.md
Normal file
25
docSite/content/zh-cn/docs/development/upgrading/4911.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
title: 'V4.9.11(进行中)'
|
||||||
|
description: 'FastGPT V4.9.11 更新说明'
|
||||||
|
icon: 'upgrade'
|
||||||
|
draft: false
|
||||||
|
toc: true
|
||||||
|
weight: 789
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## 🚀 新增内容
|
||||||
|
|
||||||
|
1. 工作流中增加节点搜索功能。
|
||||||
|
2. 工作流中,子流程版本控制,可选择“保持最新版本”,无需手动更新。
|
||||||
|
|
||||||
|
## ⚙️ 优化
|
||||||
|
|
||||||
|
1. 原文缓存改用 gridfs 存储,提高上限。
|
||||||
|
|
||||||
|
## 🐛 修复
|
||||||
|
|
||||||
|
1. 工作流中,管理员声明的全局系统工具,无法进行版本管理。
|
||||||
|
2. 工具调用节点前,有交互节点时,上下文异常。
|
||||||
|
3. 修复备份导入,小于 1000 字时,无法分块问题。
|
||||||
|
4. 自定义 PDF 解析,无法保存 base64 图片。
|
||||||
@@ -7,6 +7,10 @@ export const CUSTOM_SPLIT_SIGN = '-----CUSTOM_SPLIT_SIGN-----';
|
|||||||
type SplitProps = {
|
type SplitProps = {
|
||||||
text: string;
|
text: string;
|
||||||
chunkSize: number;
|
chunkSize: number;
|
||||||
|
|
||||||
|
paragraphChunkDeep?: number; // Paragraph deep
|
||||||
|
paragraphChunkMinSize?: number; // Paragraph min size, if too small, it will merge
|
||||||
|
|
||||||
maxSize?: number;
|
maxSize?: number;
|
||||||
overlapRatio?: number;
|
overlapRatio?: number;
|
||||||
customReg?: string[];
|
customReg?: string[];
|
||||||
@@ -108,6 +112,8 @@ const commonSplit = (props: SplitProps): SplitResponse => {
|
|||||||
let {
|
let {
|
||||||
text = '',
|
text = '',
|
||||||
chunkSize,
|
chunkSize,
|
||||||
|
paragraphChunkDeep = 5,
|
||||||
|
paragraphChunkMinSize = 100,
|
||||||
maxSize = defaultMaxChunkSize,
|
maxSize = defaultMaxChunkSize,
|
||||||
overlapRatio = 0.15,
|
overlapRatio = 0.15,
|
||||||
customReg = []
|
customReg = []
|
||||||
@@ -123,7 +129,7 @@ const commonSplit = (props: SplitProps): SplitResponse => {
|
|||||||
text = text.replace(/(```[\s\S]*?```|~~~[\s\S]*?~~~)/g, function (match) {
|
text = text.replace(/(```[\s\S]*?```|~~~[\s\S]*?~~~)/g, function (match) {
|
||||||
return match.replace(/\n/g, codeBlockMarker);
|
return match.replace(/\n/g, codeBlockMarker);
|
||||||
});
|
});
|
||||||
// 2. 表格处理 - 单独提取表格出来,进行表头合并
|
// 2. Markdown 表格处理 - 单独提取表格出来,进行表头合并
|
||||||
const tableReg =
|
const tableReg =
|
||||||
/(\n\|(?:(?:[^\n|]+\|){1,})\n\|(?:[:\-\s]+\|){1,}\n(?:\|(?:[^\n|]+\|)*\n?)*)(?:\n|$)/g;
|
/(\n\|(?:(?:[^\n|]+\|){1,})\n\|(?:[:\-\s]+\|){1,}\n(?:\|(?:[^\n|]+\|)*\n?)*)(?:\n|$)/g;
|
||||||
const tableDataList = text.match(tableReg);
|
const tableDataList = text.match(tableReg);
|
||||||
@@ -143,25 +149,40 @@ const commonSplit = (props: SplitProps): SplitResponse => {
|
|||||||
text = text.replace(/(\r?\n|\r){3,}/g, '\n\n\n');
|
text = text.replace(/(\r?\n|\r){3,}/g, '\n\n\n');
|
||||||
|
|
||||||
// The larger maxLen is, the next sentence is less likely to trigger splitting
|
// The larger maxLen is, the next sentence is less likely to trigger splitting
|
||||||
const markdownIndex = 4;
|
const customRegLen = customReg.length;
|
||||||
const forbidOverlapIndex = 8;
|
const markdownIndex = paragraphChunkDeep - 1;
|
||||||
|
const forbidOverlapIndex = customRegLen + markdownIndex + 4;
|
||||||
|
|
||||||
|
const markdownHeaderRules = ((deep?: number): { reg: RegExp; maxLen: number }[] => {
|
||||||
|
if (!deep || deep === 0) return [];
|
||||||
|
|
||||||
|
const maxDeep = Math.min(deep, 8); // Maximum 8 levels
|
||||||
|
const rules: { reg: RegExp; maxLen: number }[] = [];
|
||||||
|
|
||||||
|
for (let i = 1; i <= maxDeep; i++) {
|
||||||
|
const hashSymbols = '#'.repeat(i);
|
||||||
|
rules.push({
|
||||||
|
reg: new RegExp(`^(${hashSymbols}\\s[^\\n]+\\n)`, 'gm'),
|
||||||
|
maxLen: chunkSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules;
|
||||||
|
})(paragraphChunkDeep);
|
||||||
|
|
||||||
const stepReges: { reg: RegExp | string; maxLen: number }[] = [
|
const stepReges: { reg: RegExp | string; maxLen: number }[] = [
|
||||||
...customReg.map((text) => ({
|
...customReg.map((text) => ({
|
||||||
reg: text.replaceAll('\\n', '\n'),
|
reg: text.replaceAll('\\n', '\n'),
|
||||||
maxLen: chunkSize
|
maxLen: chunkSize
|
||||||
})),
|
})),
|
||||||
{ reg: /^(#\s[^\n]+\n)/gm, maxLen: chunkSize },
|
...markdownHeaderRules,
|
||||||
{ reg: /^(##\s[^\n]+\n)/gm, maxLen: chunkSize },
|
|
||||||
{ reg: /^(###\s[^\n]+\n)/gm, maxLen: chunkSize },
|
|
||||||
{ reg: /^(####\s[^\n]+\n)/gm, maxLen: chunkSize },
|
|
||||||
{ reg: /^(#####\s[^\n]+\n)/gm, maxLen: chunkSize },
|
|
||||||
|
|
||||||
{ reg: /([\n](```[\s\S]*?```|~~~[\s\S]*?~~~))/g, maxLen: maxSize }, // code block
|
{ reg: /([\n](```[\s\S]*?```|~~~[\s\S]*?~~~))/g, maxLen: maxSize }, // code block
|
||||||
|
// HTML Table tag 尽可能保障完整
|
||||||
{
|
{
|
||||||
reg: /(\n\|(?:(?:[^\n|]+\|){1,})\n\|(?:[:\-\s]+\|){1,}\n(?:\|(?:[^\n|]+\|)*\n)*)/g,
|
reg: /(\n\|(?:(?:[^\n|]+\|){1,})\n\|(?:[:\-\s]+\|){1,}\n(?:\|(?:[^\n|]+\|)*\n)*)/g,
|
||||||
maxLen: Math.min(chunkSize * 1.5, maxSize)
|
maxLen: chunkSize
|
||||||
}, // Table 尽可能保证完整性
|
}, // Markdown Table 尽可能保证完整性
|
||||||
{ reg: /(\n{2,})/g, maxLen: chunkSize },
|
{ reg: /(\n{2,})/g, maxLen: chunkSize },
|
||||||
{ reg: /([\n])/g, maxLen: chunkSize },
|
{ reg: /([\n])/g, maxLen: chunkSize },
|
||||||
// ------ There's no overlap on the top
|
// ------ There's no overlap on the top
|
||||||
@@ -172,12 +193,10 @@ const commonSplit = (props: SplitProps): SplitResponse => {
|
|||||||
{ reg: /([,]|,\s)/g, maxLen: chunkSize }
|
{ reg: /([,]|,\s)/g, maxLen: chunkSize }
|
||||||
];
|
];
|
||||||
|
|
||||||
const customRegLen = customReg.length;
|
|
||||||
const checkIsCustomStep = (step: number) => step < customRegLen;
|
const checkIsCustomStep = (step: number) => step < customRegLen;
|
||||||
const checkIsMarkdownSplit = (step: number) =>
|
const checkIsMarkdownSplit = (step: number) =>
|
||||||
step >= customRegLen && step <= markdownIndex + customRegLen;
|
step >= customRegLen && step <= markdownIndex + customRegLen;
|
||||||
|
const checkForbidOverlap = (step: number) => step <= forbidOverlapIndex;
|
||||||
const checkForbidOverlap = (step: number) => step <= forbidOverlapIndex + customRegLen;
|
|
||||||
|
|
||||||
// if use markdown title split, Separate record title
|
// if use markdown title split, Separate record title
|
||||||
const getSplitTexts = ({ text, step }: { text: string; step: number }) => {
|
const getSplitTexts = ({ text, step }: { text: string; step: number }) => {
|
||||||
@@ -301,6 +320,7 @@ const commonSplit = (props: SplitProps): SplitResponse => {
|
|||||||
const splitTexts = getSplitTexts({ text, step });
|
const splitTexts = getSplitTexts({ text, step });
|
||||||
|
|
||||||
const chunks: string[] = [];
|
const chunks: string[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < splitTexts.length; i++) {
|
for (let i = 0; i < splitTexts.length; i++) {
|
||||||
const item = splitTexts[i];
|
const item = splitTexts[i];
|
||||||
|
|
||||||
@@ -443,7 +463,6 @@ const commonSplit = (props: SplitProps): SplitResponse => {
|
|||||||
*/
|
*/
|
||||||
export const splitText2Chunks = (props: SplitProps): SplitResponse => {
|
export const splitText2Chunks = (props: SplitProps): SplitResponse => {
|
||||||
let { text = '' } = props;
|
let { text = '' } = props;
|
||||||
const start = Date.now();
|
|
||||||
const splitWithCustomSign = text.split(CUSTOM_SPLIT_SIGN);
|
const splitWithCustomSign = text.split(CUSTOM_SPLIT_SIGN);
|
||||||
|
|
||||||
const splitResult = splitWithCustomSign.map((item) => {
|
const splitResult = splitWithCustomSign.map((item) => {
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import { AppTypeEnum } from './constants';
|
|||||||
import { AppErrEnum } from '../../common/error/code/app';
|
import { AppErrEnum } from '../../common/error/code/app';
|
||||||
import { PluginErrEnum } from '../../common/error/code/plugin';
|
import { PluginErrEnum } from '../../common/error/code/plugin';
|
||||||
import { i18nT } from '../../../web/i18n/utils';
|
import { i18nT } from '../../../web/i18n/utils';
|
||||||
|
import appErrList from '../../common/error/code/app';
|
||||||
|
import pluginErrList from '../../common/error/code/plugin';
|
||||||
|
|
||||||
export const getDefaultAppForm = (): AppSimpleEditFormType => {
|
export const getDefaultAppForm = (): AppSimpleEditFormType => {
|
||||||
return {
|
return {
|
||||||
@@ -190,17 +192,10 @@ export const getAppType = (config?: WorkflowTemplateBasicType | AppSimpleEditFor
|
|||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatToolError = (error?: string) => {
|
export const formatToolError = (error?: any) => {
|
||||||
const unExistError: Array<string> = [
|
if (!error || typeof error !== 'string') return;
|
||||||
AppErrEnum.unAuthApp,
|
|
||||||
AppErrEnum.unExist,
|
|
||||||
PluginErrEnum.unAuth,
|
|
||||||
PluginErrEnum.unExist
|
|
||||||
];
|
|
||||||
|
|
||||||
if (error && unExistError.includes(error)) {
|
const errorText = appErrList[error]?.message || pluginErrList[error]?.message;
|
||||||
return i18nT('app:un_auth');
|
|
||||||
} else {
|
return errorText || error;
|
||||||
return error;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ export const computeChunkSize = (params: {
|
|||||||
|
|
||||||
return Math.min(params.chunkSize ?? chunkAutoChunkSize, getLLMMaxChunkSize(params.llmModel));
|
return Math.min(params.chunkSize ?? chunkAutoChunkSize, getLLMMaxChunkSize(params.llmModel));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const computeChunkSplitter = (params: {
|
export const computeChunkSplitter = (params: {
|
||||||
chunkSettingMode?: ChunkSettingModeEnum;
|
chunkSettingMode?: ChunkSettingModeEnum;
|
||||||
chunkSplitMode?: DataChunkSplitModeEnum;
|
chunkSplitMode?: DataChunkSplitModeEnum;
|
||||||
@@ -129,8 +128,21 @@ export const computeChunkSplitter = (params: {
|
|||||||
if (params.chunkSettingMode === ChunkSettingModeEnum.auto) {
|
if (params.chunkSettingMode === ChunkSettingModeEnum.auto) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (params.chunkSplitMode === DataChunkSplitModeEnum.size) {
|
if (params.chunkSplitMode !== DataChunkSplitModeEnum.char) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return params.chunkSplitter;
|
return params.chunkSplitter;
|
||||||
};
|
};
|
||||||
|
export const computeParagraphChunkDeep = (params: {
|
||||||
|
chunkSettingMode?: ChunkSettingModeEnum;
|
||||||
|
chunkSplitMode?: DataChunkSplitModeEnum;
|
||||||
|
paragraphChunkDeep?: number;
|
||||||
|
}) => {
|
||||||
|
if (params.chunkSettingMode === ChunkSettingModeEnum.auto) {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
if (params.chunkSplitMode === DataChunkSplitModeEnum.paragraph) {
|
||||||
|
return params.paragraphChunkDeep;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|||||||
8
packages/global/core/dataset/type.d.ts
vendored
8
packages/global/core/dataset/type.d.ts
vendored
@@ -9,7 +9,8 @@ import type {
|
|||||||
DatasetTypeEnum,
|
DatasetTypeEnum,
|
||||||
SearchScoreTypeEnum,
|
SearchScoreTypeEnum,
|
||||||
TrainingModeEnum,
|
TrainingModeEnum,
|
||||||
ChunkSettingModeEnum
|
ChunkSettingModeEnum,
|
||||||
|
ChunkTriggerConfigTypeEnum
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import type { DatasetPermission } from '../../support/permission/dataset/controller';
|
import type { DatasetPermission } from '../../support/permission/dataset/controller';
|
||||||
import type { APIFileServer, FeishuServer, YuqueServer } from './apiDataset';
|
import type { APIFileServer, FeishuServer, YuqueServer } from './apiDataset';
|
||||||
@@ -37,11 +38,10 @@ export type ChunkSettingsType = {
|
|||||||
paragraphChunkAIMode?: ParagraphChunkAIModeEnum;
|
paragraphChunkAIMode?: ParagraphChunkAIModeEnum;
|
||||||
paragraphChunkDeep?: number; // Paragraph deep
|
paragraphChunkDeep?: number; // Paragraph deep
|
||||||
paragraphChunkMinSize?: number; // Paragraph min size, if too small, it will merge
|
paragraphChunkMinSize?: number; // Paragraph min size, if too small, it will merge
|
||||||
paragraphChunkMaxSize?: number; // Paragraph max size, if too large, it will split
|
|
||||||
// Size split
|
// Size split
|
||||||
chunkSize?: number;
|
chunkSize?: number; // chunk/qa chunk size, Paragraph max chunk size.
|
||||||
// Char split
|
// Char split
|
||||||
chunkSplitter?: string;
|
chunkSplitter?: string; // chunk/qa chunk splitter
|
||||||
indexSize?: number;
|
indexSize?: number;
|
||||||
|
|
||||||
qaPrompt?: string;
|
qaPrompt?: string;
|
||||||
|
|||||||
@@ -40,5 +40,6 @@ export function getSourceNameIcon({
|
|||||||
export const predictDataLimitLength = (mode: TrainingModeEnum, data: any[]) => {
|
export const predictDataLimitLength = (mode: TrainingModeEnum, data: any[]) => {
|
||||||
if (mode === TrainingModeEnum.qa) return data.length * 20;
|
if (mode === TrainingModeEnum.qa) return data.length * 20;
|
||||||
if (mode === TrainingModeEnum.auto) return data.length * 5;
|
if (mode === TrainingModeEnum.auto) return data.length * 5;
|
||||||
|
if (mode === TrainingModeEnum.image) return data.length * 2;
|
||||||
return data.length;
|
return data.length;
|
||||||
};
|
};
|
||||||
|
|||||||
2
packages/global/core/workflow/type/node.d.ts
vendored
2
packages/global/core/workflow/type/node.d.ts
vendored
@@ -59,7 +59,6 @@ export type FlowNodeCommonType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type PluginDataType = {
|
export type PluginDataType = {
|
||||||
version?: string;
|
|
||||||
diagram?: string;
|
diagram?: string;
|
||||||
userGuide?: string;
|
userGuide?: string;
|
||||||
courseUrl?: string;
|
courseUrl?: string;
|
||||||
@@ -126,6 +125,7 @@ export type FlowNodeItemType = FlowNodeTemplateType & {
|
|||||||
nodeId: string;
|
nodeId: string;
|
||||||
parentNodeId?: string;
|
parentNodeId?: string;
|
||||||
isError?: boolean;
|
isError?: boolean;
|
||||||
|
searchedText?: string;
|
||||||
debugResult?: {
|
debugResult?: {
|
||||||
status: 'running' | 'success' | 'skipped' | 'failed';
|
status: 'running' | 'success' | 'skipped' | 'failed';
|
||||||
message?: string;
|
message?: string;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "4816",
|
|
||||||
"name": "钉钉 webhook",
|
"name": "钉钉 webhook",
|
||||||
"avatar": "plugins/dingding",
|
"avatar": "plugins/dingding",
|
||||||
"intro": "向钉钉机器人发起 webhook 请求。",
|
"intro": "向钉钉机器人发起 webhook 请求。",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "Menghuan1918",
|
"author": "Menghuan1918",
|
||||||
"version": "488",
|
|
||||||
"name": "PDF识别",
|
"name": "PDF识别",
|
||||||
"avatar": "plugins/doc2x",
|
"avatar": "plugins/doc2x",
|
||||||
"intro": "将PDF文件发送至Doc2X进行解析,返回结构化的LaTeX公式的文本(markdown),支持传入String类型的URL或者流程输出中的文件链接变量",
|
"intro": "将PDF文件发送至Doc2X进行解析,返回结构化的LaTeX公式的文本(markdown),支持传入String类型的URL或者流程输出中的文件链接变量",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "Menghuan1918",
|
"author": "Menghuan1918",
|
||||||
"version": "488",
|
|
||||||
"name": "Doc2X服务",
|
"name": "Doc2X服务",
|
||||||
"avatar": "plugins/doc2x",
|
"avatar": "plugins/doc2x",
|
||||||
"intro": "将传入的图片或PDF文件发送至Doc2X进行解析,返回带LaTeX公式的markdown格式的文本。",
|
"intro": "将传入的图片或PDF文件发送至Doc2X进行解析,返回带LaTeX公式的markdown格式的文本。",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "4816",
|
|
||||||
"name": "企业微信 webhook",
|
"name": "企业微信 webhook",
|
||||||
"avatar": "plugins/qiwei",
|
"avatar": "plugins/qiwei",
|
||||||
"intro": "向企业微信机器人发起 webhook 请求。只能内部群使用。",
|
"intro": "向企业微信机器人发起 webhook 请求。只能内部群使用。",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "4811",
|
|
||||||
"name": "Bing搜索",
|
"name": "Bing搜索",
|
||||||
"avatar": "core/workflow/template/bing",
|
"avatar": "core/workflow/template/bing",
|
||||||
"intro": "在Bing中搜索。",
|
"intro": "在Bing中搜索。",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "silencezhang",
|
"author": "silencezhang",
|
||||||
"version": "4811",
|
|
||||||
"name": "数据库连接",
|
"name": "数据库连接",
|
||||||
"avatar": "core/workflow/template/datasource",
|
"avatar": "core/workflow/template/datasource",
|
||||||
"intro": "可连接常用数据库,并执行sql",
|
"intro": "可连接常用数据库,并执行sql",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "collin",
|
"author": "collin",
|
||||||
"version": "4817",
|
|
||||||
"name": "流程等待",
|
"name": "流程等待",
|
||||||
"avatar": "core/workflow/template/sleep",
|
"avatar": "core/workflow/template/sleep",
|
||||||
"intro": "让工作流等待指定时间后运行",
|
"intro": "让工作流等待指定时间后运行",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "silencezhang",
|
"author": "silencezhang",
|
||||||
"version": "4817",
|
|
||||||
"name": "基础图表",
|
"name": "基础图表",
|
||||||
"avatar": "core/workflow/template/baseChart",
|
"avatar": "core/workflow/template/baseChart",
|
||||||
"intro": "根据数据生成图表,可根据chartType生成柱状图,折线图,饼图",
|
"intro": "根据数据生成图表,可根据chartType生成柱状图,折线图,饼图",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "silencezhang",
|
"author": "silencezhang",
|
||||||
"version": "486",
|
|
||||||
"name": "BI图表功能",
|
"name": "BI图表功能",
|
||||||
"avatar": "core/workflow/template/BI",
|
"avatar": "core/workflow/template/BI",
|
||||||
"intro": "BI图表功能,可以生成一些常用的图表,如饼图,柱状图,折线图等",
|
"intro": "BI图表功能,可以生成一些常用的图表,如饼图,柱状图,折线图等",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "486",
|
|
||||||
"name": "DuckDuckGo 网络搜索",
|
"name": "DuckDuckGo 网络搜索",
|
||||||
"avatar": "core/workflow/template/duckduckgo",
|
"avatar": "core/workflow/template/duckduckgo",
|
||||||
"intro": "使用 DuckDuckGo 进行网络搜索",
|
"intro": "使用 DuckDuckGo 进行网络搜索",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "486",
|
|
||||||
"name": "DuckDuckGo 图片搜索",
|
"name": "DuckDuckGo 图片搜索",
|
||||||
"avatar": "core/workflow/template/duckduckgo",
|
"avatar": "core/workflow/template/duckduckgo",
|
||||||
"intro": "使用 DuckDuckGo 进行图片搜索",
|
"intro": "使用 DuckDuckGo 进行图片搜索",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "486",
|
|
||||||
"name": "DuckDuckGo 新闻检索",
|
"name": "DuckDuckGo 新闻检索",
|
||||||
"avatar": "core/workflow/template/duckduckgo",
|
"avatar": "core/workflow/template/duckduckgo",
|
||||||
"intro": "使用 DuckDuckGo 进行新闻检索",
|
"intro": "使用 DuckDuckGo 进行新闻检索",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "486",
|
|
||||||
"name": "DuckDuckGo 视频搜索",
|
"name": "DuckDuckGo 视频搜索",
|
||||||
"avatar": "core/workflow/template/duckduckgo",
|
"avatar": "core/workflow/template/duckduckgo",
|
||||||
"intro": "使用 DuckDuckGo 进行视频搜索",
|
"intro": "使用 DuckDuckGo 进行视频搜索",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "486",
|
|
||||||
"name": "DuckDuckGo服务",
|
"name": "DuckDuckGo服务",
|
||||||
"avatar": "core/workflow/template/duckduckgo",
|
"avatar": "core/workflow/template/duckduckgo",
|
||||||
"intro": "DuckDuckGo 服务,包含网络搜索、图片搜索、新闻搜索等。",
|
"intro": "DuckDuckGo 服务,包含网络搜索、图片搜索、新闻搜索等。",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "488",
|
|
||||||
"name": "飞书 webhook",
|
"name": "飞书 webhook",
|
||||||
"avatar": "core/app/templates/plugin-feishu",
|
"avatar": "core/app/templates/plugin-feishu",
|
||||||
"intro": "向飞书机器人发起 webhook 请求。",
|
"intro": "向飞书机器人发起 webhook 请求。",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "486",
|
|
||||||
"name": "网页内容抓取",
|
"name": "网页内容抓取",
|
||||||
"avatar": "core/workflow/template/fetchUrl",
|
"avatar": "core/workflow/template/fetchUrl",
|
||||||
"intro": "可获取一个网页链接内容,并以 Markdown 格式输出,仅支持获取静态网站。",
|
"intro": "可获取一个网页链接内容,并以 Markdown 格式输出,仅支持获取静态网站。",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "481",
|
|
||||||
"templateType": "tools",
|
"templateType": "tools",
|
||||||
"name": "获取当前时间",
|
"name": "获取当前时间",
|
||||||
"avatar": "core/workflow/template/getTime",
|
"avatar": "core/workflow/template/getTime",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "4811",
|
|
||||||
"name": "Google搜索",
|
"name": "Google搜索",
|
||||||
"avatar": "core/workflow/template/google",
|
"avatar": "core/workflow/template/google",
|
||||||
"intro": "在google中搜索。",
|
"intro": "在google中搜索。",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "486",
|
|
||||||
"name": "数学公式执行",
|
"name": "数学公式执行",
|
||||||
"avatar": "core/workflow/template/mathCall",
|
"avatar": "core/workflow/template/mathCall",
|
||||||
"intro": "用于执行数学表达式的工具,通过 js 的 expr-eval 库运行表达式并返回结果。",
|
"intro": "用于执行数学表达式的工具,通过 js 的 expr-eval 库运行表达式并返回结果。",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "4816",
|
|
||||||
"name": "Search XNG 搜索",
|
"name": "Search XNG 搜索",
|
||||||
"avatar": "core/workflow/template/searxng",
|
"avatar": "core/workflow/template/searxng",
|
||||||
"intro": "使用 Search XNG 服务进行搜索。",
|
"intro": "使用 Search XNG 服务进行搜索。",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "cloudpense",
|
"author": "cloudpense",
|
||||||
"version": "1.0.0",
|
|
||||||
"name": "Email 邮件发送",
|
"name": "Email 邮件发送",
|
||||||
"avatar": "plugins/email",
|
"avatar": "plugins/email",
|
||||||
"intro": "通过SMTP协议发送电子邮件(nodemailer)",
|
"intro": "通过SMTP协议发送电子邮件(nodemailer)",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "489",
|
|
||||||
"name": "文本加工",
|
"name": "文本加工",
|
||||||
"avatar": "/imgs/workflow/textEditor.svg",
|
"avatar": "/imgs/workflow/textEditor.svg",
|
||||||
"intro": "可对固定或传入的文本进行加工后输出,非字符串类型数据最终会转成字符串类型。",
|
"intro": "可对固定或传入的文本进行加工后输出,非字符串类型数据最终会转成字符串类型。",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "4811",
|
|
||||||
"name": "Wiki搜索",
|
"name": "Wiki搜索",
|
||||||
"avatar": "core/workflow/template/wiki",
|
"avatar": "core/workflow/template/wiki",
|
||||||
"intro": "在Wiki中查询释义。",
|
"intro": "在Wiki中查询释义。",
|
||||||
|
|||||||
178
packages/service/common/buffer/rawText/controller.ts
Normal file
178
packages/service/common/buffer/rawText/controller.ts
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
import { retryFn } from '@fastgpt/global/common/system/utils';
|
||||||
|
import { connectionMongo } from '../../mongo';
|
||||||
|
import { MongoRawTextBufferSchema, bucketName } from './schema';
|
||||||
|
import { addLog } from '../../system/log';
|
||||||
|
import { setCron } from '../../system/cron';
|
||||||
|
import { checkTimerLock } from '../../system/timerLock/utils';
|
||||||
|
import { TimerIdEnum } from '../../system/timerLock/constants';
|
||||||
|
|
||||||
|
const getGridBucket = () => {
|
||||||
|
return new connectionMongo.mongo.GridFSBucket(connectionMongo.connection.db!, {
|
||||||
|
bucketName: bucketName
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addRawTextBuffer = async ({
|
||||||
|
sourceId,
|
||||||
|
sourceName,
|
||||||
|
text,
|
||||||
|
expiredTime
|
||||||
|
}: {
|
||||||
|
sourceId: string;
|
||||||
|
sourceName: string;
|
||||||
|
text: string;
|
||||||
|
expiredTime: Date;
|
||||||
|
}) => {
|
||||||
|
const gridBucket = getGridBucket();
|
||||||
|
const metadata = {
|
||||||
|
sourceId,
|
||||||
|
sourceName,
|
||||||
|
expiredTime
|
||||||
|
};
|
||||||
|
|
||||||
|
const buffer = Buffer.from(text);
|
||||||
|
|
||||||
|
const fileSize = buffer.length;
|
||||||
|
// 单块大小:尽可能大,但不超过 14MB,不小于128KB
|
||||||
|
const chunkSizeBytes = (() => {
|
||||||
|
// 计算理想块大小:文件大小 ÷ 目标块数(10)。 并且每个块需要小于 14MB
|
||||||
|
const idealChunkSize = Math.min(Math.ceil(fileSize / 10), 14 * 1024 * 1024);
|
||||||
|
|
||||||
|
// 确保块大小至少为128KB
|
||||||
|
const minChunkSize = 128 * 1024; // 128KB
|
||||||
|
|
||||||
|
// 取理想块大小和最小块大小中的较大值
|
||||||
|
let chunkSize = Math.max(idealChunkSize, minChunkSize);
|
||||||
|
|
||||||
|
// 将块大小向上取整到最接近的64KB的倍数,使其更整齐
|
||||||
|
chunkSize = Math.ceil(chunkSize / (64 * 1024)) * (64 * 1024);
|
||||||
|
|
||||||
|
return chunkSize;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const uploadStream = gridBucket.openUploadStream(sourceId, {
|
||||||
|
metadata,
|
||||||
|
chunkSizeBytes
|
||||||
|
});
|
||||||
|
|
||||||
|
return retryFn(async () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uploadStream.end(buffer);
|
||||||
|
uploadStream.on('finish', () => {
|
||||||
|
resolve(uploadStream.id);
|
||||||
|
});
|
||||||
|
uploadStream.on('error', (error) => {
|
||||||
|
addLog.error('addRawTextBuffer error', error);
|
||||||
|
resolve('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getRawTextBuffer = async (sourceId: string) => {
|
||||||
|
const gridBucket = getGridBucket();
|
||||||
|
|
||||||
|
return retryFn(async () => {
|
||||||
|
const bufferData = await MongoRawTextBufferSchema.findOne(
|
||||||
|
{
|
||||||
|
'metadata.sourceId': sourceId
|
||||||
|
},
|
||||||
|
'_id metadata'
|
||||||
|
).lean();
|
||||||
|
if (!bufferData) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read file content
|
||||||
|
const downloadStream = gridBucket.openDownloadStream(bufferData._id);
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
|
||||||
|
return new Promise<{
|
||||||
|
text: string;
|
||||||
|
sourceName: string;
|
||||||
|
} | null>((resolve, reject) => {
|
||||||
|
downloadStream.on('data', (chunk) => {
|
||||||
|
chunks.push(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
downloadStream.on('end', () => {
|
||||||
|
const buffer = Buffer.concat(chunks);
|
||||||
|
const text = buffer.toString('utf8');
|
||||||
|
resolve({
|
||||||
|
text,
|
||||||
|
sourceName: bufferData.metadata?.sourceName || ''
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
downloadStream.on('error', (error) => {
|
||||||
|
addLog.error('getRawTextBuffer error', error);
|
||||||
|
resolve(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteRawTextBuffer = async (sourceId: string): Promise<boolean> => {
|
||||||
|
const gridBucket = getGridBucket();
|
||||||
|
|
||||||
|
return retryFn(async () => {
|
||||||
|
const buffer = await MongoRawTextBufferSchema.findOne({ 'metadata.sourceId': sourceId });
|
||||||
|
if (!buffer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await gridBucket.delete(buffer._id);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateRawTextBufferExpiredTime = async ({
|
||||||
|
sourceId,
|
||||||
|
expiredTime
|
||||||
|
}: {
|
||||||
|
sourceId: string;
|
||||||
|
expiredTime: Date;
|
||||||
|
}) => {
|
||||||
|
return retryFn(async () => {
|
||||||
|
return MongoRawTextBufferSchema.updateOne(
|
||||||
|
{ 'metadata.sourceId': sourceId },
|
||||||
|
{ $set: { 'metadata.expiredTime': expiredTime } }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clearExpiredRawTextBufferCron = async () => {
|
||||||
|
const clearExpiredRawTextBuffer = async () => {
|
||||||
|
addLog.debug('Clear expired raw text buffer start');
|
||||||
|
const gridBucket = getGridBucket();
|
||||||
|
|
||||||
|
return retryFn(async () => {
|
||||||
|
const data = await MongoRawTextBufferSchema.find(
|
||||||
|
{
|
||||||
|
'metadata.expiredTime': { $lt: new Date() }
|
||||||
|
},
|
||||||
|
'_id'
|
||||||
|
).lean();
|
||||||
|
|
||||||
|
for (const item of data) {
|
||||||
|
await gridBucket.delete(item._id);
|
||||||
|
}
|
||||||
|
addLog.debug('Clear expired raw text buffer end');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
setCron('*/10 * * * *', async () => {
|
||||||
|
if (
|
||||||
|
await checkTimerLock({
|
||||||
|
timerId: TimerIdEnum.clearExpiredRawTextBuffer,
|
||||||
|
lockMinuted: 9
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
await clearExpiredRawTextBuffer();
|
||||||
|
} catch (error) {
|
||||||
|
addLog.error('clearExpiredRawTextBufferCron error', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,33 +1,22 @@
|
|||||||
import { getMongoModel, Schema } from '../../mongo';
|
import { getMongoModel, type Types, Schema } from '../../mongo';
|
||||||
import { type RawTextBufferSchemaType } from './type';
|
|
||||||
|
|
||||||
export const collectionName = 'buffer_rawtexts';
|
export const bucketName = 'buffer_rawtext';
|
||||||
|
|
||||||
const RawTextBufferSchema = new Schema({
|
const RawTextBufferSchema = new Schema({
|
||||||
sourceId: {
|
metadata: {
|
||||||
type: String,
|
sourceId: { type: String, required: true },
|
||||||
required: true
|
sourceName: { type: String, required: true },
|
||||||
},
|
expiredTime: { type: Date, required: true }
|
||||||
rawText: {
|
}
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
createTime: {
|
|
||||||
type: Date,
|
|
||||||
default: () => new Date()
|
|
||||||
},
|
|
||||||
metadata: Object
|
|
||||||
});
|
});
|
||||||
|
RawTextBufferSchema.index({ 'metadata.sourceId': 'hashed' });
|
||||||
|
RawTextBufferSchema.index({ 'metadata.expiredTime': -1 });
|
||||||
|
|
||||||
try {
|
export const MongoRawTextBufferSchema = getMongoModel<{
|
||||||
RawTextBufferSchema.index({ sourceId: 1 });
|
_id: Types.ObjectId;
|
||||||
// 20 minutes
|
metadata: {
|
||||||
RawTextBufferSchema.index({ createTime: 1 }, { expireAfterSeconds: 20 * 60 });
|
sourceId: string;
|
||||||
} catch (error) {
|
sourceName: string;
|
||||||
console.log(error);
|
expiredTime: Date;
|
||||||
}
|
};
|
||||||
|
}>(`${bucketName}.files`, RawTextBufferSchema);
|
||||||
export const MongoRawTextBuffer = getMongoModel<RawTextBufferSchemaType>(
|
|
||||||
collectionName,
|
|
||||||
RawTextBufferSchema
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
export type RawTextBufferSchemaType = {
|
|
||||||
sourceId: string;
|
|
||||||
rawText: string;
|
|
||||||
createTime: Date;
|
|
||||||
metadata?: {
|
|
||||||
filename: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -6,13 +6,13 @@ import { type DatasetFileSchema } from '@fastgpt/global/core/dataset/type';
|
|||||||
import { MongoChatFileSchema, MongoDatasetFileSchema } from './schema';
|
import { MongoChatFileSchema, MongoDatasetFileSchema } from './schema';
|
||||||
import { detectFileEncoding, detectFileEncodingByPath } from '@fastgpt/global/common/file/tools';
|
import { detectFileEncoding, detectFileEncodingByPath } from '@fastgpt/global/common/file/tools';
|
||||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||||
import { MongoRawTextBuffer } from '../../buffer/rawText/schema';
|
|
||||||
import { readRawContentByFileBuffer } from '../read/utils';
|
import { readRawContentByFileBuffer } from '../read/utils';
|
||||||
import { gridFsStream2Buffer, stream2Encoding } from './utils';
|
import { gridFsStream2Buffer, stream2Encoding } from './utils';
|
||||||
import { addLog } from '../../system/log';
|
import { addLog } from '../../system/log';
|
||||||
import { readFromSecondary } from '../../mongo/utils';
|
|
||||||
import { parseFileExtensionFromUrl } from '@fastgpt/global/common/string/tools';
|
import { parseFileExtensionFromUrl } from '@fastgpt/global/common/string/tools';
|
||||||
import { Readable } from 'stream';
|
import { Readable } from 'stream';
|
||||||
|
import { addRawTextBuffer, getRawTextBuffer } from '../../buffer/rawText/controller';
|
||||||
|
import { addMinutes } from 'date-fns';
|
||||||
|
|
||||||
export function getGFSCollection(bucket: `${BucketNameEnum}`) {
|
export function getGFSCollection(bucket: `${BucketNameEnum}`) {
|
||||||
MongoDatasetFileSchema;
|
MongoDatasetFileSchema;
|
||||||
@@ -223,15 +223,13 @@ export const readFileContentFromMongo = async ({
|
|||||||
rawText: string;
|
rawText: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
}> => {
|
}> => {
|
||||||
const bufferId = `${fileId}-${customPdfParse}`;
|
const bufferId = `${String(fileId)}-${customPdfParse}`;
|
||||||
// read buffer
|
// read buffer
|
||||||
const fileBuffer = await MongoRawTextBuffer.findOne({ sourceId: bufferId }, undefined, {
|
const fileBuffer = await getRawTextBuffer(bufferId);
|
||||||
...readFromSecondary
|
|
||||||
}).lean();
|
|
||||||
if (fileBuffer) {
|
if (fileBuffer) {
|
||||||
return {
|
return {
|
||||||
rawText: fileBuffer.rawText,
|
rawText: fileBuffer.text,
|
||||||
filename: fileBuffer.metadata?.filename || ''
|
filename: fileBuffer?.sourceName
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,16 +263,13 @@ export const readFileContentFromMongo = async ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// < 14M
|
// Add buffer
|
||||||
if (fileBuffers.length < 14 * 1024 * 1024 && rawText.trim()) {
|
addRawTextBuffer({
|
||||||
MongoRawTextBuffer.create({
|
sourceId: bufferId,
|
||||||
sourceId: bufferId,
|
sourceName: file.filename,
|
||||||
rawText,
|
text: rawText,
|
||||||
metadata: {
|
expiredTime: addMinutes(new Date(), 20)
|
||||||
filename: file.filename
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rawText,
|
rawText,
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { Schema, getMongoModel } from '../../mongo';
|
import { Schema, getMongoModel } from '../../mongo';
|
||||||
|
|
||||||
const DatasetFileSchema = new Schema({});
|
const DatasetFileSchema = new Schema({
|
||||||
const ChatFileSchema = new Schema({});
|
metadata: Object
|
||||||
|
});
|
||||||
|
const ChatFileSchema = new Schema({
|
||||||
|
metadata: Object
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
DatasetFileSchema.index({ uploadDate: -1 });
|
||||||
DatasetFileSchema.index({ uploadDate: -1 });
|
|
||||||
|
|
||||||
ChatFileSchema.index({ uploadDate: -1 });
|
ChatFileSchema.index({ uploadDate: -1 });
|
||||||
ChatFileSchema.index({ 'metadata.chatId': 1 });
|
ChatFileSchema.index({ 'metadata.chatId': 1 });
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MongoDatasetFileSchema = getMongoModel('dataset.files', DatasetFileSchema);
|
export const MongoDatasetFileSchema = getMongoModel('dataset.files', DatasetFileSchema);
|
||||||
export const MongoChatFileSchema = getMongoModel('chat.files', ChatFileSchema);
|
export const MongoChatFileSchema = getMongoModel('chat.files', ChatFileSchema);
|
||||||
|
|||||||
@@ -1,5 +1,57 @@
|
|||||||
import { detectFileEncoding } from '@fastgpt/global/common/file/tools';
|
import { detectFileEncoding } from '@fastgpt/global/common/file/tools';
|
||||||
import { PassThrough } from 'stream';
|
import { PassThrough } from 'stream';
|
||||||
|
import { getGridBucket } from './controller';
|
||||||
|
import { type BucketNameEnum } from '@fastgpt/global/common/file/constants';
|
||||||
|
import { retryFn } from '@fastgpt/global/common/system/utils';
|
||||||
|
|
||||||
|
export const createFileFromText = async ({
|
||||||
|
bucket,
|
||||||
|
filename,
|
||||||
|
text,
|
||||||
|
metadata
|
||||||
|
}: {
|
||||||
|
bucket: `${BucketNameEnum}`;
|
||||||
|
filename: string;
|
||||||
|
text: string;
|
||||||
|
metadata: Record<string, any>;
|
||||||
|
}) => {
|
||||||
|
const gridBucket = getGridBucket(bucket);
|
||||||
|
|
||||||
|
const buffer = Buffer.from(text);
|
||||||
|
|
||||||
|
const fileSize = buffer.length;
|
||||||
|
// 单块大小:尽可能大,但不超过 14MB,不小于128KB
|
||||||
|
const chunkSizeBytes = (() => {
|
||||||
|
// 计算理想块大小:文件大小 ÷ 目标块数(10)。 并且每个块需要小于 14MB
|
||||||
|
const idealChunkSize = Math.min(Math.ceil(fileSize / 10), 14 * 1024 * 1024);
|
||||||
|
|
||||||
|
// 确保块大小至少为128KB
|
||||||
|
const minChunkSize = 128 * 1024; // 128KB
|
||||||
|
|
||||||
|
// 取理想块大小和最小块大小中的较大值
|
||||||
|
let chunkSize = Math.max(idealChunkSize, minChunkSize);
|
||||||
|
|
||||||
|
// 将块大小向上取整到最接近的64KB的倍数,使其更整齐
|
||||||
|
chunkSize = Math.ceil(chunkSize / (64 * 1024)) * (64 * 1024);
|
||||||
|
|
||||||
|
return chunkSize;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const uploadStream = gridBucket.openUploadStream(filename, {
|
||||||
|
metadata,
|
||||||
|
chunkSizeBytes
|
||||||
|
});
|
||||||
|
|
||||||
|
return retryFn(async () => {
|
||||||
|
return new Promise<{ fileId: string }>((resolve, reject) => {
|
||||||
|
uploadStream.end(buffer);
|
||||||
|
uploadStream.on('finish', () => {
|
||||||
|
resolve({ fileId: String(uploadStream.id) });
|
||||||
|
});
|
||||||
|
uploadStream.on('error', reject);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const gridFsStream2Buffer = (stream: NodeJS.ReadableStream) => {
|
export const gridFsStream2Buffer = (stream: NodeJS.ReadableStream) => {
|
||||||
return new Promise<Buffer>((resolve, reject) => {
|
return new Promise<Buffer>((resolve, reject) => {
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export const readRawContentByFileBuffer = async ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
rawText: text,
|
rawText: text,
|
||||||
formatText: rawText,
|
formatText: text,
|
||||||
imageList
|
imageList
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ export enum TimerIdEnum {
|
|||||||
clearExpiredSubPlan = 'clearExpiredSubPlan',
|
clearExpiredSubPlan = 'clearExpiredSubPlan',
|
||||||
updateStandardPlan = 'updateStandardPlan',
|
updateStandardPlan = 'updateStandardPlan',
|
||||||
scheduleTriggerApp = 'scheduleTriggerApp',
|
scheduleTriggerApp = 'scheduleTriggerApp',
|
||||||
notification = 'notification'
|
notification = 'notification',
|
||||||
|
clearExpiredRawTextBuffer = 'clearExpiredRawTextBuffer'
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LockNotificationEnum {
|
export enum LockNotificationEnum {
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ import { Types } from 'mongoose';
|
|||||||
community: community-id
|
community: community-id
|
||||||
commercial: commercial-id
|
commercial: commercial-id
|
||||||
*/
|
*/
|
||||||
|
export function splitCombineToolId(id: string) {
|
||||||
export async function splitCombinePluginId(id: string) {
|
|
||||||
const splitRes = id.split('-');
|
const splitRes = id.split('-');
|
||||||
if (splitRes.length === 1) {
|
if (splitRes.length === 1) {
|
||||||
// app id
|
// app id
|
||||||
@@ -42,7 +41,7 @@ export async function splitCombinePluginId(id: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [source, pluginId] = id.split('-') as [PluginSourceEnum, string];
|
const [source, pluginId] = id.split('-') as [PluginSourceEnum, string];
|
||||||
if (!source || !pluginId) return Promise.reject('pluginId not found');
|
if (!source || !pluginId) throw new Error('pluginId not found');
|
||||||
|
|
||||||
return { source, pluginId: id };
|
return { source, pluginId: id };
|
||||||
}
|
}
|
||||||
@@ -54,7 +53,7 @@ const getSystemPluginTemplateById = async (
|
|||||||
versionId?: string
|
versionId?: string
|
||||||
): Promise<ChildAppType> => {
|
): Promise<ChildAppType> => {
|
||||||
const item = getSystemPluginTemplates().find((plugin) => plugin.id === pluginId);
|
const item = getSystemPluginTemplates().find((plugin) => plugin.id === pluginId);
|
||||||
if (!item) return Promise.reject(PluginErrEnum.unAuth);
|
if (!item) return Promise.reject(PluginErrEnum.unExist);
|
||||||
|
|
||||||
const plugin = cloneDeep(item);
|
const plugin = cloneDeep(item);
|
||||||
|
|
||||||
@@ -64,10 +63,10 @@ const getSystemPluginTemplateById = async (
|
|||||||
{ pluginId: plugin.id, 'customConfig.associatedPluginId': plugin.associatedPluginId },
|
{ pluginId: plugin.id, 'customConfig.associatedPluginId': plugin.associatedPluginId },
|
||||||
'associatedPluginId'
|
'associatedPluginId'
|
||||||
).lean();
|
).lean();
|
||||||
if (!systemPlugin) return Promise.reject(PluginErrEnum.unAuth);
|
if (!systemPlugin) return Promise.reject(PluginErrEnum.unExist);
|
||||||
|
|
||||||
const app = await MongoApp.findById(plugin.associatedPluginId).lean();
|
const app = await MongoApp.findById(plugin.associatedPluginId).lean();
|
||||||
if (!app) return Promise.reject(PluginErrEnum.unAuth);
|
if (!app) return Promise.reject(PluginErrEnum.unExist);
|
||||||
|
|
||||||
const version = versionId
|
const version = versionId
|
||||||
? await getAppVersionById({
|
? await getAppVersionById({
|
||||||
@@ -77,6 +76,12 @@ const getSystemPluginTemplateById = async (
|
|||||||
})
|
})
|
||||||
: await getAppLatestVersion(plugin.associatedPluginId, app);
|
: await getAppLatestVersion(plugin.associatedPluginId, app);
|
||||||
if (!version.versionId) return Promise.reject('App version not found');
|
if (!version.versionId) return Promise.reject('App version not found');
|
||||||
|
const isLatest = version.versionId
|
||||||
|
? await checkIsLatestVersion({
|
||||||
|
appId: plugin.associatedPluginId,
|
||||||
|
versionId: version.versionId
|
||||||
|
})
|
||||||
|
: true;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...plugin,
|
...plugin,
|
||||||
@@ -85,12 +90,19 @@ const getSystemPluginTemplateById = async (
|
|||||||
edges: version.edges,
|
edges: version.edges,
|
||||||
chatConfig: version.chatConfig
|
chatConfig: version.chatConfig
|
||||||
},
|
},
|
||||||
version: versionId || String(version.versionId),
|
version: versionId ? version?.versionId : '',
|
||||||
|
versionLabel: version?.versionName,
|
||||||
|
isLatestVersion: isLatest,
|
||||||
teamId: String(app.teamId),
|
teamId: String(app.teamId),
|
||||||
tmbId: String(app.tmbId)
|
tmbId: String(app.tmbId)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return plugin;
|
|
||||||
|
return {
|
||||||
|
...plugin,
|
||||||
|
version: undefined,
|
||||||
|
isLatestVersion: true
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Format plugin to workflow preview node data */
|
/* Format plugin to workflow preview node data */
|
||||||
@@ -102,11 +114,11 @@ export async function getChildAppPreviewNode({
|
|||||||
versionId?: string;
|
versionId?: string;
|
||||||
}): Promise<FlowNodeTemplateType> {
|
}): Promise<FlowNodeTemplateType> {
|
||||||
const app: ChildAppType = await (async () => {
|
const app: ChildAppType = await (async () => {
|
||||||
const { source, pluginId } = await splitCombinePluginId(appId);
|
const { source, pluginId } = splitCombineToolId(appId);
|
||||||
|
|
||||||
if (source === PluginSourceEnum.personal) {
|
if (source === PluginSourceEnum.personal) {
|
||||||
const item = await MongoApp.findById(appId).lean();
|
const item = await MongoApp.findById(appId).lean();
|
||||||
if (!item) return Promise.reject('plugin not found');
|
if (!item) return Promise.reject(PluginErrEnum.unExist);
|
||||||
|
|
||||||
const version = await getAppVersionById({ appId, versionId, app: item });
|
const version = await getAppVersionById({ appId, versionId, app: item });
|
||||||
|
|
||||||
@@ -132,8 +144,8 @@ export async function getChildAppPreviewNode({
|
|||||||
},
|
},
|
||||||
templateType: FlowNodeTemplateTypeEnum.teamApp,
|
templateType: FlowNodeTemplateTypeEnum.teamApp,
|
||||||
|
|
||||||
version: version.versionId,
|
version: versionId ? version?.versionId : '',
|
||||||
versionLabel: version?.versionName || '',
|
versionLabel: version?.versionName,
|
||||||
isLatestVersion: isLatest,
|
isLatestVersion: isLatest,
|
||||||
|
|
||||||
originCost: 0,
|
originCost: 0,
|
||||||
@@ -142,7 +154,7 @@ export async function getChildAppPreviewNode({
|
|||||||
pluginOrder: 0
|
pluginOrder: 0
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return getSystemPluginTemplateById(pluginId);
|
return getSystemPluginTemplateById(pluginId, versionId);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -216,12 +228,12 @@ export async function getChildAppRuntimeById(
|
|||||||
id: string,
|
id: string,
|
||||||
versionId?: string
|
versionId?: string
|
||||||
): Promise<PluginRuntimeType> {
|
): Promise<PluginRuntimeType> {
|
||||||
const app: ChildAppType = await (async () => {
|
const app = await (async () => {
|
||||||
const { source, pluginId } = await splitCombinePluginId(id);
|
const { source, pluginId } = splitCombineToolId(id);
|
||||||
|
|
||||||
if (source === PluginSourceEnum.personal) {
|
if (source === PluginSourceEnum.personal) {
|
||||||
const item = await MongoApp.findById(id).lean();
|
const item = await MongoApp.findById(id).lean();
|
||||||
if (!item) return Promise.reject('plugin not found');
|
if (!item) return Promise.reject(PluginErrEnum.unExist);
|
||||||
|
|
||||||
const version = await getAppVersionById({
|
const version = await getAppVersionById({
|
||||||
appId: id,
|
appId: id,
|
||||||
@@ -244,8 +256,6 @@ export async function getChildAppRuntimeById(
|
|||||||
},
|
},
|
||||||
templateType: FlowNodeTemplateTypeEnum.teamApp,
|
templateType: FlowNodeTemplateTypeEnum.teamApp,
|
||||||
|
|
||||||
// 用不到
|
|
||||||
version: item?.pluginData?.nodeVersion,
|
|
||||||
originCost: 0,
|
originCost: 0,
|
||||||
currentCost: 0,
|
currentCost: 0,
|
||||||
hasTokenFee: false,
|
hasTokenFee: false,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { type ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
|
import { type ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
|
||||||
import { type PluginRuntimeType } from '@fastgpt/global/core/plugin/type';
|
import { type PluginRuntimeType } from '@fastgpt/global/core/plugin/type';
|
||||||
import { splitCombinePluginId } from './controller';
|
import { splitCombineToolId } from './controller';
|
||||||
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
|
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -20,7 +20,7 @@ export const computedPluginUsage = async ({
|
|||||||
childrenUsage: ChatNodeUsageType[];
|
childrenUsage: ChatNodeUsageType[];
|
||||||
error?: boolean;
|
error?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const { source } = await splitCombinePluginId(plugin.id);
|
const { source } = splitCombineToolId(plugin.id);
|
||||||
const childrenUsages = childrenUsage.reduce((sum, item) => sum + (item.totalPoints || 0), 0);
|
const childrenUsages = childrenUsage.reduce((sum, item) => sum + (item.totalPoints || 0), 0);
|
||||||
|
|
||||||
if (source !== PluginSourceEnum.personal) {
|
if (source !== PluginSourceEnum.personal) {
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { MongoDataset } from '../dataset/schema';
|
import { MongoDataset } from '../dataset/schema';
|
||||||
import { getEmbeddingModel } from '../ai/model';
|
import { getEmbeddingModel } from '../ai/model';
|
||||||
import {
|
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||||
AppNodeFlowNodeTypeMap,
|
|
||||||
FlowNodeTypeEnum
|
|
||||||
} from '@fastgpt/global/core/workflow/node/constant';
|
|
||||||
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||||
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';
|
||||||
import { MongoAppVersion } from './version/schema';
|
import { getChildAppPreviewNode, splitCombineToolId } from './plugin/controller';
|
||||||
import { checkIsLatestVersion } from './version/controller';
|
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
|
||||||
import { Types } from '../../common/mongo';
|
import { authAppByTmbId } from '../../support/permission/app/auth';
|
||||||
|
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||||
|
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||||
|
|
||||||
export async function listAppDatasetDataByTeamIdAndDatasetIds({
|
export async function listAppDatasetDataByTeamIdAndDatasetIds({
|
||||||
teamId,
|
teamId,
|
||||||
@@ -33,53 +32,58 @@ export async function listAppDatasetDataByTeamIdAndDatasetIds({
|
|||||||
export async function rewriteAppWorkflowToDetail({
|
export async function rewriteAppWorkflowToDetail({
|
||||||
nodes,
|
nodes,
|
||||||
teamId,
|
teamId,
|
||||||
isRoot
|
isRoot,
|
||||||
|
ownerTmbId
|
||||||
}: {
|
}: {
|
||||||
nodes: StoreNodeItemType[];
|
nodes: StoreNodeItemType[];
|
||||||
teamId: string;
|
teamId: string;
|
||||||
isRoot: boolean;
|
isRoot: boolean;
|
||||||
|
ownerTmbId: string;
|
||||||
}) {
|
}) {
|
||||||
const datasetIdSet = new Set<string>();
|
const datasetIdSet = new Set<string>();
|
||||||
|
|
||||||
// Add node(App Type) versionlabel and latest sign
|
/* Add node(App Type) versionlabel and latest sign ==== */
|
||||||
const appNodes = nodes.filter((node) => AppNodeFlowNodeTypeMap[node.flowNodeType]);
|
await Promise.all(
|
||||||
const versionIds = appNodes
|
nodes.map(async (node) => {
|
||||||
.filter((node) => node.version && Types.ObjectId.isValid(node.version))
|
if (!node.pluginId) return;
|
||||||
.map((node) => node.version);
|
const { source } = splitCombineToolId(node.pluginId);
|
||||||
|
|
||||||
if (versionIds.length > 0) {
|
try {
|
||||||
const versionDataList = await MongoAppVersion.find(
|
const [preview] = await Promise.all([
|
||||||
{
|
getChildAppPreviewNode({
|
||||||
_id: { $in: versionIds }
|
appId: node.pluginId,
|
||||||
},
|
versionId: node.version
|
||||||
'_id versionName appId time'
|
}),
|
||||||
).lean();
|
...(source === PluginSourceEnum.personal
|
||||||
|
? [
|
||||||
|
authAppByTmbId({
|
||||||
|
tmbId: ownerTmbId,
|
||||||
|
appId: node.pluginId,
|
||||||
|
per: ReadPermissionVal
|
||||||
|
})
|
||||||
|
]
|
||||||
|
: [])
|
||||||
|
]);
|
||||||
|
|
||||||
const versionMap: Record<string, any> = {};
|
node.pluginData = {
|
||||||
|
diagram: preview.diagram,
|
||||||
const isLatestChecks = await Promise.all(
|
userGuide: preview.userGuide,
|
||||||
versionDataList.map(async (version) => {
|
courseUrl: preview.courseUrl,
|
||||||
const isLatest = await checkIsLatestVersion({
|
name: preview.name,
|
||||||
appId: version.appId,
|
avatar: preview.avatar
|
||||||
versionId: version._id
|
};
|
||||||
});
|
node.versionLabel = preview.versionLabel;
|
||||||
|
node.isLatestVersion = preview.isLatestVersion;
|
||||||
return { versionId: String(version._id), isLatest };
|
node.version = preview.version;
|
||||||
})
|
} catch (error) {
|
||||||
);
|
node.pluginData = {
|
||||||
const isLatestMap = new Map(isLatestChecks.map((item) => [item.versionId, item.isLatest]));
|
error: getErrText(error)
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
);
|
||||||
|
|
||||||
|
/* Add node(App Type) versionlabel and latest sign ==== */
|
||||||
|
|
||||||
// Get all dataset ids from nodes
|
// Get all dataset ids from nodes
|
||||||
nodes.forEach((node) => {
|
nodes.forEach((node) => {
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ export const checkIsLatestVersion = async ({
|
|||||||
appId: string;
|
appId: string;
|
||||||
versionId: string;
|
versionId: string;
|
||||||
}) => {
|
}) => {
|
||||||
|
if (!Types.ObjectId.isValid(versionId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const version = await MongoAppVersion.findOne(
|
const version = await MongoAppVersion.findOne(
|
||||||
{
|
{
|
||||||
appId,
|
appId,
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { getTrainingModeByCollection } from './utils';
|
|||||||
import {
|
import {
|
||||||
computeChunkSize,
|
computeChunkSize,
|
||||||
computeChunkSplitter,
|
computeChunkSplitter,
|
||||||
|
computeParagraphChunkDeep,
|
||||||
getLLMMaxChunkSize
|
getLLMMaxChunkSize
|
||||||
} from '@fastgpt/global/core/dataset/training/utils';
|
} from '@fastgpt/global/core/dataset/training/utils';
|
||||||
import { DatasetDataIndexTypeEnum } from '@fastgpt/global/core/dataset/data/constants';
|
import { DatasetDataIndexTypeEnum } from '@fastgpt/global/core/dataset/data/constants';
|
||||||
@@ -74,7 +75,12 @@ export const createCollectionAndInsertData = async ({
|
|||||||
llmModel: getLLMModel(dataset.agentModel)
|
llmModel: getLLMModel(dataset.agentModel)
|
||||||
});
|
});
|
||||||
const chunkSplitter = computeChunkSplitter(createCollectionParams);
|
const chunkSplitter = computeChunkSplitter(createCollectionParams);
|
||||||
if (trainingType === DatasetCollectionDataProcessModeEnum.qa) {
|
const paragraphChunkDeep = computeParagraphChunkDeep(createCollectionParams);
|
||||||
|
|
||||||
|
if (
|
||||||
|
trainingType === DatasetCollectionDataProcessModeEnum.qa ||
|
||||||
|
trainingType === DatasetCollectionDataProcessModeEnum.backup
|
||||||
|
) {
|
||||||
delete createCollectionParams.chunkTriggerType;
|
delete createCollectionParams.chunkTriggerType;
|
||||||
delete createCollectionParams.chunkTriggerMinSize;
|
delete createCollectionParams.chunkTriggerMinSize;
|
||||||
delete createCollectionParams.dataEnhanceCollectionName;
|
delete createCollectionParams.dataEnhanceCollectionName;
|
||||||
@@ -87,7 +93,11 @@ export const createCollectionAndInsertData = async ({
|
|||||||
// 1. split chunks
|
// 1. split chunks
|
||||||
const chunks = rawText2Chunks({
|
const chunks = rawText2Chunks({
|
||||||
rawText,
|
rawText,
|
||||||
|
chunkTriggerType: createCollectionParams.chunkTriggerType,
|
||||||
|
chunkTriggerMinSize: createCollectionParams.chunkTriggerMinSize,
|
||||||
chunkSize,
|
chunkSize,
|
||||||
|
paragraphChunkDeep,
|
||||||
|
paragraphChunkMinSize: createCollectionParams.paragraphChunkMinSize,
|
||||||
maxSize: getLLMMaxChunkSize(getLLMModel(dataset.agentModel)),
|
maxSize: getLLMMaxChunkSize(getLLMModel(dataset.agentModel)),
|
||||||
overlapRatio: trainingType === DatasetCollectionDataProcessModeEnum.chunk ? 0.2 : 0,
|
overlapRatio: trainingType === DatasetCollectionDataProcessModeEnum.chunk ? 0.2 : 0,
|
||||||
customReg: chunkSplitter ? [chunkSplitter] : [],
|
customReg: chunkSplitter ? [chunkSplitter] : [],
|
||||||
@@ -112,6 +122,7 @@ export const createCollectionAndInsertData = async ({
|
|||||||
const { _id: collectionId } = await createOneCollection({
|
const { _id: collectionId } = await createOneCollection({
|
||||||
...createCollectionParams,
|
...createCollectionParams,
|
||||||
trainingType,
|
trainingType,
|
||||||
|
paragraphChunkDeep,
|
||||||
chunkSize,
|
chunkSize,
|
||||||
chunkSplitter,
|
chunkSplitter,
|
||||||
|
|
||||||
@@ -212,46 +223,19 @@ export type CreateOneCollectionParams = CreateDatasetCollectionParams & {
|
|||||||
tmbId: string;
|
tmbId: string;
|
||||||
session?: ClientSession;
|
session?: ClientSession;
|
||||||
};
|
};
|
||||||
export async function createOneCollection({
|
export async function createOneCollection({ session, ...props }: CreateOneCollectionParams) {
|
||||||
teamId,
|
const {
|
||||||
tmbId,
|
teamId,
|
||||||
name,
|
parentId,
|
||||||
parentId,
|
datasetId,
|
||||||
datasetId,
|
tags,
|
||||||
type,
|
|
||||||
|
|
||||||
createTime,
|
fileId,
|
||||||
updateTime,
|
rawLink,
|
||||||
|
externalFileId,
|
||||||
hashRawText,
|
externalFileUrl,
|
||||||
rawTextLength,
|
apiFileId
|
||||||
metadata = {},
|
} = props;
|
||||||
tags,
|
|
||||||
|
|
||||||
nextSyncTime,
|
|
||||||
|
|
||||||
fileId,
|
|
||||||
rawLink,
|
|
||||||
externalFileId,
|
|
||||||
externalFileUrl,
|
|
||||||
apiFileId,
|
|
||||||
|
|
||||||
// Parse settings
|
|
||||||
customPdfParse,
|
|
||||||
imageIndex,
|
|
||||||
autoIndexes,
|
|
||||||
|
|
||||||
// Chunk settings
|
|
||||||
trainingType,
|
|
||||||
chunkSettingMode,
|
|
||||||
chunkSplitMode,
|
|
||||||
chunkSize,
|
|
||||||
indexSize,
|
|
||||||
chunkSplitter,
|
|
||||||
qaPrompt,
|
|
||||||
|
|
||||||
session
|
|
||||||
}: CreateOneCollectionParams) {
|
|
||||||
// Create collection tags
|
// Create collection tags
|
||||||
const collectionTags = await createOrGetCollectionTags({ tags, teamId, datasetId, session });
|
const collectionTags = await createOrGetCollectionTags({ tags, teamId, datasetId, session });
|
||||||
|
|
||||||
@@ -259,41 +243,18 @@ export async function createOneCollection({
|
|||||||
const [collection] = await MongoDatasetCollection.create(
|
const [collection] = await MongoDatasetCollection.create(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
...props,
|
||||||
teamId,
|
teamId,
|
||||||
tmbId,
|
|
||||||
parentId: parentId || null,
|
parentId: parentId || null,
|
||||||
datasetId,
|
datasetId,
|
||||||
name,
|
|
||||||
type,
|
|
||||||
|
|
||||||
rawTextLength,
|
|
||||||
hashRawText,
|
|
||||||
tags: collectionTags,
|
tags: collectionTags,
|
||||||
metadata,
|
|
||||||
|
|
||||||
createTime,
|
|
||||||
updateTime,
|
|
||||||
nextSyncTime,
|
|
||||||
|
|
||||||
...(fileId ? { fileId } : {}),
|
...(fileId ? { fileId } : {}),
|
||||||
...(rawLink ? { rawLink } : {}),
|
...(rawLink ? { rawLink } : {}),
|
||||||
...(externalFileId ? { externalFileId } : {}),
|
...(externalFileId ? { externalFileId } : {}),
|
||||||
...(externalFileUrl ? { externalFileUrl } : {}),
|
...(externalFileUrl ? { externalFileUrl } : {}),
|
||||||
...(apiFileId ? { apiFileId } : {}),
|
...(apiFileId ? { apiFileId } : {})
|
||||||
|
|
||||||
// Parse settings
|
|
||||||
customPdfParse,
|
|
||||||
imageIndex,
|
|
||||||
autoIndexes,
|
|
||||||
|
|
||||||
// Chunk settings
|
|
||||||
trainingType,
|
|
||||||
chunkSettingMode,
|
|
||||||
chunkSplitMode,
|
|
||||||
chunkSize,
|
|
||||||
indexSize,
|
|
||||||
chunkSplitter,
|
|
||||||
qaPrompt
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
{ session, ordered: true }
|
{ session, ordered: true }
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { BucketNameEnum } from '@fastgpt/global/common/file/constants';
|
import { BucketNameEnum } from '@fastgpt/global/common/file/constants';
|
||||||
import { DatasetSourceReadTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
import {
|
||||||
|
ChunkTriggerConfigTypeEnum,
|
||||||
|
DatasetSourceReadTypeEnum
|
||||||
|
} from '@fastgpt/global/core/dataset/constants';
|
||||||
import { readFileContentFromMongo } from '../../common/file/gridfs/controller';
|
import { readFileContentFromMongo } from '../../common/file/gridfs/controller';
|
||||||
import { urlsFetch } from '../../common/string/cheerio';
|
import { urlsFetch } from '../../common/string/cheerio';
|
||||||
import { type TextSplitProps, splitText2Chunks } from '@fastgpt/global/common/string/textSplitter';
|
import { type TextSplitProps, splitText2Chunks } from '@fastgpt/global/common/string/textSplitter';
|
||||||
@@ -179,11 +182,17 @@ export const readApiServerFileContent = async ({
|
|||||||
|
|
||||||
export const rawText2Chunks = ({
|
export const rawText2Chunks = ({
|
||||||
rawText,
|
rawText,
|
||||||
|
chunkTriggerType = ChunkTriggerConfigTypeEnum.minSize,
|
||||||
|
chunkTriggerMinSize = 1000,
|
||||||
backupParse,
|
backupParse,
|
||||||
chunkSize = 512,
|
chunkSize = 512,
|
||||||
...splitProps
|
...splitProps
|
||||||
}: {
|
}: {
|
||||||
rawText: string;
|
rawText: string;
|
||||||
|
|
||||||
|
chunkTriggerType?: ChunkTriggerConfigTypeEnum;
|
||||||
|
chunkTriggerMinSize?: number; // maxSize from agent model, not store
|
||||||
|
|
||||||
backupParse?: boolean;
|
backupParse?: boolean;
|
||||||
tableParse?: boolean;
|
tableParse?: boolean;
|
||||||
} & TextSplitProps): {
|
} & TextSplitProps): {
|
||||||
@@ -213,6 +222,28 @@ export const rawText2Chunks = ({
|
|||||||
return parseDatasetBackup2Chunks(rawText).chunks;
|
return parseDatasetBackup2Chunks(rawText).chunks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Chunk condition
|
||||||
|
// 1. 选择最大值条件,只有超过了最大值(默认为模型的最大值*0.7),才会触发分块
|
||||||
|
if (chunkTriggerType === ChunkTriggerConfigTypeEnum.maxSize) {
|
||||||
|
const textLength = rawText.trim().length;
|
||||||
|
const maxSize = splitProps.maxSize ? splitProps.maxSize * 0.7 : 16000;
|
||||||
|
if (textLength < maxSize) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
q: rawText,
|
||||||
|
a: ''
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2. 选择最小值条件,只有超过最小值(手动决定)才会触发分块
|
||||||
|
if (chunkTriggerType !== ChunkTriggerConfigTypeEnum.forceChunk) {
|
||||||
|
const textLength = rawText.trim().length;
|
||||||
|
if (textLength < chunkTriggerMinSize) {
|
||||||
|
return [{ q: rawText, a: '' }];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const { chunks } = splitText2Chunks({
|
const { chunks } = splitText2Chunks({
|
||||||
text: rawText,
|
text: rawText,
|
||||||
chunkSize,
|
chunkSize,
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ export const ChunkSettings = {
|
|||||||
},
|
},
|
||||||
paragraphChunkDeep: Number,
|
paragraphChunkDeep: Number,
|
||||||
paragraphChunkMinSize: Number,
|
paragraphChunkMinSize: Number,
|
||||||
paragraphChunkMaxSize: Number,
|
|
||||||
chunkSize: Number,
|
chunkSize: Number,
|
||||||
chunkSplitter: String,
|
chunkSplitter: String,
|
||||||
|
|
||||||
|
|||||||
@@ -658,7 +658,7 @@ export async function searchDatasetData(
|
|||||||
tokenLen: 0
|
tokenLen: 0
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addLog.error('multiQueryRecall error', error);
|
addLog.error('Full text search error', error);
|
||||||
return {
|
return {
|
||||||
fullTextRecallResults: [],
|
fullTextRecallResults: [],
|
||||||
tokenLen: 0
|
tokenLen: 0
|
||||||
|
|||||||
@@ -86,7 +86,6 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Check interactive entry
|
// Check interactive entry
|
||||||
const interactiveResponse = lastInteractive;
|
|
||||||
props.node.isEntry = false;
|
props.node.isEntry = false;
|
||||||
const hasReadFilesTool = toolNodes.some(
|
const hasReadFilesTool = toolNodes.some(
|
||||||
(item) => item.flowNodeType === FlowNodeTypeEnum.readFiles
|
(item) => item.flowNodeType === FlowNodeTypeEnum.readFiles
|
||||||
@@ -143,7 +142,7 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
if (interactiveResponse) {
|
if (lastInteractive && isEntry) {
|
||||||
return value.slice(0, -2);
|
return value.slice(0, -2);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
@@ -183,7 +182,7 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
|||||||
toolModel,
|
toolModel,
|
||||||
maxRunToolTimes: 30,
|
maxRunToolTimes: 30,
|
||||||
messages: adaptMessages,
|
messages: adaptMessages,
|
||||||
interactiveEntryToolParams: interactiveResponse?.toolParams
|
interactiveEntryToolParams: lastInteractive?.toolParams
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (toolModel.functionCall) {
|
if (toolModel.functionCall) {
|
||||||
@@ -194,7 +193,7 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
|||||||
toolNodes,
|
toolNodes,
|
||||||
toolModel,
|
toolModel,
|
||||||
messages: adaptMessages,
|
messages: adaptMessages,
|
||||||
interactiveEntryToolParams: interactiveResponse?.toolParams
|
interactiveEntryToolParams: lastInteractive?.toolParams
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +223,7 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
|
|||||||
toolNodes,
|
toolNodes,
|
||||||
toolModel,
|
toolModel,
|
||||||
messages: adaptMessages,
|
messages: adaptMessages,
|
||||||
interactiveEntryToolParams: interactiveResponse?.toolParams
|
interactiveEntryToolParams: lastInteractive?.toolParams
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import type {
|
|||||||
SystemVariablesType
|
SystemVariablesType
|
||||||
} from '@fastgpt/global/core/workflow/runtime/type';
|
} from '@fastgpt/global/core/workflow/runtime/type';
|
||||||
import type { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type.d';
|
import type { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type.d';
|
||||||
import type { FlowNodeOutputItemType } from '@fastgpt/global/core/workflow/type/io.d';
|
|
||||||
import type {
|
import type {
|
||||||
AIChatItemValueItemType,
|
AIChatItemValueItemType,
|
||||||
ChatHistoryItemResType,
|
ChatHistoryItemResType,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { chatValue2RuntimePrompt } from '@fastgpt/global/core/chat/adapt';
|
|||||||
import { getPluginRunUserQuery } from '@fastgpt/global/core/workflow/utils';
|
import { getPluginRunUserQuery } from '@fastgpt/global/core/workflow/utils';
|
||||||
import { getPluginInputsFromStoreNodes } from '@fastgpt/global/core/app/plugin/utils';
|
import { getPluginInputsFromStoreNodes } from '@fastgpt/global/core/app/plugin/utils';
|
||||||
import type { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
import type { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
||||||
|
import { getUserChatInfoAndAuthTeamPoints } from '../../../../support/permission/auth/team';
|
||||||
|
|
||||||
type RunPluginProps = ModuleDispatchProps<{
|
type RunPluginProps = ModuleDispatchProps<{
|
||||||
[NodeInputKeyEnum.forbidStream]?: boolean;
|
[NodeInputKeyEnum.forbidStream]?: boolean;
|
||||||
@@ -73,9 +74,11 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { externalProvider } = await getUserChatInfoAndAuthTeamPoints(runningAppInfo.tmbId);
|
||||||
const runtimeVariables = {
|
const runtimeVariables = {
|
||||||
...filterSystemVariables(props.variables),
|
...filterSystemVariables(props.variables),
|
||||||
appId: String(plugin.id)
|
appId: String(plugin.id),
|
||||||
|
...(externalProvider ? externalProvider.externalWorkflowVariables : {})
|
||||||
};
|
};
|
||||||
const { flowResponses, flowUsages, assistantResponses, runTimes } = await dispatchWorkFlow({
|
const { flowResponses, flowUsages, assistantResponses, runTimes } = await dispatchWorkFlow({
|
||||||
...props,
|
...props,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
|||||||
import { getAppVersionById } from '../../../app/version/controller';
|
import { getAppVersionById } from '../../../app/version/controller';
|
||||||
import { parseUrlToFileType } from '@fastgpt/global/common/file/tools';
|
import { parseUrlToFileType } from '@fastgpt/global/common/file/tools';
|
||||||
import { type ChildrenInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
import { type ChildrenInteractive } from '@fastgpt/global/core/workflow/template/system/interactive/type';
|
||||||
|
import { getUserChatInfoAndAuthTeamPoints } from '../../../../support/permission/auth/team';
|
||||||
|
|
||||||
type Props = ModuleDispatchProps<{
|
type Props = ModuleDispatchProps<{
|
||||||
[NodeInputKeyEnum.userChatInput]: string;
|
[NodeInputKeyEnum.userChatInput]: string;
|
||||||
@@ -97,11 +98,13 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
|
|||||||
|
|
||||||
// Rewrite children app variables
|
// Rewrite children app variables
|
||||||
const systemVariables = filterSystemVariables(variables);
|
const systemVariables = filterSystemVariables(variables);
|
||||||
|
const { externalProvider } = await getUserChatInfoAndAuthTeamPoints(appData.tmbId);
|
||||||
const childrenRunVariables = {
|
const childrenRunVariables = {
|
||||||
...systemVariables,
|
...systemVariables,
|
||||||
...childrenAppVariables,
|
...childrenAppVariables,
|
||||||
histories: chatHistories,
|
histories: chatHistories,
|
||||||
appId: String(appData._id)
|
appId: String(appData._id),
|
||||||
|
...(externalProvider ? externalProvider.externalWorkflowVariables : {})
|
||||||
};
|
};
|
||||||
|
|
||||||
const childrenInteractive =
|
const childrenInteractive =
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import { NodeOutputKeyEnum } from '@fastgpt/global/core/workflow/constants';
|
|||||||
import { type DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
import { type DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { serverRequestBaseUrl } from '../../../../common/api/serverRequest';
|
import { serverRequestBaseUrl } from '../../../../common/api/serverRequest';
|
||||||
import { MongoRawTextBuffer } from '../../../../common/buffer/rawText/schema';
|
|
||||||
import { readFromSecondary } from '../../../../common/mongo/utils';
|
|
||||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||||
import { detectFileEncoding, parseUrlToFileType } from '@fastgpt/global/common/file/tools';
|
import { detectFileEncoding, parseUrlToFileType } from '@fastgpt/global/common/file/tools';
|
||||||
import { readRawContentByFileBuffer } from '../../../../common/file/read/utils';
|
import { readRawContentByFileBuffer } from '../../../../common/file/read/utils';
|
||||||
@@ -14,6 +12,8 @@ import { ChatRoleEnum } from '@fastgpt/global/core/chat/constants';
|
|||||||
import { type ChatItemType, type UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
import { type ChatItemType, type UserChatItemValueItemType } from '@fastgpt/global/core/chat/type';
|
||||||
import { parseFileExtensionFromUrl } from '@fastgpt/global/common/string/tools';
|
import { parseFileExtensionFromUrl } from '@fastgpt/global/common/string/tools';
|
||||||
import { addLog } from '../../../../common/system/log';
|
import { addLog } from '../../../../common/system/log';
|
||||||
|
import { addRawTextBuffer, getRawTextBuffer } from '../../../../common/buffer/rawText/controller';
|
||||||
|
import { addMinutes } from 'date-fns';
|
||||||
|
|
||||||
type Props = ModuleDispatchProps<{
|
type Props = ModuleDispatchProps<{
|
||||||
[NodeInputKeyEnum.fileUrlList]: string[];
|
[NodeInputKeyEnum.fileUrlList]: string[];
|
||||||
@@ -158,14 +158,12 @@ export const getFileContentFromLinks = async ({
|
|||||||
parseUrlList
|
parseUrlList
|
||||||
.map(async (url) => {
|
.map(async (url) => {
|
||||||
// Get from buffer
|
// Get from buffer
|
||||||
const fileBuffer = await MongoRawTextBuffer.findOne({ sourceId: url }, undefined, {
|
const fileBuffer = await getRawTextBuffer(url);
|
||||||
...readFromSecondary
|
|
||||||
}).lean();
|
|
||||||
if (fileBuffer) {
|
if (fileBuffer) {
|
||||||
return formatResponseObject({
|
return formatResponseObject({
|
||||||
filename: fileBuffer.metadata?.filename || url,
|
filename: fileBuffer.sourceName || url,
|
||||||
url,
|
url,
|
||||||
content: fileBuffer.rawText
|
content: fileBuffer.text
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,17 +218,12 @@ export const getFileContentFromLinks = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Add to buffer
|
// Add to buffer
|
||||||
try {
|
addRawTextBuffer({
|
||||||
if (buffer.length < 14 * 1024 * 1024 && rawText.trim()) {
|
sourceId: url,
|
||||||
MongoRawTextBuffer.create({
|
sourceName: filename,
|
||||||
sourceId: url,
|
text: rawText,
|
||||||
rawText,
|
expiredTime: addMinutes(new Date(), 20)
|
||||||
metadata: {
|
});
|
||||||
filename: filename
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {}
|
|
||||||
|
|
||||||
return formatResponseObject({ filename, url, content: rawText });
|
return formatResponseObject({ filename, url, content: rawText });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { AppPermission } from '@fastgpt/global/support/permission/app/controller
|
|||||||
import { type PermissionValueType } from '@fastgpt/global/support/permission/type';
|
import { type PermissionValueType } from '@fastgpt/global/support/permission/type';
|
||||||
import { AppFolderTypeList } from '@fastgpt/global/core/app/constants';
|
import { AppFolderTypeList } from '@fastgpt/global/core/app/constants';
|
||||||
import { type ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
import { type ParentIdType } from '@fastgpt/global/common/parentFolder/type';
|
||||||
import { splitCombinePluginId } from '../../../core/app/plugin/controller';
|
import { splitCombineToolId } from '../../../core/app/plugin/controller';
|
||||||
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
|
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
|
||||||
import { type AuthModeType, type AuthResponseType } from '../type';
|
import { type AuthModeType, type AuthResponseType } from '../type';
|
||||||
import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant';
|
import { AppDefaultPermissionVal } from '@fastgpt/global/support/permission/app/constant';
|
||||||
@@ -24,7 +24,7 @@ export const authPluginByTmbId = async ({
|
|||||||
appId: string;
|
appId: string;
|
||||||
per: PermissionValueType;
|
per: PermissionValueType;
|
||||||
}) => {
|
}) => {
|
||||||
const { source } = await splitCombinePluginId(appId);
|
const { source } = splitCombineToolId(appId);
|
||||||
if (source === PluginSourceEnum.personal) {
|
if (source === PluginSourceEnum.personal) {
|
||||||
const { app } = await authAppByTmbId({
|
const { app } = await authAppByTmbId({
|
||||||
appId,
|
appId,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import iconv from 'iconv-lite';
|
import iconv from 'iconv-lite';
|
||||||
import { type ReadRawTextByBuffer, type ReadFileResponse } from '../type';
|
import { type ReadRawTextByBuffer, type ReadFileResponse } from '../type';
|
||||||
|
import { matchMdImg } from '@fastgpt/global/common/string/markdown';
|
||||||
|
|
||||||
const rawEncodingList = [
|
const rawEncodingList = [
|
||||||
'ascii',
|
'ascii',
|
||||||
@@ -34,7 +35,10 @@ export const readFileRawText = ({ buffer, encoding }: ReadRawTextByBuffer): Read
|
|||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
const { text, imageList } = matchMdImg(content);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rawText: content
|
rawText: text,
|
||||||
|
imageList
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const RadioGroup = <T = any,>({ list, value, onChange, ...props }: Props<T>) =>
|
|||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
<HStack spacing={1} color={'myGray.900'} whiteSpace={'nowrap'} fontSize={'sm'}>
|
<HStack spacing={0.5} color={'myGray.900'} whiteSpace={'nowrap'} fontSize={'sm'}>
|
||||||
<Box>{typeof item.title === 'string' ? t(item.title as any) : item.title}</Box>
|
<Box>{typeof item.title === 'string' ? t(item.title as any) : item.title}</Box>
|
||||||
{!!item.tooltip && <QuestionTip label={item.tooltip} color={'myGray.600'} />}
|
{!!item.tooltip && <QuestionTip label={item.tooltip} color={'myGray.600'} />}
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|||||||
@@ -1,17 +1,26 @@
|
|||||||
import { Box } from '@chakra-ui/react';
|
import { Box } from '@chakra-ui/react';
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
const HighlightText = ({
|
const HighlightText = ({
|
||||||
rawText,
|
rawText,
|
||||||
matchText,
|
matchText,
|
||||||
color = 'primary.600'
|
color = 'primary.600',
|
||||||
|
mode = 'text'
|
||||||
}: {
|
}: {
|
||||||
rawText: string;
|
rawText: string;
|
||||||
matchText: string;
|
matchText: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
|
mode?: 'text' | 'bg';
|
||||||
}) => {
|
}) => {
|
||||||
const regex = new RegExp(`(${matchText})`, 'gi');
|
const { parts } = useMemo(() => {
|
||||||
const parts = rawText.split(regex);
|
const regx = new RegExp(`(${matchText})`, 'gi');
|
||||||
|
const parts = rawText.split(regx);
|
||||||
|
|
||||||
|
return {
|
||||||
|
regx,
|
||||||
|
parts
|
||||||
|
};
|
||||||
|
}, [rawText, matchText]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
@@ -28,7 +37,17 @@ const HighlightText = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box as="span" key={index} color={highLight ? color : 'inherit'}>
|
<Box
|
||||||
|
as="span"
|
||||||
|
key={index}
|
||||||
|
{...(mode === 'bg'
|
||||||
|
? {
|
||||||
|
bg: highLight ? color : 'transparent'
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
color: highLight ? color : 'inherit'
|
||||||
|
})}
|
||||||
|
>
|
||||||
{part}
|
{part}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -37,4 +56,4 @@ const HighlightText = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default HighlightText;
|
export default React.memo(HighlightText);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default function Variable({ variableLabel }: { variableLabel: string }) {
|
|||||||
: { bg: 'red.50', color: 'red.600' })}
|
: { bg: 'red.50', color: 'red.600' })}
|
||||||
>
|
>
|
||||||
{variableLabel ? (
|
{variableLabel ? (
|
||||||
<Flex alignItems={'center'}>{variableLabel}</Flex>
|
<Flex alignItems={'center'}>{t(variableLabel as any)}</Flex>
|
||||||
) : (
|
) : (
|
||||||
<Box>{t('common:invalid_variable')}</Box>
|
<Box>{t('common:invalid_variable')}</Box>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ export function useScrollPagination<
|
|||||||
scrollLoadType = 'bottom',
|
scrollLoadType = 'bottom',
|
||||||
|
|
||||||
pageSize = 10,
|
pageSize = 10,
|
||||||
params = {},
|
params,
|
||||||
EmptyTip,
|
EmptyTip,
|
||||||
showErrorToast = true,
|
showErrorToast = true,
|
||||||
disalbed = false,
|
disalbed = false,
|
||||||
@@ -196,7 +196,7 @@ export function useScrollPagination<
|
|||||||
scrollLoadType?: 'top' | 'bottom';
|
scrollLoadType?: 'top' | 'bottom';
|
||||||
|
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
params?: Record<string, any>;
|
params?: Omit<TParams, 'offset' | 'pageSize'>;
|
||||||
EmptyTip?: React.JSX.Element;
|
EmptyTip?: React.JSX.Element;
|
||||||
showErrorToast?: boolean;
|
showErrorToast?: boolean;
|
||||||
disalbed?: boolean;
|
disalbed?: boolean;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { useContextSelector } from 'use-context-selector';
|
|||||||
|
|
||||||
export const useSystem = () => {
|
export const useSystem = () => {
|
||||||
const isPc = useContextSelector(useSystemStoreContext, (state) => state.isPc);
|
const isPc = useContextSelector(useSystemStoreContext, (state) => state.isPc);
|
||||||
|
const isMac =
|
||||||
|
typeof window !== 'undefined' && window.navigator.userAgent.toLocaleLowerCase().includes('mac');
|
||||||
|
|
||||||
return { isPc };
|
return { isPc, isMac };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -85,6 +85,7 @@
|
|||||||
"interval.per_hour": "Every Hour",
|
"interval.per_hour": "Every Hour",
|
||||||
"intro": "A comprehensive model application orchestration system that offers out-of-the-box data processing and model invocation capabilities. It allows for rapid Dataset construction and workflow orchestration through Flow visualization, enabling complex Dataset scenarios!",
|
"intro": "A comprehensive model application orchestration system that offers out-of-the-box data processing and model invocation capabilities. It allows for rapid Dataset construction and workflow orchestration through Flow visualization, enabling complex Dataset scenarios!",
|
||||||
"invalid_json_format": "JSON format error",
|
"invalid_json_format": "JSON format error",
|
||||||
|
"keep_the_latest": "Keep the latest",
|
||||||
"llm_not_support_vision": "This model does not support image recognition",
|
"llm_not_support_vision": "This model does not support image recognition",
|
||||||
"llm_use_vision": "Vision",
|
"llm_use_vision": "Vision",
|
||||||
"llm_use_vision_tip": "After clicking on the model selection, you can see whether the model supports image recognition and the ability to control whether to start image recognition. \nAfter starting image recognition, the model will read the image content in the file link, and if the user question is less than 500 words, it will automatically parse the image in the user question.",
|
"llm_use_vision_tip": "After clicking on the model selection, you can see whether the model supports image recognition and the ability to control whether to start image recognition. \nAfter starting image recognition, the model will read the image content in the file link, and if the user question is less than 500 words, it will automatically parse the image in the user question.",
|
||||||
|
|||||||
@@ -145,8 +145,8 @@
|
|||||||
"code_error.outlink_error.invalid_link": "Invalid Share Link",
|
"code_error.outlink_error.invalid_link": "Invalid Share Link",
|
||||||
"code_error.outlink_error.link_not_exist": "Share Link Does Not Exist",
|
"code_error.outlink_error.link_not_exist": "Share Link Does Not Exist",
|
||||||
"code_error.outlink_error.un_auth_user": "Identity Verification Failed",
|
"code_error.outlink_error.un_auth_user": "Identity Verification Failed",
|
||||||
"code_error.plugin_error.not_exist": "Plugin Does Not Exist",
|
"code_error.plugin_error.not_exist": "The tool does not exist",
|
||||||
"code_error.plugin_error.un_auth": "Unauthorized to Operate This Plugin",
|
"code_error.plugin_error.un_auth": "No permission to operate the tool",
|
||||||
"code_error.system_error.community_version_num_limit": "Exceeded Open Source Version Limit, Please Upgrade to Commercial Version: https://tryfastgpt.ai",
|
"code_error.system_error.community_version_num_limit": "Exceeded Open Source Version Limit, Please Upgrade to Commercial Version: https://tryfastgpt.ai",
|
||||||
"code_error.system_error.license_app_amount_limit": "Exceed the maximum number of applications in the system",
|
"code_error.system_error.license_app_amount_limit": "Exceed the maximum number of applications in the system",
|
||||||
"code_error.system_error.license_dataset_amount_limit": "Exceed the maximum number of knowledge bases in the system",
|
"code_error.system_error.license_dataset_amount_limit": "Exceed the maximum number of knowledge bases in the system",
|
||||||
|
|||||||
@@ -15,7 +15,13 @@
|
|||||||
"backup_dataset_tip": "You can reimport the downloaded csv file when exporting the knowledge base.",
|
"backup_dataset_tip": "You can reimport the downloaded csv file when exporting the knowledge base.",
|
||||||
"backup_mode": "Backup import",
|
"backup_mode": "Backup import",
|
||||||
"chunk_max_tokens": "max_tokens",
|
"chunk_max_tokens": "max_tokens",
|
||||||
|
"chunk_process_params": "Block processing parameters",
|
||||||
"chunk_size": "Block size",
|
"chunk_size": "Block size",
|
||||||
|
"chunk_trigger": "Blocking conditions",
|
||||||
|
"chunk_trigger_force_chunk": "Forced chunking",
|
||||||
|
"chunk_trigger_max_size": "The original text length is greater than the maximum context of the file processing model 70%",
|
||||||
|
"chunk_trigger_min_size": "The original text is greater than",
|
||||||
|
"chunk_trigger_tips": "Block storage is triggered when certain conditions are met, otherwise the original text will be stored in full directly",
|
||||||
"close_auto_sync": "Are you sure you want to turn off automatic sync?",
|
"close_auto_sync": "Are you sure you want to turn off automatic sync?",
|
||||||
"collection.Create update time": "Creation/Update Time",
|
"collection.Create update time": "Creation/Update Time",
|
||||||
"collection.Training type": "Training",
|
"collection.Training type": "Training",
|
||||||
@@ -29,6 +35,7 @@
|
|||||||
"collection_tags": "Collection Tags",
|
"collection_tags": "Collection Tags",
|
||||||
"common_dataset": "General Dataset",
|
"common_dataset": "General Dataset",
|
||||||
"common_dataset_desc": "Building a knowledge base by importing files, web page links, or manual entry",
|
"common_dataset_desc": "Building a knowledge base by importing files, web page links, or manual entry",
|
||||||
|
"condition": "condition",
|
||||||
"config_sync_schedule": "Configure scheduled synchronization",
|
"config_sync_schedule": "Configure scheduled synchronization",
|
||||||
"confirm_to_rebuild_embedding_tip": "Are you sure you want to switch the index for the Dataset?\nSwitching the index is a significant operation that requires re-indexing all data in your Dataset, which may take a long time. Please ensure your account has sufficient remaining points.\n\nAdditionally, you need to update the applications that use this Dataset to avoid conflicts with other indexed model Datasets.",
|
"confirm_to_rebuild_embedding_tip": "Are you sure you want to switch the index for the Dataset?\nSwitching the index is a significant operation that requires re-indexing all data in your Dataset, which may take a long time. Please ensure your account has sufficient remaining points.\n\nAdditionally, you need to update the applications that use this Dataset to avoid conflicts with other indexed model Datasets.",
|
||||||
"core.dataset.import.Adjust parameters": "Adjust parameters",
|
"core.dataset.import.Adjust parameters": "Adjust parameters",
|
||||||
@@ -100,6 +107,7 @@
|
|||||||
"is_open_schedule": "Enable scheduled synchronization",
|
"is_open_schedule": "Enable scheduled synchronization",
|
||||||
"keep_image": "Keep the picture",
|
"keep_image": "Keep the picture",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
|
"max_chunk_size": "Maximum chunk size",
|
||||||
"move.hint": "After moving, the selected knowledge base/folder will inherit the permission settings of the new folder, and the original permission settings will become invalid.",
|
"move.hint": "After moving, the selected knowledge base/folder will inherit the permission settings of the new folder, and the original permission settings will become invalid.",
|
||||||
"noChildren": "No subdirectories",
|
"noChildren": "No subdirectories",
|
||||||
"noSelectedFolder": "No selected folder",
|
"noSelectedFolder": "No selected folder",
|
||||||
@@ -107,8 +115,10 @@
|
|||||||
"noValidId": "No valid ID",
|
"noValidId": "No valid ID",
|
||||||
"open_auto_sync": "After scheduled synchronization is turned on, the system will try to synchronize the collection from time to time every day. During the collection synchronization period, the collection data will not be searched.",
|
"open_auto_sync": "After scheduled synchronization is turned on, the system will try to synchronize the collection from time to time every day. During the collection synchronization period, the collection data will not be searched.",
|
||||||
"other_dataset": "Third-party knowledge base",
|
"other_dataset": "Third-party knowledge base",
|
||||||
|
"paragraph_max_deep": "Maximum paragraph depth",
|
||||||
|
"paragraph_split": "Partition by paragraph",
|
||||||
|
"paragraph_split_tip": "Priority is given to chunking according to the Makdown title paragraph. If the chunking is too long, then chunking is done according to the length.",
|
||||||
"params_config": "Config",
|
"params_config": "Config",
|
||||||
"params_setting": "Parameter settings",
|
|
||||||
"pdf_enhance_parse": "PDF enhancement analysis",
|
"pdf_enhance_parse": "PDF enhancement analysis",
|
||||||
"pdf_enhance_parse_price": "{{price}} points/page",
|
"pdf_enhance_parse_price": "{{price}} points/page",
|
||||||
"pdf_enhance_parse_tips": "Calling PDF recognition model for parsing, you can convert it into Markdown and retain pictures in the document. At the same time, you can also identify scanned documents, which will take a long time to identify them.",
|
"pdf_enhance_parse_tips": "Calling PDF recognition model for parsing, you can convert it into Markdown and retain pictures in the document. At the same time, you can also identify scanned documents, which will take a long time to identify them.",
|
||||||
|
|||||||
@@ -63,6 +63,8 @@
|
|||||||
"field_required": "Required",
|
"field_required": "Required",
|
||||||
"field_used_as_tool_input": "Used as Tool Call Parameter",
|
"field_used_as_tool_input": "Used as Tool Call Parameter",
|
||||||
"filter_description": "Currently supports filtering by tags and creation time. Fill in the format as follows:\n{\n \"tags\": {\n \"$and\": [\"Tag 1\",\"Tag 2\"],\n \"$or\": [\"When there are $and tags, and is effective, or is not effective\"]\n },\n \"createTime\": {\n \"$gte\": \"YYYY-MM-DD HH:mm format, collection creation time greater than this time\",\n \"$lte\": \"YYYY-MM-DD HH:mm format, collection creation time less than this time, can be used with $gte\"\n }\n}",
|
"filter_description": "Currently supports filtering by tags and creation time. Fill in the format as follows:\n{\n \"tags\": {\n \"$and\": [\"Tag 1\",\"Tag 2\"],\n \"$or\": [\"When there are $and tags, and is effective, or is not effective\"]\n },\n \"createTime\": {\n \"$gte\": \"YYYY-MM-DD HH:mm format, collection creation time greater than this time\",\n \"$lte\": \"YYYY-MM-DD HH:mm format, collection creation time less than this time, can be used with $gte\"\n }\n}",
|
||||||
|
"find_tip": "Find node ctrl f",
|
||||||
|
"find_tip_mac": "Find node ⌘ f",
|
||||||
"foldAll": "Collapse all",
|
"foldAll": "Collapse all",
|
||||||
"form_input_result": "User complete input result",
|
"form_input_result": "User complete input result",
|
||||||
"form_input_result_tip": "an object containing the complete result",
|
"form_input_result_tip": "an object containing the complete result",
|
||||||
@@ -123,18 +125,23 @@
|
|||||||
"max_tokens": "Maximum Tokens",
|
"max_tokens": "Maximum Tokens",
|
||||||
"mouse_priority": "Mouse first\n- Press the left button to drag the canvas\n- Hold down shift and left click to select batches",
|
"mouse_priority": "Mouse first\n- Press the left button to drag the canvas\n- Hold down shift and left click to select batches",
|
||||||
"new_context": "New Context",
|
"new_context": "New Context",
|
||||||
|
"next": "Next",
|
||||||
|
"no_match_node": "No results",
|
||||||
|
"no_node_found": "No node was not found",
|
||||||
"not_contains": "Does Not Contain",
|
"not_contains": "Does Not Contain",
|
||||||
"only_the_reference_type_is_supported": "Only reference type is supported",
|
"only_the_reference_type_is_supported": "Only reference type is supported",
|
||||||
"optional_value_type": "Optional Value Type",
|
"optional_value_type": "Optional Value Type",
|
||||||
"optional_value_type_tip": "You can specify one or more data types. When dynamically adding fields, users can only select the configured types.",
|
"optional_value_type_tip": "You can specify one or more data types. When dynamically adding fields, users can only select the configured types.",
|
||||||
"pan_priority": "Touchpad first\n- Click to batch select\n- Move the canvas with two fingers",
|
"pan_priority": "Touchpad first\n- Click to batch select\n- Move the canvas with two fingers",
|
||||||
"pass_returned_object_as_output_to_next_nodes": "Pass the object returned in the code as output to the next nodes. The variable name needs to correspond to the return key.",
|
"pass_returned_object_as_output_to_next_nodes": "Pass the object returned in the code as output to the next nodes. The variable name needs to correspond to the return key.",
|
||||||
|
"please_enter_node_name": "Enter the node name",
|
||||||
"plugin.Instruction_Tip": "You can configure an instruction to explain the purpose of the plugin. This instruction will be displayed each time the plugin is used. Supports standard Markdown syntax.",
|
"plugin.Instruction_Tip": "You can configure an instruction to explain the purpose of the plugin. This instruction will be displayed each time the plugin is used. Supports standard Markdown syntax.",
|
||||||
"plugin.Instructions": "Instructions",
|
"plugin.Instructions": "Instructions",
|
||||||
"plugin.global_file_input": "File links (deprecated)",
|
"plugin.global_file_input": "File links (deprecated)",
|
||||||
"plugin_file_abandon_tip": "Plugin global file upload has been deprecated, please adjust it as soon as possible. \nRelated functions can be achieved through plug-in input and adding image type input.",
|
"plugin_file_abandon_tip": "Plugin global file upload has been deprecated, please adjust it as soon as possible. \nRelated functions can be achieved through plug-in input and adding image type input.",
|
||||||
"plugin_input": "Plugin Input",
|
"plugin_input": "Plugin Input",
|
||||||
"plugin_output_tool": "When the plug-in is executed as a tool, whether this field responds as a result of the tool",
|
"plugin_output_tool": "When the plug-in is executed as a tool, whether this field responds as a result of the tool",
|
||||||
|
"previous": "Previous",
|
||||||
"question_classification": "Classify",
|
"question_classification": "Classify",
|
||||||
"question_optimization": "Query extension",
|
"question_optimization": "Query extension",
|
||||||
"quote_content_placeholder": "The structure of the reference content can be customized to better suit different scenarios. \nSome variables can be used for template configuration\n\n{{q}} - main content\n\n{{a}} - auxiliary data\n\n{{source}} - source name\n\n{{sourceId}} - source ID\n\n{{index}} - nth reference",
|
"quote_content_placeholder": "The structure of the reference content can be customized to better suit different scenarios. \nSome variables can be used for template configuration\n\n{{q}} - main content\n\n{{a}} - auxiliary data\n\n{{source}} - source name\n\n{{sourceId}} - source ID\n\n{{index}} - nth reference",
|
||||||
@@ -177,9 +184,9 @@
|
|||||||
"text_content_extraction": "Text Extract",
|
"text_content_extraction": "Text Extract",
|
||||||
"text_to_extract": "Text to Extract",
|
"text_to_extract": "Text to Extract",
|
||||||
"these_variables_will_be_input_parameters_for_code_execution": "These variables will be input parameters for code execution",
|
"these_variables_will_be_input_parameters_for_code_execution": "These variables will be input parameters for code execution",
|
||||||
"tool.tool_result": "Tool operation results",
|
|
||||||
"to_add_node": "to add",
|
"to_add_node": "to add",
|
||||||
"to_connect_node": "to connect",
|
"to_connect_node": "to connect",
|
||||||
|
"tool.tool_result": "Tool operation results",
|
||||||
"tool_call_termination": "Stop ToolCall",
|
"tool_call_termination": "Stop ToolCall",
|
||||||
"tool_custom_field": "Custom Tool",
|
"tool_custom_field": "Custom Tool",
|
||||||
"tool_field": " Tool Field Parameter Configuration",
|
"tool_field": " Tool Field Parameter Configuration",
|
||||||
|
|||||||
@@ -85,6 +85,7 @@
|
|||||||
"interval.per_hour": "每小时",
|
"interval.per_hour": "每小时",
|
||||||
"intro": "是一个大模型应用编排系统,提供开箱即用的数据处理、模型调用等能力,可以快速的构建知识库并通过 Flow 可视化进行工作流编排,实现复杂的知识库场景!",
|
"intro": "是一个大模型应用编排系统,提供开箱即用的数据处理、模型调用等能力,可以快速的构建知识库并通过 Flow 可视化进行工作流编排,实现复杂的知识库场景!",
|
||||||
"invalid_json_format": "JSON 格式错误",
|
"invalid_json_format": "JSON 格式错误",
|
||||||
|
"keep_the_latest": "保持最新版本",
|
||||||
"llm_not_support_vision": "该模型不支持图片识别",
|
"llm_not_support_vision": "该模型不支持图片识别",
|
||||||
"llm_use_vision": "图片识别",
|
"llm_use_vision": "图片识别",
|
||||||
"llm_use_vision_tip": "点击模型选择后,可以看到模型是否支持图片识别以及控制是否启动图片识别的能力。启动图片识别后,模型会读取文件链接里图片内容,并且如果用户问题少于 500 字,会自动解析用户问题中的图片。",
|
"llm_use_vision_tip": "点击模型选择后,可以看到模型是否支持图片识别以及控制是否启动图片识别的能力。启动图片识别后,模型会读取文件链接里图片内容,并且如果用户问题少于 500 字,会自动解析用户问题中的图片。",
|
||||||
|
|||||||
@@ -145,8 +145,8 @@
|
|||||||
"code_error.outlink_error.invalid_link": "分享链接无效",
|
"code_error.outlink_error.invalid_link": "分享链接无效",
|
||||||
"code_error.outlink_error.link_not_exist": "分享链接不存在",
|
"code_error.outlink_error.link_not_exist": "分享链接不存在",
|
||||||
"code_error.outlink_error.un_auth_user": "身份校验失败",
|
"code_error.outlink_error.un_auth_user": "身份校验失败",
|
||||||
"code_error.plugin_error.not_exist": "插件不存在",
|
"code_error.plugin_error.not_exist": "工具不存在",
|
||||||
"code_error.plugin_error.un_auth": "无权操作该插件",
|
"code_error.plugin_error.un_auth": "无权操作该工具",
|
||||||
"code_error.system_error.community_version_num_limit": "超出开源版数量限制,请升级商业版: https://fastgpt.in",
|
"code_error.system_error.community_version_num_limit": "超出开源版数量限制,请升级商业版: https://fastgpt.in",
|
||||||
"code_error.system_error.license_app_amount_limit": "超出系统最大应用数量",
|
"code_error.system_error.license_app_amount_limit": "超出系统最大应用数量",
|
||||||
"code_error.system_error.license_dataset_amount_limit": "超出系统最大知识库数量",
|
"code_error.system_error.license_dataset_amount_limit": "超出系统最大知识库数量",
|
||||||
@@ -554,7 +554,7 @@
|
|||||||
"core.dataset.training.Agent queue": "QA 训练排队",
|
"core.dataset.training.Agent queue": "QA 训练排队",
|
||||||
"core.dataset.training.Auto mode": "补充索引",
|
"core.dataset.training.Auto mode": "补充索引",
|
||||||
"core.dataset.training.Auto mode Tip": "通过子索引以及调用模型生成相关问题与摘要,来增加数据块的语义丰富度,更利于检索。需要消耗更多的存储空间和增加 AI 调用次数。",
|
"core.dataset.training.Auto mode Tip": "通过子索引以及调用模型生成相关问题与摘要,来增加数据块的语义丰富度,更利于检索。需要消耗更多的存储空间和增加 AI 调用次数。",
|
||||||
"core.dataset.training.Chunk mode": "直接分块",
|
"core.dataset.training.Chunk mode": "分块存储",
|
||||||
"core.dataset.training.Full": "预计 20 分钟以上",
|
"core.dataset.training.Full": "预计 20 分钟以上",
|
||||||
"core.dataset.training.Leisure": "空闲",
|
"core.dataset.training.Leisure": "空闲",
|
||||||
"core.dataset.training.QA mode": "问答对提取",
|
"core.dataset.training.QA mode": "问答对提取",
|
||||||
|
|||||||
@@ -15,7 +15,13 @@
|
|||||||
"backup_dataset_tip": "可以将导出知识库时,下载的 csv 文件重新导入。",
|
"backup_dataset_tip": "可以将导出知识库时,下载的 csv 文件重新导入。",
|
||||||
"backup_mode": "备份导入",
|
"backup_mode": "备份导入",
|
||||||
"chunk_max_tokens": "分块上限",
|
"chunk_max_tokens": "分块上限",
|
||||||
|
"chunk_process_params": "分块处理参数",
|
||||||
"chunk_size": "分块大小",
|
"chunk_size": "分块大小",
|
||||||
|
"chunk_trigger": "分块条件",
|
||||||
|
"chunk_trigger_force_chunk": "强制分块",
|
||||||
|
"chunk_trigger_max_size": "原文长度大于文件处理模型最大上下文70%",
|
||||||
|
"chunk_trigger_min_size": "原文长度大于",
|
||||||
|
"chunk_trigger_tips": "当满足一定条件时才触发分块存储,否则会直接完整存储原文",
|
||||||
"close_auto_sync": "确认关闭自动同步功能?",
|
"close_auto_sync": "确认关闭自动同步功能?",
|
||||||
"collection.Create update time": "创建/更新时间",
|
"collection.Create update time": "创建/更新时间",
|
||||||
"collection.Training type": "训练模式",
|
"collection.Training type": "训练模式",
|
||||||
@@ -29,6 +35,7 @@
|
|||||||
"collection_tags": "集合标签",
|
"collection_tags": "集合标签",
|
||||||
"common_dataset": "通用知识库",
|
"common_dataset": "通用知识库",
|
||||||
"common_dataset_desc": "通过导入文件、网页链接或手动录入形式构建知识库",
|
"common_dataset_desc": "通过导入文件、网页链接或手动录入形式构建知识库",
|
||||||
|
"condition": "条件",
|
||||||
"config_sync_schedule": "配置定时同步",
|
"config_sync_schedule": "配置定时同步",
|
||||||
"confirm_to_rebuild_embedding_tip": "确认为知识库切换索引?\n切换索引是一个非常重量的操作,需要对您知识库内所有数据进行重新索引,时间可能较长,请确保账号内剩余积分充足。\n\n此外,你还需要注意修改选择该知识库的应用,避免它们与其他索引模型知识库混用。",
|
"confirm_to_rebuild_embedding_tip": "确认为知识库切换索引?\n切换索引是一个非常重量的操作,需要对您知识库内所有数据进行重新索引,时间可能较长,请确保账号内剩余积分充足。\n\n此外,你还需要注意修改选择该知识库的应用,避免它们与其他索引模型知识库混用。",
|
||||||
"core.dataset.import.Adjust parameters": "调整参数",
|
"core.dataset.import.Adjust parameters": "调整参数",
|
||||||
@@ -100,6 +107,7 @@
|
|||||||
"is_open_schedule": "启用定时同步",
|
"is_open_schedule": "启用定时同步",
|
||||||
"keep_image": "保留图片",
|
"keep_image": "保留图片",
|
||||||
"loading": "加载中...",
|
"loading": "加载中...",
|
||||||
|
"max_chunk_size": "最大分块大小",
|
||||||
"move.hint": "移动后,所选知识库/文件夹将继承新文件夹的权限设置,原先的权限设置失效。",
|
"move.hint": "移动后,所选知识库/文件夹将继承新文件夹的权限设置,原先的权限设置失效。",
|
||||||
"noChildren": "无子目录",
|
"noChildren": "无子目录",
|
||||||
"noSelectedFolder": "没有选择文件夹",
|
"noSelectedFolder": "没有选择文件夹",
|
||||||
@@ -107,8 +115,10 @@
|
|||||||
"noValidId": "没有有效的 ID",
|
"noValidId": "没有有效的 ID",
|
||||||
"open_auto_sync": "开启定时同步后,系统将会每天不定时尝试同步集合,集合同步期间,会出现无法搜索到该集合数据现象。",
|
"open_auto_sync": "开启定时同步后,系统将会每天不定时尝试同步集合,集合同步期间,会出现无法搜索到该集合数据现象。",
|
||||||
"other_dataset": "第三方知识库",
|
"other_dataset": "第三方知识库",
|
||||||
|
"paragraph_max_deep": "最大段落深度",
|
||||||
|
"paragraph_split": "按段落分块",
|
||||||
|
"paragraph_split_tip": "优先按 Makdown 标题段落进行分块,如果分块过长,再按长度进行二次分块",
|
||||||
"params_config": "配置",
|
"params_config": "配置",
|
||||||
"params_setting": "参数设置",
|
|
||||||
"pdf_enhance_parse": "PDF增强解析",
|
"pdf_enhance_parse": "PDF增强解析",
|
||||||
"pdf_enhance_parse_price": "{{price}}积分/页",
|
"pdf_enhance_parse_price": "{{price}}积分/页",
|
||||||
"pdf_enhance_parse_tips": "调用 PDF 识别模型进行解析,可以将其转换成 Markdown 并保留文档中的图片,同时也可以对扫描件进行识别,识别时间较长。",
|
"pdf_enhance_parse_tips": "调用 PDF 识别模型进行解析,可以将其转换成 Markdown 并保留文档中的图片,同时也可以对扫描件进行识别,识别时间较长。",
|
||||||
|
|||||||
@@ -63,6 +63,8 @@
|
|||||||
"field_required": "必填",
|
"field_required": "必填",
|
||||||
"field_used_as_tool_input": "作为工具调用参数",
|
"field_used_as_tool_input": "作为工具调用参数",
|
||||||
"filter_description": "目前支持标签和创建时间过滤,需按照以下格式填写:\n{\n \"tags\": {\n \"$and\": [\"标签 1\",\"标签 2\"],\n \"$or\": [\"有 $and 标签时,and 生效,or 不生效\"]\n },\n \"createTime\": {\n \"$gte\": \"YYYY-MM-DD HH:mm 格式即可,集合的创建时间大于该时间\",\n \"$lte\": \"YYYY-MM-DD HH:mm 格式即可,集合的创建时间小于该时间,可和 $gte 共同使用\"\n }\n}",
|
"filter_description": "目前支持标签和创建时间过滤,需按照以下格式填写:\n{\n \"tags\": {\n \"$and\": [\"标签 1\",\"标签 2\"],\n \"$or\": [\"有 $and 标签时,and 生效,or 不生效\"]\n },\n \"createTime\": {\n \"$gte\": \"YYYY-MM-DD HH:mm 格式即可,集合的创建时间大于该时间\",\n \"$lte\": \"YYYY-MM-DD HH:mm 格式即可,集合的创建时间小于该时间,可和 $gte 共同使用\"\n }\n}",
|
||||||
|
"find_tip": "查找节点 ctrl f",
|
||||||
|
"find_tip_mac": "查找节点 ⌘ f",
|
||||||
"foldAll": "全部折叠",
|
"foldAll": "全部折叠",
|
||||||
"form_input_result": "用户完整输入结果",
|
"form_input_result": "用户完整输入结果",
|
||||||
"form_input_result_tip": "一个包含完整结果的对象",
|
"form_input_result_tip": "一个包含完整结果的对象",
|
||||||
@@ -123,18 +125,23 @@
|
|||||||
"max_tokens": "最大 Tokens",
|
"max_tokens": "最大 Tokens",
|
||||||
"mouse_priority": "鼠标优先\n- 左键按下后可拖动画布\n- 按住 shift 后左键可批量选择",
|
"mouse_priority": "鼠标优先\n- 左键按下后可拖动画布\n- 按住 shift 后左键可批量选择",
|
||||||
"new_context": "新的上下文",
|
"new_context": "新的上下文",
|
||||||
|
"next": "下一个",
|
||||||
|
"no_match_node": "无结果",
|
||||||
|
"no_node_found": "未搜索到节点",
|
||||||
"not_contains": "不包含",
|
"not_contains": "不包含",
|
||||||
"only_the_reference_type_is_supported": "仅支持引用类型",
|
"only_the_reference_type_is_supported": "仅支持引用类型",
|
||||||
"optional_value_type": "可选的数据类型",
|
"optional_value_type": "可选的数据类型",
|
||||||
"optional_value_type_tip": "可以指定 1 个或多个数据类型,用户在动态添加字段时,仅可选择配置的类型",
|
"optional_value_type_tip": "可以指定 1 个或多个数据类型,用户在动态添加字段时,仅可选择配置的类型",
|
||||||
"pan_priority": "触摸板优先\n- 单击批量选择\n- 双指移动画布",
|
"pan_priority": "触摸板优先\n- 单击批量选择\n- 双指移动画布",
|
||||||
"pass_returned_object_as_output_to_next_nodes": "将代码中 return 的对象作为输出,传递给后续的节点。变量名需要对应 return 的 key",
|
"pass_returned_object_as_output_to_next_nodes": "将代码中 return 的对象作为输出,传递给后续的节点。变量名需要对应 return 的 key",
|
||||||
|
"please_enter_node_name": "请输入节点名称",
|
||||||
"plugin.Instruction_Tip": "可以配置一段说明,以解释该插件的用途。每次使用插件前,会显示该段说明。支持标准 Markdown 语法。",
|
"plugin.Instruction_Tip": "可以配置一段说明,以解释该插件的用途。每次使用插件前,会显示该段说明。支持标准 Markdown 语法。",
|
||||||
"plugin.Instructions": "使用说明",
|
"plugin.Instructions": "使用说明",
|
||||||
"plugin.global_file_input": "文件链接(弃用)",
|
"plugin.global_file_input": "文件链接(弃用)",
|
||||||
"plugin_file_abandon_tip": "插件全局文件上传已弃用,请尽快调整。可以通过插件输入,添加图片类型输入来实现相关功能。",
|
"plugin_file_abandon_tip": "插件全局文件上传已弃用,请尽快调整。可以通过插件输入,添加图片类型输入来实现相关功能。",
|
||||||
"plugin_input": "插件输入",
|
"plugin_input": "插件输入",
|
||||||
"plugin_output_tool": "插件作为工具执行时,该字段是否作为工具响应结果",
|
"plugin_output_tool": "插件作为工具执行时,该字段是否作为工具响应结果",
|
||||||
|
"previous": "上一个",
|
||||||
"question_classification": "问题分类",
|
"question_classification": "问题分类",
|
||||||
"question_optimization": "问题优化",
|
"question_optimization": "问题优化",
|
||||||
"quote_content_placeholder": "可以自定义引用内容的结构,以更好的适配不同场景。可以使用一些变量来进行模板配置\n{{q}} - 主要内容\n{{a}} - 辅助数据\n{{source}} - 来源名\n{{sourceId}} - 来源ID\n{{index}} - 第 n 个引用",
|
"quote_content_placeholder": "可以自定义引用内容的结构,以更好的适配不同场景。可以使用一些变量来进行模板配置\n{{q}} - 主要内容\n{{a}} - 辅助数据\n{{source}} - 来源名\n{{sourceId}} - 来源ID\n{{index}} - 第 n 个引用",
|
||||||
|
|||||||
@@ -85,6 +85,7 @@
|
|||||||
"interval.per_hour": "每小時",
|
"interval.per_hour": "每小時",
|
||||||
"intro": "FastGPT 是一個基於大型語言模型的知識庫平臺,提供開箱即用的資料處理、向量檢索和視覺化 AI 工作流程編排等功能,讓您可以輕鬆開發和部署複雜的問答系統,而無需繁瑣的設定或設定。",
|
"intro": "FastGPT 是一個基於大型語言模型的知識庫平臺,提供開箱即用的資料處理、向量檢索和視覺化 AI 工作流程編排等功能,讓您可以輕鬆開發和部署複雜的問答系統,而無需繁瑣的設定或設定。",
|
||||||
"invalid_json_format": "JSON 格式錯誤",
|
"invalid_json_format": "JSON 格式錯誤",
|
||||||
|
"keep_the_latest": "保持最新版本",
|
||||||
"llm_not_support_vision": "這個模型不支援圖片辨識",
|
"llm_not_support_vision": "這個模型不支援圖片辨識",
|
||||||
"llm_use_vision": "圖片辨識",
|
"llm_use_vision": "圖片辨識",
|
||||||
"llm_use_vision_tip": "點選模型選擇後,可以看到模型是否支援圖片辨識以及控制是否啟用圖片辨識的功能。啟用圖片辨識後,模型會讀取檔案連結中的圖片內容,並且如果使用者問題少於 500 字,會自動解析使用者問題中的圖片。",
|
"llm_use_vision_tip": "點選模型選擇後,可以看到模型是否支援圖片辨識以及控制是否啟用圖片辨識的功能。啟用圖片辨識後,模型會讀取檔案連結中的圖片內容,並且如果使用者問題少於 500 字,會自動解析使用者問題中的圖片。",
|
||||||
|
|||||||
@@ -145,8 +145,8 @@
|
|||||||
"code_error.outlink_error.invalid_link": "分享連結無效",
|
"code_error.outlink_error.invalid_link": "分享連結無效",
|
||||||
"code_error.outlink_error.link_not_exist": "分享連結不存在",
|
"code_error.outlink_error.link_not_exist": "分享連結不存在",
|
||||||
"code_error.outlink_error.un_auth_user": "身份驗證失敗",
|
"code_error.outlink_error.un_auth_user": "身份驗證失敗",
|
||||||
"code_error.plugin_error.not_exist": "外掛程式不存在",
|
"code_error.plugin_error.not_exist": "工具不存在",
|
||||||
"code_error.plugin_error.un_auth": "無權操作此外掛程式",
|
"code_error.plugin_error.un_auth": "無權操作該工具",
|
||||||
"code_error.system_error.community_version_num_limit": "超出開源版數量限制,請升級商業版:https://tryfastgpt.ai",
|
"code_error.system_error.community_version_num_limit": "超出開源版數量限制,請升級商業版:https://tryfastgpt.ai",
|
||||||
"code_error.system_error.license_app_amount_limit": "超出系統最大應用數量",
|
"code_error.system_error.license_app_amount_limit": "超出系統最大應用數量",
|
||||||
"code_error.system_error.license_dataset_amount_limit": "超出系統最大知識庫數量",
|
"code_error.system_error.license_dataset_amount_limit": "超出系統最大知識庫數量",
|
||||||
@@ -554,7 +554,7 @@
|
|||||||
"core.dataset.training.Agent queue": "問答訓練排隊中",
|
"core.dataset.training.Agent queue": "問答訓練排隊中",
|
||||||
"core.dataset.training.Auto mode": "補充索引",
|
"core.dataset.training.Auto mode": "補充索引",
|
||||||
"core.dataset.training.Auto mode Tip": "透過子索引以及呼叫模型產生相關問題與摘要,來增加資料區塊的語意豐富度,更有利於檢索。需要消耗更多的儲存空間並增加 AI 呼叫次數。",
|
"core.dataset.training.Auto mode Tip": "透過子索引以及呼叫模型產生相關問題與摘要,來增加資料區塊的語意豐富度,更有利於檢索。需要消耗更多的儲存空間並增加 AI 呼叫次數。",
|
||||||
"core.dataset.training.Chunk mode": "直接分塊",
|
"core.dataset.training.Chunk mode": "分塊存儲",
|
||||||
"core.dataset.training.Full": "預計 20 分鐘以上",
|
"core.dataset.training.Full": "預計 20 分鐘以上",
|
||||||
"core.dataset.training.Leisure": "閒置",
|
"core.dataset.training.Leisure": "閒置",
|
||||||
"core.dataset.training.QA mode": "問答對提取",
|
"core.dataset.training.QA mode": "問答對提取",
|
||||||
|
|||||||
@@ -14,7 +14,12 @@
|
|||||||
"backup_dataset_tip": "可以將導出知識庫時,下載的 csv 文件重新導入。",
|
"backup_dataset_tip": "可以將導出知識庫時,下載的 csv 文件重新導入。",
|
||||||
"backup_mode": "備份導入",
|
"backup_mode": "備份導入",
|
||||||
"chunk_max_tokens": "分塊上限",
|
"chunk_max_tokens": "分塊上限",
|
||||||
|
"chunk_process_params": "分塊處理參數",
|
||||||
"chunk_size": "分塊大小",
|
"chunk_size": "分塊大小",
|
||||||
|
"chunk_trigger": "分塊條件",
|
||||||
|
"chunk_trigger_force_chunk": "強制分塊",
|
||||||
|
"chunk_trigger_max_size": "原文長度大於文件處理模型最大上下文70%",
|
||||||
|
"chunk_trigger_min_size": "原文長度大於",
|
||||||
"close_auto_sync": "確認關閉自動同步功能?",
|
"close_auto_sync": "確認關閉自動同步功能?",
|
||||||
"collection.Create update time": "建立/更新時間",
|
"collection.Create update time": "建立/更新時間",
|
||||||
"collection.Training type": "分段模式",
|
"collection.Training type": "分段模式",
|
||||||
@@ -28,6 +33,7 @@
|
|||||||
"collection_tags": "集合標籤",
|
"collection_tags": "集合標籤",
|
||||||
"common_dataset": "通用資料集",
|
"common_dataset": "通用資料集",
|
||||||
"common_dataset_desc": "通過導入文件、網頁鏈接或手動錄入形式構建知識庫",
|
"common_dataset_desc": "通過導入文件、網頁鏈接或手動錄入形式構建知識庫",
|
||||||
|
"condition": "條件",
|
||||||
"config_sync_schedule": "設定定時同步",
|
"config_sync_schedule": "設定定時同步",
|
||||||
"confirm_to_rebuild_embedding_tip": "確定要為資料集切換索引嗎?\n切換索引是一個重要的操作,需要對您資料集內所有資料重新建立索引,可能需要較長時間,請確保帳號內剩餘點數充足。\n\n此外,您還需要注意修改使用此資料集的應用程式,避免與其他索引模型資料集混用。",
|
"confirm_to_rebuild_embedding_tip": "確定要為資料集切換索引嗎?\n切換索引是一個重要的操作,需要對您資料集內所有資料重新建立索引,可能需要較長時間,請確保帳號內剩餘點數充足。\n\n此外,您還需要注意修改使用此資料集的應用程式,避免與其他索引模型資料集混用。",
|
||||||
"core.dataset.import.Adjust parameters": "調整參數",
|
"core.dataset.import.Adjust parameters": "調整參數",
|
||||||
@@ -99,6 +105,7 @@
|
|||||||
"is_open_schedule": "啟用定時同步",
|
"is_open_schedule": "啟用定時同步",
|
||||||
"keep_image": "保留圖片",
|
"keep_image": "保留圖片",
|
||||||
"loading": "加載中...",
|
"loading": "加載中...",
|
||||||
|
"max_chunk_size": "最大分塊大小",
|
||||||
"move.hint": "移動後,所選資料集/資料夾將繼承新資料夾的權限設定,原先的權限設定將失效。",
|
"move.hint": "移動後,所選資料集/資料夾將繼承新資料夾的權限設定,原先的權限設定將失效。",
|
||||||
"noChildren": "無子目錄",
|
"noChildren": "無子目錄",
|
||||||
"noSelectedFolder": "沒有選擇文件夾",
|
"noSelectedFolder": "沒有選擇文件夾",
|
||||||
@@ -106,8 +113,10 @@
|
|||||||
"noValidId": "沒有有效的 ID",
|
"noValidId": "沒有有效的 ID",
|
||||||
"open_auto_sync": "開啟定時同步後,系統將每天不定時嘗試同步集合,集合同步期間,會出現無法搜尋到該集合資料現象。",
|
"open_auto_sync": "開啟定時同步後,系統將每天不定時嘗試同步集合,集合同步期間,會出現無法搜尋到該集合資料現象。",
|
||||||
"other_dataset": "第三方知識庫",
|
"other_dataset": "第三方知識庫",
|
||||||
|
"paragraph_max_deep": "最大段落深度",
|
||||||
|
"paragraph_split": "按段落分塊",
|
||||||
|
"paragraph_split_tip": "優先按 Makdown 標題段落進行分塊,如果分塊過長,再按長度進行二次分塊",
|
||||||
"params_config": "設定",
|
"params_config": "設定",
|
||||||
"params_setting": "參數設定",
|
|
||||||
"pdf_enhance_parse": "PDF 增強解析",
|
"pdf_enhance_parse": "PDF 增強解析",
|
||||||
"pdf_enhance_parse_price": "{{price}}積分/頁",
|
"pdf_enhance_parse_price": "{{price}}積分/頁",
|
||||||
"pdf_enhance_parse_tips": "呼叫 PDF 識別模型進行解析,可以將其轉換成 Markdown 並保留文件中的圖片,同時也可以對掃描件進行識別,識別時間較長。",
|
"pdf_enhance_parse_tips": "呼叫 PDF 識別模型進行解析,可以將其轉換成 Markdown 並保留文件中的圖片,同時也可以對掃描件進行識別,識別時間較長。",
|
||||||
|
|||||||
@@ -63,6 +63,8 @@
|
|||||||
"field_required": "必填",
|
"field_required": "必填",
|
||||||
"field_used_as_tool_input": "作為工具呼叫參數",
|
"field_used_as_tool_input": "作為工具呼叫參數",
|
||||||
"filter_description": "目前支援標籤和建立時間篩選,需按照以下格式填寫:\n{\n \"tags\": {\n \"$and\": [\"標籤 1\",\"標籤 2\"],\n \"$or\": [\"當有 $and 標籤時,$and 才會生效,$or 不會生效\"]\n },\n \"createTime\": {\n \"$gte\": \"YYYY-MM-DD HH:mm 格式,資料集的建立時間大於這個時間\",\n \"$lte\": \"YYYY-MM-DD HH:mm 格式,資料集的建立時間小於這個時間,可以和 $gte 一起使用\"\n }\n}",
|
"filter_description": "目前支援標籤和建立時間篩選,需按照以下格式填寫:\n{\n \"tags\": {\n \"$and\": [\"標籤 1\",\"標籤 2\"],\n \"$or\": [\"當有 $and 標籤時,$and 才會生效,$or 不會生效\"]\n },\n \"createTime\": {\n \"$gte\": \"YYYY-MM-DD HH:mm 格式,資料集的建立時間大於這個時間\",\n \"$lte\": \"YYYY-MM-DD HH:mm 格式,資料集的建立時間小於這個時間,可以和 $gte 一起使用\"\n }\n}",
|
||||||
|
"find_tip": "查找節點 ctrl f",
|
||||||
|
"find_tip_mac": "查找節點 ⌘ f",
|
||||||
"foldAll": "全部折疊",
|
"foldAll": "全部折疊",
|
||||||
"form_input_result": "使用者完整輸入結果",
|
"form_input_result": "使用者完整輸入結果",
|
||||||
"form_input_result_tip": "一個包含完整結果的物件",
|
"form_input_result_tip": "一個包含完整結果的物件",
|
||||||
@@ -123,18 +125,23 @@
|
|||||||
"max_tokens": "最大 Token 數",
|
"max_tokens": "最大 Token 數",
|
||||||
"mouse_priority": "滑鼠優先\n- 按下左鍵拖曳畫布\n- 按住 Shift 鍵並點選左鍵可批次選取",
|
"mouse_priority": "滑鼠優先\n- 按下左鍵拖曳畫布\n- 按住 Shift 鍵並點選左鍵可批次選取",
|
||||||
"new_context": "新的脈絡",
|
"new_context": "新的脈絡",
|
||||||
|
"next": "下一個",
|
||||||
|
"no_match_node": "無結果",
|
||||||
|
"no_node_found": "未搜索到節點",
|
||||||
"not_contains": "不包含",
|
"not_contains": "不包含",
|
||||||
"only_the_reference_type_is_supported": "僅支援引用類型",
|
"only_the_reference_type_is_supported": "僅支援引用類型",
|
||||||
"optional_value_type": "可選的資料類型",
|
"optional_value_type": "可選的資料類型",
|
||||||
"optional_value_type_tip": "可以指定一或多個資料類型,使用者在動態新增欄位時,只能選擇已設定的類型",
|
"optional_value_type_tip": "可以指定一或多個資料類型,使用者在動態新增欄位時,只能選擇已設定的類型",
|
||||||
"pan_priority": "觸控板優先\n- 點選可批次選取\n- 使用兩指移動畫布",
|
"pan_priority": "觸控板優先\n- 點選可批次選取\n- 使用兩指移動畫布",
|
||||||
"pass_returned_object_as_output_to_next_nodes": "將程式碼中 return 的物件作為輸出,傳遞給後續的節點。變數名稱需要對應 return 的鍵值",
|
"pass_returned_object_as_output_to_next_nodes": "將程式碼中 return 的物件作為輸出,傳遞給後續的節點。變數名稱需要對應 return 的鍵值",
|
||||||
|
"please_enter_node_name": "請輸入節點名稱",
|
||||||
"plugin.Instruction_Tip": "您可以設定一段說明來解釋這個外掛程式的用途。每次使用外掛程式前,都會顯示這段說明。支援標準 Markdown 語法。",
|
"plugin.Instruction_Tip": "您可以設定一段說明來解釋這個外掛程式的用途。每次使用外掛程式前,都會顯示這段說明。支援標準 Markdown 語法。",
|
||||||
"plugin.Instructions": "使用說明",
|
"plugin.Instructions": "使用說明",
|
||||||
"plugin.global_file_input": "檔案連結(已淘汰)",
|
"plugin.global_file_input": "檔案連結(已淘汰)",
|
||||||
"plugin_file_abandon_tip": "外掛程式全域檔案上傳功能已淘汰,請儘速調整。您可以透過外掛程式輸入,新增圖片類型輸入來達成相關功能。",
|
"plugin_file_abandon_tip": "外掛程式全域檔案上傳功能已淘汰,請儘速調整。您可以透過外掛程式輸入,新增圖片類型輸入來達成相關功能。",
|
||||||
"plugin_input": "外掛程式輸入",
|
"plugin_input": "外掛程式輸入",
|
||||||
"plugin_output_tool": "外掛程式作為工具執行時,這個欄位是否作為工具的回應結果",
|
"plugin_output_tool": "外掛程式作為工具執行時,這個欄位是否作為工具的回應結果",
|
||||||
|
"previous": "上一個",
|
||||||
"question_classification": "問題分類",
|
"question_classification": "問題分類",
|
||||||
"question_optimization": "問題最佳化",
|
"question_optimization": "問題最佳化",
|
||||||
"quote_content_placeholder": "可以自訂引用內容的結構,以便更好地適應不同場景。可以使用一些變數來設定範本\n{{q}} - 主要內容\n{{a}} - 輔助資料\n{{source}} - 來源名稱\n{{sourceId}} - 來源 ID\n{{index}} - 第 n 個引用",
|
"quote_content_placeholder": "可以自訂引用內容的結構,以便更好地適應不同場景。可以使用一些變數來設定範本\n{{q}} - 主要內容\n{{a}} - 輔助資料\n{{source}} - 來源名稱\n{{sourceId}} - 來源 ID\n{{index}} - 第 n 個引用",
|
||||||
@@ -177,9 +184,9 @@
|
|||||||
"text_content_extraction": "文字內容擷取",
|
"text_content_extraction": "文字內容擷取",
|
||||||
"text_to_extract": "要擷取的文字",
|
"text_to_extract": "要擷取的文字",
|
||||||
"these_variables_will_be_input_parameters_for_code_execution": "這些變數會作為程式碼執行的輸入參數",
|
"these_variables_will_be_input_parameters_for_code_execution": "這些變數會作為程式碼執行的輸入參數",
|
||||||
"tool.tool_result": "工具運行結果",
|
|
||||||
"to_add_node": "添加節點",
|
"to_add_node": "添加節點",
|
||||||
"to_connect_node": "連接節點",
|
"to_connect_node": "連接節點",
|
||||||
|
"tool.tool_result": "工具運行結果",
|
||||||
"tool_call_termination": "工具呼叫終止",
|
"tool_call_termination": "工具呼叫終止",
|
||||||
"tool_custom_field": "自訂工具變數",
|
"tool_custom_field": "自訂工具變數",
|
||||||
"tool_field": "工具參數設定",
|
"tool_field": "工具參數設定",
|
||||||
|
|||||||
68
pnpm-lock.yaml
generated
68
pnpm-lock.yaml
generated
@@ -46,7 +46,7 @@ importers:
|
|||||||
version: 10.1.4(socks@2.8.4)
|
version: 10.1.4(socks@2.8.4)
|
||||||
next-i18next:
|
next-i18next:
|
||||||
specifier: 15.4.2
|
specifier: 15.4.2
|
||||||
version: 15.4.2(i18next@23.16.8)(next@14.2.28(@babel/core@7.26.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
version: 15.4.2(i18next@23.16.8)(next@14.2.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||||
prettier:
|
prettier:
|
||||||
specifier: 3.2.4
|
specifier: 3.2.4
|
||||||
version: 3.2.4
|
version: 3.2.4
|
||||||
@@ -343,7 +343,7 @@ importers:
|
|||||||
version: 2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
version: 2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||||
'@chakra-ui/next-js':
|
'@chakra-ui/next-js':
|
||||||
specifier: 2.4.2
|
specifier: 2.4.2
|
||||||
version: 2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.28(@babel/core@7.26.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)
|
version: 2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)
|
||||||
'@chakra-ui/react':
|
'@chakra-ui/react':
|
||||||
specifier: 2.10.7
|
specifier: 2.10.7
|
||||||
version: 2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
@@ -406,7 +406,7 @@ importers:
|
|||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
next-i18next:
|
next-i18next:
|
||||||
specifier: 15.4.2
|
specifier: 15.4.2
|
||||||
version: 15.4.2(i18next@23.16.8)(next@14.2.28(@babel/core@7.26.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
version: 15.4.2(i18next@23.16.8)(next@14.2.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||||
papaparse:
|
papaparse:
|
||||||
specifier: ^5.4.1
|
specifier: ^5.4.1
|
||||||
version: 5.4.1
|
version: 5.4.1
|
||||||
@@ -467,7 +467,7 @@ importers:
|
|||||||
version: 2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
version: 2.1.1(@chakra-ui/system@2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||||
'@chakra-ui/next-js':
|
'@chakra-ui/next-js':
|
||||||
specifier: 2.4.2
|
specifier: 2.4.2
|
||||||
version: 2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.28(@babel/core@7.26.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)
|
version: 2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)
|
||||||
'@chakra-ui/react':
|
'@chakra-ui/react':
|
||||||
specifier: 2.10.7
|
specifier: 2.10.7
|
||||||
version: 2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
@@ -569,7 +569,7 @@ importers:
|
|||||||
version: 14.2.28(@babel/core@7.26.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)
|
version: 14.2.28(@babel/core@7.26.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1)
|
||||||
next-i18next:
|
next-i18next:
|
||||||
specifier: 15.4.2
|
specifier: 15.4.2
|
||||||
version: 15.4.2(i18next@23.16.8)(next@14.2.28(@babel/core@7.26.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
version: 15.4.2(i18next@23.16.8)(next@14.2.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||||
nprogress:
|
nprogress:
|
||||||
specifier: ^0.2.0
|
specifier: ^0.2.0
|
||||||
version: 0.2.0
|
version: 0.2.0
|
||||||
@@ -612,6 +612,9 @@ importers:
|
|||||||
rehype-katex:
|
rehype-katex:
|
||||||
specifier: ^7.0.0
|
specifier: ^7.0.0
|
||||||
version: 7.0.1
|
version: 7.0.1
|
||||||
|
rehype-raw:
|
||||||
|
specifier: ^7.0.0
|
||||||
|
version: 7.0.0
|
||||||
remark-breaks:
|
remark-breaks:
|
||||||
specifier: ^4.0.0
|
specifier: ^4.0.0
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
@@ -5879,9 +5882,15 @@ packages:
|
|||||||
hast-util-parse-selector@4.0.0:
|
hast-util-parse-selector@4.0.0:
|
||||||
resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
|
resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
|
||||||
|
|
||||||
|
hast-util-raw@9.1.0:
|
||||||
|
resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==}
|
||||||
|
|
||||||
hast-util-to-jsx-runtime@2.3.6:
|
hast-util-to-jsx-runtime@2.3.6:
|
||||||
resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
|
resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
|
||||||
|
|
||||||
|
hast-util-to-parse5@8.0.0:
|
||||||
|
resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==}
|
||||||
|
|
||||||
hast-util-to-text@4.0.2:
|
hast-util-to-text@4.0.2:
|
||||||
resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==}
|
resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==}
|
||||||
|
|
||||||
@@ -5919,6 +5928,9 @@ packages:
|
|||||||
html-url-attributes@3.0.1:
|
html-url-attributes@3.0.1:
|
||||||
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
|
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
|
||||||
|
|
||||||
|
html-void-elements@3.0.0:
|
||||||
|
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
|
||||||
|
|
||||||
htmlparser2@8.0.2:
|
htmlparser2@8.0.2:
|
||||||
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
|
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
|
||||||
|
|
||||||
@@ -8026,6 +8038,9 @@ packages:
|
|||||||
property-information@5.6.0:
|
property-information@5.6.0:
|
||||||
resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==}
|
resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==}
|
||||||
|
|
||||||
|
property-information@6.5.0:
|
||||||
|
resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
|
||||||
|
|
||||||
property-information@7.0.0:
|
property-information@7.0.0:
|
||||||
resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==}
|
resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==}
|
||||||
|
|
||||||
@@ -8361,6 +8376,9 @@ packages:
|
|||||||
rehype-katex@7.0.1:
|
rehype-katex@7.0.1:
|
||||||
resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==}
|
resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==}
|
||||||
|
|
||||||
|
rehype-raw@7.0.0:
|
||||||
|
resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==}
|
||||||
|
|
||||||
remark-breaks@4.0.0:
|
remark-breaks@4.0.0:
|
||||||
resolution: {integrity: sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==}
|
resolution: {integrity: sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==}
|
||||||
|
|
||||||
@@ -10969,7 +10987,7 @@ snapshots:
|
|||||||
'@chakra-ui/system': 2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1)
|
'@chakra-ui/system': 2.6.1(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(react@18.3.1)
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
|
|
||||||
'@chakra-ui/next-js@2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.28(@babel/core@7.26.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)':
|
'@chakra-ui/next-js@2.4.2(@chakra-ui/react@2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(next@14.2.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@chakra-ui/react': 2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@chakra-ui/react': 2.10.7(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@emotion/styled@11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(framer-motion@9.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@emotion/cache': 11.14.0
|
'@emotion/cache': 11.14.0
|
||||||
@@ -16108,6 +16126,22 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/hast': 3.0.4
|
'@types/hast': 3.0.4
|
||||||
|
|
||||||
|
hast-util-raw@9.1.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
'@types/unist': 3.0.3
|
||||||
|
'@ungap/structured-clone': 1.3.0
|
||||||
|
hast-util-from-parse5: 8.0.3
|
||||||
|
hast-util-to-parse5: 8.0.0
|
||||||
|
html-void-elements: 3.0.0
|
||||||
|
mdast-util-to-hast: 13.2.0
|
||||||
|
parse5: 7.2.1
|
||||||
|
unist-util-position: 5.0.0
|
||||||
|
unist-util-visit: 5.0.0
|
||||||
|
vfile: 6.0.3
|
||||||
|
web-namespaces: 2.0.1
|
||||||
|
zwitch: 2.0.4
|
||||||
|
|
||||||
hast-util-to-jsx-runtime@2.3.6:
|
hast-util-to-jsx-runtime@2.3.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.6
|
'@types/estree': 1.0.6
|
||||||
@@ -16128,6 +16162,16 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
hast-util-to-parse5@8.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
comma-separated-tokens: 2.0.3
|
||||||
|
devlop: 1.1.0
|
||||||
|
property-information: 6.5.0
|
||||||
|
space-separated-tokens: 2.0.2
|
||||||
|
web-namespaces: 2.0.1
|
||||||
|
zwitch: 2.0.4
|
||||||
|
|
||||||
hast-util-to-text@4.0.2:
|
hast-util-to-text@4.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/hast': 3.0.4
|
'@types/hast': 3.0.4
|
||||||
@@ -16175,6 +16219,8 @@ snapshots:
|
|||||||
|
|
||||||
html-url-attributes@3.0.1: {}
|
html-url-attributes@3.0.1: {}
|
||||||
|
|
||||||
|
html-void-elements@3.0.0: {}
|
||||||
|
|
||||||
htmlparser2@8.0.2:
|
htmlparser2@8.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
domelementtype: 2.3.0
|
domelementtype: 2.3.0
|
||||||
@@ -18238,7 +18284,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
next-i18next@15.4.2(i18next@23.16.8)(next@14.2.28(@babel/core@7.26.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
|
next-i18next@15.4.2(i18next@23.16.8)(next@14.2.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.85.1))(react-i18next@14.1.2(i18next@23.16.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.26.10
|
'@babel/runtime': 7.26.10
|
||||||
'@types/hoist-non-react-statics': 3.3.6
|
'@types/hoist-non-react-statics': 3.3.6
|
||||||
@@ -18873,6 +18919,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
xtend: 4.0.2
|
xtend: 4.0.2
|
||||||
|
|
||||||
|
property-information@6.5.0: {}
|
||||||
|
|
||||||
property-information@7.0.0: {}
|
property-information@7.0.0: {}
|
||||||
|
|
||||||
proto-list@1.2.4: {}
|
proto-list@1.2.4: {}
|
||||||
@@ -19292,6 +19340,12 @@ snapshots:
|
|||||||
unist-util-visit-parents: 6.0.1
|
unist-util-visit-parents: 6.0.1
|
||||||
vfile: 6.0.3
|
vfile: 6.0.3
|
||||||
|
|
||||||
|
rehype-raw@7.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
hast-util-raw: 9.1.0
|
||||||
|
vfile: 6.0.3
|
||||||
|
|
||||||
remark-breaks@4.0.0:
|
remark-breaks@4.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/mdast': 4.0.4
|
'@types/mdast': 4.0.4
|
||||||
|
|||||||
@@ -11,6 +11,53 @@ const nextConfig = {
|
|||||||
output: 'standalone',
|
output: 'standalone',
|
||||||
reactStrictMode: isDev ? false : true,
|
reactStrictMode: isDev ? false : true,
|
||||||
compress: true,
|
compress: true,
|
||||||
|
|
||||||
|
headers: async () => {
|
||||||
|
const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
|
||||||
|
const csp = `'nonce-${nonce}'`;
|
||||||
|
const scheme_source = 'data: mediastream: blob: filesystem:';
|
||||||
|
const NECESSARY_DOMAINS = [
|
||||||
|
'*.sentry.io',
|
||||||
|
'http://localhost:*',
|
||||||
|
'http://127.0.0.1:*',
|
||||||
|
'https://analytics.google.com',
|
||||||
|
'googletagmanager.com',
|
||||||
|
'*.googletagmanager.com',
|
||||||
|
'https://www.google-analytics.com',
|
||||||
|
'https://api.github.com'
|
||||||
|
].join(' ');
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: '/chat/(.*)',
|
||||||
|
headers: [
|
||||||
|
{ key: 'X-Frame-Options', value: 'DENY' },
|
||||||
|
{ key: 'X-Content-Type-Options', value: 'nosniff' },
|
||||||
|
{ key: 'X-XSS-Protection', value: '1; mode=block' },
|
||||||
|
{ key: 'Referrer-Policy', value: 'no-referrer' },
|
||||||
|
{
|
||||||
|
key: 'Content-Security-Policy',
|
||||||
|
value: [
|
||||||
|
`default-src 'self' ${scheme_source} ${NECESSARY_DOMAINS} ${csp}`,
|
||||||
|
`script-src 'self' 'unsafe-inline' 'unsafe-eval' ${csp} ${NECESSARY_DOMAINS}`,
|
||||||
|
`style-src 'self' 'unsafe-inline' ${csp} ${NECESSARY_DOMAINS}`,
|
||||||
|
`media-src 'self' http: ${scheme_source} ${NECESSARY_DOMAINS} ${csp}`,
|
||||||
|
`worker-src 'self' ${csp} ${NECESSARY_DOMAINS} ${scheme_source}`,
|
||||||
|
`img-src * data: blob:`,
|
||||||
|
`font-src 'self'`,
|
||||||
|
`connect-src 'self' wss: https: ${scheme_source} ${NECESSARY_DOMAINS} ${csp}`,
|
||||||
|
"object-src 'none'",
|
||||||
|
"form-action 'self'",
|
||||||
|
"base-uri 'self'",
|
||||||
|
"frame-src 'self' 'allow-scripts'",
|
||||||
|
'sandbox allow-scripts allow-same-origin allow-popups allow-forms',
|
||||||
|
'upgrade-insecure-requests'
|
||||||
|
].join('; ')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
},
|
||||||
webpack(config, { isServer, nextRuntime }) {
|
webpack(config, { isServer, nextRuntime }) {
|
||||||
Object.assign(config.resolve.alias, {
|
Object.assign(config.resolve.alias, {
|
||||||
'@mongodb-js/zstd': false,
|
'@mongodb-js/zstd': false,
|
||||||
@@ -85,7 +132,7 @@ const nextConfig = {
|
|||||||
'pg',
|
'pg',
|
||||||
'bullmq',
|
'bullmq',
|
||||||
'@zilliz/milvus2-sdk-node',
|
'@zilliz/milvus2-sdk-node',
|
||||||
"tiktoken",
|
'tiktoken'
|
||||||
],
|
],
|
||||||
outputFileTracingRoot: path.join(__dirname, '../../'),
|
outputFileTracingRoot: path.join(__dirname, '../../'),
|
||||||
instrumentationHook: true
|
instrumentationHook: true
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
"recharts": "^2.15.0",
|
"recharts": "^2.15.0",
|
||||||
"rehype-external-links": "^3.0.0",
|
"rehype-external-links": "^3.0.0",
|
||||||
"rehype-katex": "^7.0.0",
|
"rehype-katex": "^7.0.0",
|
||||||
|
"rehype-raw": "^7.0.0",
|
||||||
"remark-breaks": "^4.0.0",
|
"remark-breaks": "^4.0.0",
|
||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
|
|||||||
35
projects/app/src/components/Markdown/errorBoundry.tsx
Normal file
35
projects/app/src/components/Markdown/errorBoundry.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface ErrorBoundaryProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
fallback?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ErrorBoundaryState {
|
||||||
|
hasError: boolean;
|
||||||
|
error: Error | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||||
|
constructor(props: ErrorBoundaryProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = { hasError: false, error: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromError(error: Error) {
|
||||||
|
return { hasError: true, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||||
|
console.error('ErrorBoundary caught an error:', error, errorInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
return this.props.fallback || <div>Something went wrong while rendering Markdown.</div>;
|
||||||
|
}
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorBoundary;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { memo, useCallback, useMemo } from 'react';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import 'katex/dist/katex.min.css';
|
import 'katex/dist/katex.min.css';
|
||||||
import RemarkMath from 'remark-math'; // Math syntax
|
import RemarkMath from 'remark-math'; // Math syntax
|
||||||
@@ -6,15 +6,15 @@ import RemarkBreaks from 'remark-breaks'; // Line break
|
|||||||
import RehypeKatex from 'rehype-katex'; // Math render
|
import RehypeKatex from 'rehype-katex'; // Math render
|
||||||
import RemarkGfm from 'remark-gfm'; // Special markdown syntax
|
import RemarkGfm from 'remark-gfm'; // Special markdown syntax
|
||||||
import RehypeExternalLinks from 'rehype-external-links';
|
import RehypeExternalLinks from 'rehype-external-links';
|
||||||
|
import RehypeRaw from 'rehype-raw';
|
||||||
import styles from './index.module.scss';
|
import styles from './index.module.scss';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
import { Box } from '@chakra-ui/react';
|
import { Box } from '@chakra-ui/react';
|
||||||
import { CodeClassNameEnum, mdTextFormat } from './utils';
|
import { CodeClassNameEnum, mdTextFormat } from './utils';
|
||||||
|
import ErrorBoundary from './errorBoundry';
|
||||||
|
import SVGRenderer from './markdowSVG';
|
||||||
import { useCreation } from 'ahooks';
|
import { useCreation } from 'ahooks';
|
||||||
import type { AProps } from './A';
|
import type { AProps } from './A';
|
||||||
|
|
||||||
const CodeLight = dynamic(() => import('./codeBlock/CodeLight'), { ssr: false });
|
const CodeLight = dynamic(() => import('./codeBlock/CodeLight'), { ssr: false });
|
||||||
const MermaidCodeBlock = dynamic(() => import('./img/MermaidCodeBlock'), { ssr: false });
|
const MermaidCodeBlock = dynamic(() => import('./img/MermaidCodeBlock'), { ssr: false });
|
||||||
const MdImage = dynamic(() => import('./img/Image'), { ssr: false });
|
const MdImage = dynamic(() => import('./img/Image'), { ssr: false });
|
||||||
@@ -26,7 +26,40 @@ const AudioBlock = dynamic(() => import('./codeBlock/Audio'), { ssr: false });
|
|||||||
|
|
||||||
const ChatGuide = dynamic(() => import('./chat/Guide'), { ssr: false });
|
const ChatGuide = dynamic(() => import('./chat/Guide'), { ssr: false });
|
||||||
const QuestionGuide = dynamic(() => import('./chat/QuestionGuide'), { ssr: false });
|
const QuestionGuide = dynamic(() => import('./chat/QuestionGuide'), { ssr: false });
|
||||||
const A = dynamic(() => import('./A'), { ssr: false });
|
|
||||||
|
function isSafeHref(href: string): boolean {
|
||||||
|
if (!href) return false;
|
||||||
|
// allow http(s), mailto, tel, relative paths, #, data:image/audio/video
|
||||||
|
return /^(https?:|mailto:|tel:|\/|#|data:(?:image|audio|video))/i.test(href.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
const SafeA = (props: any) => {
|
||||||
|
const href = props.href || '';
|
||||||
|
const safeHref = isSafeHref(href) ? href : '#';
|
||||||
|
|
||||||
|
const ALLOWED_A_ATTRS = new Set([
|
||||||
|
'href',
|
||||||
|
'target',
|
||||||
|
'rel',
|
||||||
|
'className',
|
||||||
|
'children',
|
||||||
|
'style',
|
||||||
|
'title'
|
||||||
|
]);
|
||||||
|
const safeProps = filterSafeProps(props, ALLOWED_A_ATTRS);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
{...safeProps}
|
||||||
|
href={safeHref}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="cursor-pointer underline !decoration-primary-700 decoration-dashed"
|
||||||
|
>
|
||||||
|
{props.children || 'Download'}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
source?: string;
|
source?: string;
|
||||||
@@ -36,11 +69,9 @@ type Props = {
|
|||||||
} & AProps;
|
} & AProps;
|
||||||
const Markdown = (props: Props) => {
|
const Markdown = (props: Props) => {
|
||||||
const source = props.source || '';
|
const source = props.source || '';
|
||||||
|
|
||||||
if (source.length < 200000) {
|
if (source.length < 200000) {
|
||||||
return <MarkdownRender {...props} />;
|
return <MarkdownRender {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Box whiteSpace={'pre-wrap'}>{source}</Box>;
|
return <Box whiteSpace={'pre-wrap'}>{source}</Box>;
|
||||||
};
|
};
|
||||||
const MarkdownRender = ({
|
const MarkdownRender = ({
|
||||||
@@ -48,7 +79,6 @@ const MarkdownRender = ({
|
|||||||
showAnimation,
|
showAnimation,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
forbidZhFormat,
|
forbidZhFormat,
|
||||||
|
|
||||||
chatAuthData,
|
chatAuthData,
|
||||||
onOpenCiteModal
|
onOpenCiteModal
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
@@ -57,54 +87,116 @@ const MarkdownRender = ({
|
|||||||
img: Image,
|
img: Image,
|
||||||
pre: RewritePre,
|
pre: RewritePre,
|
||||||
code: Code,
|
code: Code,
|
||||||
|
svg: SVGRenderer,
|
||||||
|
script: ScriptBlock,
|
||||||
a: (props: any) => (
|
a: (props: any) => (
|
||||||
<A
|
<SafeA
|
||||||
{...props}
|
{...props}
|
||||||
showAnimation={showAnimation}
|
|
||||||
chatAuthData={chatAuthData}
|
chatAuthData={chatAuthData}
|
||||||
onOpenCiteModal={onOpenCiteModal}
|
onOpenCiteModal={onOpenCiteModal}
|
||||||
|
showAnimation={showAnimation}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}, [chatAuthData, onOpenCiteModal, showAnimation]);
|
}, [chatAuthData, onOpenCiteModal, showAnimation]);
|
||||||
|
|
||||||
const formatSource = useMemo(() => {
|
const formatSource = useMemo(() => {
|
||||||
if (showAnimation || forbidZhFormat) return source;
|
if (showAnimation || forbidZhFormat) return source;
|
||||||
return mdTextFormat(source);
|
return mdTextFormat(source);
|
||||||
}, [forbidZhFormat, showAnimation, source]);
|
}, [forbidZhFormat, showAnimation, source]);
|
||||||
|
|
||||||
const urlTransform = useCallback((val: string) => {
|
const urlTransform = useCallback((val: string) => val, []);
|
||||||
return val;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box position={'relative'}>
|
<Box position={'relative'}>
|
||||||
<ReactMarkdown
|
<ErrorBoundary>
|
||||||
className={`markdown ${styles.markdown}
|
<ReactMarkdown
|
||||||
${showAnimation ? `${formatSource ? styles.waitingAnimation : styles.animation}` : ''}
|
className={`markdown ${styles.markdown} ${
|
||||||
`}
|
showAnimation ? `${formatSource ? styles.waitingAnimation : styles.animation}` : ''
|
||||||
remarkPlugins={[RemarkMath, [RemarkGfm, { singleTilde: false }], RemarkBreaks]}
|
}`}
|
||||||
rehypePlugins={[RehypeKatex, [RehypeExternalLinks, { target: '_blank' }]]}
|
remarkPlugins={[RemarkMath, [RemarkGfm, { singleTilde: false }], RemarkBreaks]}
|
||||||
components={components}
|
rehypePlugins={[
|
||||||
urlTransform={urlTransform}
|
RehypeKatex,
|
||||||
>
|
[RehypeExternalLinks, { target: '_blank', rel: ['noopener', 'noreferrer'] }],
|
||||||
{formatSource}
|
RehypeRaw as any,
|
||||||
</ReactMarkdown>
|
() => {
|
||||||
|
return (tree) => {
|
||||||
|
const iterate = (node: any) => {
|
||||||
|
if (node.type === 'element') {
|
||||||
|
// delete ref
|
||||||
|
if (node.properties?.ref) delete node.properties.ref;
|
||||||
|
|
||||||
|
// handle invalid tag name
|
||||||
|
if (!/^[a-z][a-z0-9]*$/i.test(node.tagName)) {
|
||||||
|
node.type = 'text';
|
||||||
|
node.value = `<${node.tagName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle properties, filter events
|
||||||
|
if (node.properties) {
|
||||||
|
Object.keys(node.properties).forEach((key) => {
|
||||||
|
const keyLower = key.toLowerCase();
|
||||||
|
// if event property (on开头)
|
||||||
|
if (keyLower.startsWith('on')) {
|
||||||
|
const value = node.properties[key];
|
||||||
|
// if event value is not a function or contains suspicious content, delete the event
|
||||||
|
if (
|
||||||
|
typeof value === 'string' || // delete event handler in string format
|
||||||
|
value === null ||
|
||||||
|
value === undefined ||
|
||||||
|
(typeof value === 'string' &&
|
||||||
|
(value.includes('javascript:') ||
|
||||||
|
value.includes('alert') ||
|
||||||
|
value.includes('eval') ||
|
||||||
|
value.includes('Function') ||
|
||||||
|
/[\(\)\[\]\{\}]/.test(value))) // flag for executable code containing parentheses, etc.
|
||||||
|
) {
|
||||||
|
delete node.properties[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursive handle child nodes
|
||||||
|
if (node.children) node.children.forEach(iterate);
|
||||||
|
};
|
||||||
|
tree.children.forEach(iterate);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
disallowedElements={[
|
||||||
|
'iframe',
|
||||||
|
'head',
|
||||||
|
'html',
|
||||||
|
'meta',
|
||||||
|
'link',
|
||||||
|
'style',
|
||||||
|
'body',
|
||||||
|
'embed',
|
||||||
|
'object',
|
||||||
|
'param',
|
||||||
|
'applet',
|
||||||
|
'area',
|
||||||
|
'map',
|
||||||
|
'isindex'
|
||||||
|
]}
|
||||||
|
components={components}
|
||||||
|
urlTransform={urlTransform}
|
||||||
|
>
|
||||||
|
{formatSource}
|
||||||
|
</ReactMarkdown>
|
||||||
|
</ErrorBoundary>
|
||||||
{isDisabled && <Box position={'absolute'} top={0} right={0} left={0} bottom={0} />}
|
{isDisabled && <Box position={'absolute'} top={0} right={0} left={0} bottom={0} />}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(Markdown);
|
export default React.memo(Markdown);
|
||||||
|
|
||||||
/* Custom dom */
|
/* Custom dom */
|
||||||
function Code(e: any) {
|
function Code(e: any) {
|
||||||
const { className, codeBlock, children } = e;
|
const { className, codeBlock, children } = e;
|
||||||
const match = /language-(\w+)/.exec(className || '');
|
const match = /language-(\w+)/.exec(className || '');
|
||||||
const codeType = match?.[1]?.toLowerCase();
|
const codeType = match?.[1]?.toLowerCase();
|
||||||
|
|
||||||
const strChildren = String(children);
|
const strChildren = String(children);
|
||||||
|
|
||||||
const Component = useMemo(() => {
|
const Component = useMemo(() => {
|
||||||
if (codeType === CodeClassNameEnum.mermaid) {
|
if (codeType === CodeClassNameEnum.mermaid) {
|
||||||
return <MermaidCodeBlock code={strChildren} />;
|
return <MermaidCodeBlock code={strChildren} />;
|
||||||
@@ -134,22 +226,61 @@ function Code(e: any) {
|
|||||||
if (codeType === CodeClassNameEnum.audio) {
|
if (codeType === CodeClassNameEnum.audio) {
|
||||||
return <AudioBlock code={strChildren} />;
|
return <AudioBlock code={strChildren} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CodeLight className={className} codeBlock={codeBlock} match={match}>
|
<CodeLight className={className} codeBlock={codeBlock} match={match}>
|
||||||
{children}
|
{children}
|
||||||
</CodeLight>
|
</CodeLight>
|
||||||
);
|
);
|
||||||
}, [codeType, className, codeBlock, match, children, strChildren]);
|
}, [codeType, className, codeBlock, match, children, strChildren]);
|
||||||
|
|
||||||
return Component;
|
return Component;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Image({ src }: { src?: string }) {
|
function sanitizeImageSrc(src?: string): string | undefined {
|
||||||
return <MdImage src={src} />;
|
if (!src) return undefined;
|
||||||
|
// remove leading and trailing spaces
|
||||||
|
const trimmed = src.trim();
|
||||||
|
// only allow http/https/data/blob protocols
|
||||||
|
if (/^(https?:|data:|blob:)/i.test(trimmed)) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
// allow relative paths (not starting with javascript: or vbscript:)
|
||||||
|
if (
|
||||||
|
!/^(\w+:)/.test(trimmed) &&
|
||||||
|
!trimmed.startsWith('javascript:') &&
|
||||||
|
!trimmed.startsWith('vbscript:')
|
||||||
|
) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function RewritePre({ children }: any) {
|
const ALLOWED_IMG_ATTRS = new Set([
|
||||||
|
'alt',
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
'className',
|
||||||
|
'style',
|
||||||
|
'title',
|
||||||
|
'loading',
|
||||||
|
'decoding',
|
||||||
|
'crossOrigin',
|
||||||
|
'referrerPolicy'
|
||||||
|
]);
|
||||||
|
|
||||||
|
function Image({ src, ...rest }: { src?: string; [key: string]: any }) {
|
||||||
|
const safeSrc = sanitizeImageSrc(src);
|
||||||
|
if (!safeSrc) {
|
||||||
|
console.warn(`Blocked unsafe image src: ${src}`);
|
||||||
|
}
|
||||||
|
// only allow whitelist attributes, and remove all on* events
|
||||||
|
const safeProps = filterSafeProps(rest, ALLOWED_IMG_ATTRS);
|
||||||
|
return <MdImage src={safeSrc} {...safeProps} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RewritePre({ children, ...rest }: any) {
|
||||||
|
// only allow className, style, etc. safe attributes
|
||||||
|
const ALLOWED_PRE_ATTRS = new Set(['className', 'style']);
|
||||||
|
const safeProps = filterSafeProps(rest, ALLOWED_PRE_ATTRS);
|
||||||
const modifiedChildren = React.Children.map(children, (child) => {
|
const modifiedChildren = React.Children.map(children, (child) => {
|
||||||
if (React.isValidElement(child)) {
|
if (React.isValidElement(child)) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -157,6 +288,146 @@ function RewritePre({ children }: any) {
|
|||||||
}
|
}
|
||||||
return child;
|
return child;
|
||||||
});
|
});
|
||||||
|
return <pre {...safeProps}>{modifiedChildren}</pre>;
|
||||||
return <>{modifiedChildren}</>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* general safe attribute filter
|
||||||
|
* @param props input props object
|
||||||
|
* @param allowedAttrs allowed attribute name Set
|
||||||
|
* @param eventTypeCheck whether to check event type (e.g. onClick must be a function)
|
||||||
|
*/
|
||||||
|
export function filterSafeProps(
|
||||||
|
props: Record<string, any>,
|
||||||
|
allowedAttrs: Set<string>,
|
||||||
|
eventTypeCheck: boolean = true
|
||||||
|
) {
|
||||||
|
// dangerous protocols
|
||||||
|
const DANGEROUS_PROTOCOLS =
|
||||||
|
/^(?:\s| | )*(?:javascript|vbscript|data(?!:(?:image|audio|video)))/i;
|
||||||
|
|
||||||
|
// dangerous event properties (including various possible ways)
|
||||||
|
const DANGEROUS_EVENTS =
|
||||||
|
/^(?:\s| | )*(?:on|formaction|data-|\[\[|\{\{|xlink:|href|src|action)/i;
|
||||||
|
|
||||||
|
// complete decode function
|
||||||
|
function fullDecode(input: string): string {
|
||||||
|
if (!input) return '';
|
||||||
|
|
||||||
|
let result = input;
|
||||||
|
let lastResult = '';
|
||||||
|
|
||||||
|
// continue decoding until no more decoding can be done
|
||||||
|
while (result !== lastResult) {
|
||||||
|
lastResult = result;
|
||||||
|
try {
|
||||||
|
// HTML entity decode
|
||||||
|
result = result.replace(/&(#?[\w\d]+);/g, (_, entity) => {
|
||||||
|
try {
|
||||||
|
const txt = document.createElement('textarea');
|
||||||
|
txt.innerHTML = `&${entity};`;
|
||||||
|
return txt.value;
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Unicode decode (\u0061 format)
|
||||||
|
result = result.replace(/(?:\\|%5C|%5c)u([0-9a-f]{4})/gi, (_, hex) =>
|
||||||
|
String.fromCharCode(parseInt(hex, 16))
|
||||||
|
);
|
||||||
|
|
||||||
|
// URL encode decode
|
||||||
|
result = result.replace(/%([0-9a-f]{2})/gi, (_, hex) =>
|
||||||
|
String.fromCharCode(parseInt(hex, 16))
|
||||||
|
);
|
||||||
|
|
||||||
|
// octal decode
|
||||||
|
result = result.replace(/\\([0-7]{3})/gi, (_, oct) =>
|
||||||
|
String.fromCharCode(parseInt(oct, 8))
|
||||||
|
);
|
||||||
|
|
||||||
|
// hexadecimal decode (\x61 format)
|
||||||
|
result = result.replace(/(?:\\|%5C|%5c)x([0-9a-f]{2})/gi, (_, hex) =>
|
||||||
|
String.fromCharCode(parseInt(hex, 16))
|
||||||
|
);
|
||||||
|
|
||||||
|
// handle whitespace and comments
|
||||||
|
result = result.replace(/(?:\s|\/\*.*?\*\/|<!--.*?-->)+/g, '');
|
||||||
|
} catch {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if it contains dangerous content
|
||||||
|
function containsDangerousContent(value: string): boolean {
|
||||||
|
const decoded = fullDecode(value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
// check dangerous protocol
|
||||||
|
DANGEROUS_PROTOCOLS.test(decoded) ||
|
||||||
|
// check dangerous event
|
||||||
|
DANGEROUS_EVENTS.test(decoded) ||
|
||||||
|
// check inline event
|
||||||
|
/on\w+\s*=/.test(decoded) ||
|
||||||
|
// check javascript: link
|
||||||
|
/javascript\s*:/.test(decoded) ||
|
||||||
|
// check other possible injections
|
||||||
|
/<\w+/i.test(decoded) ||
|
||||||
|
/\(\s*\)/i.test(decoded) ||
|
||||||
|
/\[\s*\]/i.test(decoded) ||
|
||||||
|
/\{\s*\}/i.test(decoded)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(props).filter(([key, value]) => {
|
||||||
|
// 1. decode and check property name
|
||||||
|
const decodedKey = fullDecode(key);
|
||||||
|
|
||||||
|
// 2. intercept all event related properties
|
||||||
|
if (DANGEROUS_EVENTS.test(decodedKey)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. all properties not in the whitelist are rejected
|
||||||
|
if (!allowedAttrs.has(key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. check property value
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
if (containsDangerousContent(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (typeof value === 'object' && value !== null) {
|
||||||
|
// only allow simple style objects
|
||||||
|
if (key !== 'style') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// check the value of the style object
|
||||||
|
for (const styleKey in value) {
|
||||||
|
if (containsDangerousContent(String(value[styleKey]))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (typeof value === 'function') {
|
||||||
|
// only allow specific function properties (e.g. onClick)
|
||||||
|
if (!eventTypeCheck || decodedKey !== 'onclick') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ScriptBlock = memo(({ node }: any) => {
|
||||||
|
const scriptContent = node.children[0]?.value || '';
|
||||||
|
return `<script>${scriptContent}</script>`;
|
||||||
|
});
|
||||||
|
ScriptBlock.displayName = 'ScriptBlock';
|
||||||
|
|||||||
108
projects/app/src/components/Markdown/markdowSVG.tsx
Normal file
108
projects/app/src/components/Markdown/markdowSVG.tsx
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ErrorBoundary from './errorBoundry';
|
||||||
|
import { filterSafeProps } from './index';
|
||||||
|
|
||||||
|
interface SVGProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SVG_ALLOWED_ATTRS = new Set([
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
'viewBox',
|
||||||
|
'fill',
|
||||||
|
'stroke',
|
||||||
|
'd',
|
||||||
|
'x',
|
||||||
|
'y',
|
||||||
|
'cx',
|
||||||
|
'cy',
|
||||||
|
'r',
|
||||||
|
'className',
|
||||||
|
'style'
|
||||||
|
]);
|
||||||
|
|
||||||
|
const SVGRenderer = ({ children, className, style, ...props }: SVGProps) => {
|
||||||
|
// filter props
|
||||||
|
const svgProps = { ...props, className, style };
|
||||||
|
const sanitizedProps = filterSafeProps(svgProps, SVG_ALLOWED_ATTRS, false);
|
||||||
|
|
||||||
|
const sanitizeSVGContent = (content: string | React.ReactNode): string => {
|
||||||
|
if (typeof content !== 'string') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
let cleaned = content;
|
||||||
|
|
||||||
|
cleaned = cleaned.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
|
||||||
|
cleaned = cleaned.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '');
|
||||||
|
cleaned = cleaned.replace(
|
||||||
|
/<foreignObject\b[^<]*(?:(?!<\/foreignObject>)<[^<]*)*<\/foreignObject>/gi,
|
||||||
|
''
|
||||||
|
);
|
||||||
|
|
||||||
|
cleaned = cleaned.replace(/\son\w+="[^"]*"/gi, '');
|
||||||
|
cleaned = cleaned.replace(/\son\w+='[^']*'/gi, '');
|
||||||
|
cleaned = cleaned.replace(/url\s*\(\s*['"]?\s*javascript:[^)]+\)/gi, '');
|
||||||
|
cleaned = cleaned.replace(/\bhref="javascript:[^"]*"/gi, '');
|
||||||
|
cleaned = cleaned.replace(/\bhref='javascript:[^']*'/gi, '');
|
||||||
|
cleaned = cleaned.replace(/\bxlink:href="javascript:[^"]*"/gi, '');
|
||||||
|
cleaned = cleaned.replace(/\bxlink:href='javascript:[^']*'/gi, '');
|
||||||
|
cleaned = cleaned.replace(/\bxmlns(:xlink)?=['"]?javascript:[^"']*['"]?/gi, '');
|
||||||
|
cleaned = cleaned.replace(/style\s*=\s*(['"])(?:(?!\1).)*javascript:.*?\1/gi, '');
|
||||||
|
|
||||||
|
cleaned = cleaned.replace(/\bdata:[^,]*?;base64,[^"')]*["')]/gi, (match) => {
|
||||||
|
return match.toLowerCase().includes('javascript') ? '' : match;
|
||||||
|
});
|
||||||
|
|
||||||
|
const ALLOWED_ATTRS = new Set([
|
||||||
|
'width',
|
||||||
|
'height',
|
||||||
|
'viewBox',
|
||||||
|
'fill',
|
||||||
|
'stroke',
|
||||||
|
'd',
|
||||||
|
'x',
|
||||||
|
'y',
|
||||||
|
'cx',
|
||||||
|
'cy',
|
||||||
|
'r',
|
||||||
|
'class',
|
||||||
|
'style'
|
||||||
|
]);
|
||||||
|
cleaned = cleaned.replace(/\s(\w+)=['"][^'"]*['"]/gi, (match, attr) => {
|
||||||
|
return ALLOWED_ATTRS.has(attr.toLowerCase()) ? match : '';
|
||||||
|
});
|
||||||
|
|
||||||
|
cleaned = cleaned.replace(/<!--[\s\S]*?-->/g, '');
|
||||||
|
|
||||||
|
return cleaned;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sanitizedContent = React.Children.map(children, (child) => {
|
||||||
|
if (typeof child === 'string') {
|
||||||
|
return sanitizeSVGContent(child);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ErrorBoundary fallback={<div>Something went wrong while rendering Markdown.</div>}>
|
||||||
|
<svg
|
||||||
|
{...sanitizedProps}
|
||||||
|
className={className}
|
||||||
|
style={style}
|
||||||
|
dangerouslySetInnerHTML={
|
||||||
|
typeof children === 'string' ? { __html: sanitizeSVGContent(children) } : undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{typeof children !== 'string' && sanitizedContent}
|
||||||
|
</svg>
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SVGRenderer;
|
||||||
2
projects/app/src/global/core/chat/api.d.ts
vendored
2
projects/app/src/global/core/chat/api.d.ts
vendored
@@ -24,7 +24,7 @@ export type GetChatRecordsProps = OutLinkChatAuthProps & {
|
|||||||
appId: string;
|
appId: string;
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
loadCustomFeedbacks?: boolean;
|
loadCustomFeedbacks?: boolean;
|
||||||
type: `${GetChatTypeEnum}`;
|
type?: `${GetChatTypeEnum}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type InitOutLinkChatProps = {
|
export type InitOutLinkChatProps = {
|
||||||
|
|||||||
@@ -39,6 +39,12 @@ export async function register() {
|
|||||||
systemStartCb();
|
systemStartCb();
|
||||||
initGlobalVariables();
|
initGlobalVariables();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await preLoadWorker();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Preload worker error', error);
|
||||||
|
}
|
||||||
|
|
||||||
// Connect to MongoDB
|
// Connect to MongoDB
|
||||||
await connectMongo(connectionMongo, MONGO_URL);
|
await connectMongo(connectionMongo, MONGO_URL);
|
||||||
connectMongo(connectionLogMongo, MONGO_LOG_URL);
|
connectMongo(connectionLogMongo, MONGO_LOG_URL);
|
||||||
@@ -54,12 +60,6 @@ export async function register() {
|
|||||||
startCron();
|
startCron();
|
||||||
startTrainingQueue(true);
|
startTrainingQueue(true);
|
||||||
|
|
||||||
try {
|
|
||||||
await preLoadWorker();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Preload worker error', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Init system success');
|
console.log('Init system success');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -25,16 +25,20 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
|
|||||||
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
|
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
|
||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
import SaveButton from '../Workflow/components/SaveButton';
|
|
||||||
import PublishHistories from '../PublishHistoriesSlider';
|
import PublishHistories from '../PublishHistoriesSlider';
|
||||||
import { WorkflowEventContext } from '../WorkflowComponents/context/workflowEventContext';
|
import { WorkflowEventContext } from '../WorkflowComponents/context/workflowEventContext';
|
||||||
import { WorkflowStatusContext } from '../WorkflowComponents/context/workflowStatusContext';
|
import { WorkflowStatusContext } from '../WorkflowComponents/context/workflowStatusContext';
|
||||||
|
import SaveButton from '../Workflow/components/SaveButton';
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { isPc } = useSystem();
|
const { isPc } = useSystem();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { toast } = useToast();
|
const { toast: backSaveToast } = useToast({
|
||||||
|
containerStyle: {
|
||||||
|
mt: '60px'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const { appDetail, onSaveApp, currentTab } = useContextSelector(AppContext, (v) => v);
|
const { appDetail, onSaveApp, currentTab } = useContextSelector(AppContext, (v) => v);
|
||||||
const isV2Workflow = appDetail?.version === 'v2';
|
const isV2Workflow = appDetail?.version === 'v2';
|
||||||
@@ -183,6 +187,7 @@ const Header = () => {
|
|||||||
size={'sm'}
|
size={'sm'}
|
||||||
leftIcon={<MyIcon name={'core/workflow/debug'} w={['14px', '16px']} />}
|
leftIcon={<MyIcon name={'core/workflow/debug'} w={['14px', '16px']} />}
|
||||||
variant={'whitePrimary'}
|
variant={'whitePrimary'}
|
||||||
|
flexShrink={0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const data = flowData2StoreDataAndCheck();
|
const data = flowData2StoreDataAndCheck();
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -211,12 +216,12 @@ const Header = () => {
|
|||||||
onBack,
|
onBack,
|
||||||
onOpenBackConfirm,
|
onOpenBackConfirm,
|
||||||
isV2Workflow,
|
isV2Workflow,
|
||||||
showHistoryModal,
|
|
||||||
t,
|
t,
|
||||||
|
showHistoryModal,
|
||||||
loading,
|
loading,
|
||||||
onClickSave,
|
onClickSave,
|
||||||
flowData2StoreDataAndCheck,
|
|
||||||
setShowHistoryModal,
|
setShowHistoryModal,
|
||||||
|
flowData2StoreDataAndCheck,
|
||||||
setWorkflowTestData
|
setWorkflowTestData
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -229,10 +234,11 @@ const Header = () => {
|
|||||||
setShowHistoryModal(false);
|
setShowHistoryModal(false);
|
||||||
}}
|
}}
|
||||||
past={past}
|
past={past}
|
||||||
onSwitchTmpVersion={onSwitchTmpVersion}
|
|
||||||
onSwitchCloudVersion={onSwitchCloudVersion}
|
onSwitchCloudVersion={onSwitchCloudVersion}
|
||||||
|
onSwitchTmpVersion={onSwitchTmpVersion}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<MyModal
|
<MyModal
|
||||||
isOpen={isOpenBackConfirm}
|
isOpen={isOpenBackConfirm}
|
||||||
onClose={onCloseBackConfirm}
|
onClose={onCloseBackConfirm}
|
||||||
@@ -254,7 +260,7 @@ const Header = () => {
|
|||||||
await onClickSave({});
|
await onClickSave({});
|
||||||
onCloseBackConfirm();
|
onCloseBackConfirm();
|
||||||
onBack();
|
onBack();
|
||||||
toast({
|
backSaveToast({
|
||||||
status: 'success',
|
status: 'success',
|
||||||
title: t('app:saved_success'),
|
title: t('app:saved_success'),
|
||||||
position: 'top-right'
|
position: 'top-right'
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { useTranslation } from 'next-i18next';
|
|||||||
|
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
import { WorkflowContext } from '../WorkflowComponents/context';
|
import { WorkflowContext, type WorkflowSnapshotsType } from '../WorkflowComponents/context';
|
||||||
import { AppContext, TabEnum } from '../context';
|
import { AppContext, TabEnum } from '../context';
|
||||||
import RouteTab from '../RouteTab';
|
import RouteTab from '../RouteTab';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
@@ -25,10 +25,10 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
|
|||||||
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
|
import { formatTime2YMDHMS } from '@fastgpt/global/common/string/time';
|
||||||
import { useToast } from '@fastgpt/web/hooks/useToast';
|
import { useToast } from '@fastgpt/web/hooks/useToast';
|
||||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||||
import SaveButton from './components/SaveButton';
|
|
||||||
import PublishHistories from '../PublishHistoriesSlider';
|
import PublishHistories from '../PublishHistoriesSlider';
|
||||||
import { WorkflowEventContext } from '../WorkflowComponents/context/workflowEventContext';
|
import { WorkflowEventContext } from '../WorkflowComponents/context/workflowEventContext';
|
||||||
import { WorkflowStatusContext } from '../WorkflowComponents/context/workflowStatusContext';
|
import { WorkflowStatusContext } from '../WorkflowComponents/context/workflowStatusContext';
|
||||||
|
import SaveButton from '../Workflow/components/SaveButton';
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -187,6 +187,7 @@ const Header = () => {
|
|||||||
size={'sm'}
|
size={'sm'}
|
||||||
leftIcon={<MyIcon name={'core/workflow/debug'} w={['14px', '16px']} />}
|
leftIcon={<MyIcon name={'core/workflow/debug'} w={['14px', '16px']} />}
|
||||||
variant={'whitePrimary'}
|
variant={'whitePrimary'}
|
||||||
|
flexShrink={0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const data = flowData2StoreDataAndCheck();
|
const data = flowData2StoreDataAndCheck();
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -215,12 +216,12 @@ const Header = () => {
|
|||||||
onBack,
|
onBack,
|
||||||
onOpenBackConfirm,
|
onOpenBackConfirm,
|
||||||
isV2Workflow,
|
isV2Workflow,
|
||||||
showHistoryModal,
|
|
||||||
t,
|
t,
|
||||||
|
showHistoryModal,
|
||||||
loading,
|
loading,
|
||||||
onClickSave,
|
onClickSave,
|
||||||
flowData2StoreDataAndCheck,
|
|
||||||
setShowHistoryModal,
|
setShowHistoryModal,
|
||||||
|
flowData2StoreDataAndCheck,
|
||||||
setWorkflowTestData
|
setWorkflowTestData
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -228,7 +229,7 @@ const Header = () => {
|
|||||||
<>
|
<>
|
||||||
{Render}
|
{Render}
|
||||||
{showHistoryModal && isV2Workflow && currentTab === TabEnum.appEdit && (
|
{showHistoryModal && isV2Workflow && currentTab === TabEnum.appEdit && (
|
||||||
<PublishHistories
|
<PublishHistories<WorkflowSnapshotsType>
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setShowHistoryModal(false);
|
setShowHistoryModal(false);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ const SaveButton = ({
|
|||||||
Trigger={
|
Trigger={
|
||||||
<Button
|
<Button
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
|
flexShrink={0}
|
||||||
rightIcon={
|
rightIcon={
|
||||||
<MyIcon
|
<MyIcon
|
||||||
name={isSave ? 'core/chat/chevronUp' : 'core/chat/chevronDown'}
|
name={isSave ? 'core/chat/chevronUp' : 'core/chat/chevronDown'}
|
||||||
|
|||||||
@@ -0,0 +1,220 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import { Box, Flex, Button, IconButton, type ButtonProps, Input } from '@chakra-ui/react';
|
||||||
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { useContextSelector } from 'use-context-selector';
|
||||||
|
import { WorkflowNodeEdgeContext } from '../../WorkflowComponents/context/workflowInitContext';
|
||||||
|
import { useReactFlow } from 'reactflow';
|
||||||
|
import { useKeyPress, useThrottleEffect } from 'ahooks';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
|
||||||
|
import { useSystem } from '@fastgpt/web/hooks/useSystem';
|
||||||
|
|
||||||
|
const SearchButton = (props: ButtonProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const setNodes = useContextSelector(WorkflowNodeEdgeContext, (state) => state.setNodes);
|
||||||
|
const { fitView } = useReactFlow();
|
||||||
|
const { isMac } = useSystem();
|
||||||
|
|
||||||
|
const [keyword, setKeyword] = useState<string>();
|
||||||
|
const [searchIndex, setSearchIndex] = useState<number>(0);
|
||||||
|
const [searchedNodeCount, setSearchedNodeCount] = useState(0);
|
||||||
|
|
||||||
|
useKeyPress(['ctrl.f', 'meta.f'], (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setKeyword('');
|
||||||
|
});
|
||||||
|
useKeyPress(['esc'], (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
setKeyword(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
const onSearch = useCallback(() => {
|
||||||
|
setNodes((nodes) => {
|
||||||
|
if (!keyword) {
|
||||||
|
setSearchIndex(0);
|
||||||
|
setSearchedNodeCount(0);
|
||||||
|
return nodes.map((node) => ({
|
||||||
|
...node,
|
||||||
|
data: {
|
||||||
|
...node.data,
|
||||||
|
searchedText: undefined
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchResult = nodes.filter((node) => {
|
||||||
|
const nodeName = t(node.data.name as any);
|
||||||
|
return nodeName.toLowerCase().includes(keyword.toLowerCase());
|
||||||
|
});
|
||||||
|
|
||||||
|
if (searchResult.length === 0) {
|
||||||
|
return nodes.map((node) => ({
|
||||||
|
...node,
|
||||||
|
data: {
|
||||||
|
...node.data,
|
||||||
|
searchedText: undefined
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
setSearchedNodeCount(searchResult.length);
|
||||||
|
|
||||||
|
const searchedNode = searchResult[searchIndex] ?? searchResult[0];
|
||||||
|
|
||||||
|
if (searchedNode) {
|
||||||
|
fitView({ nodes: [searchedNode], padding: 0.4 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes.map((node) => ({
|
||||||
|
...node,
|
||||||
|
selected: node.id === searchedNode.id,
|
||||||
|
data: {
|
||||||
|
...node.data,
|
||||||
|
searchedText: searchResult.find((item) => item.id === node.id) ? keyword : undefined
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}, [keyword, searchIndex]);
|
||||||
|
|
||||||
|
useThrottleEffect(
|
||||||
|
() => {
|
||||||
|
onSearch();
|
||||||
|
},
|
||||||
|
[onSearch],
|
||||||
|
{
|
||||||
|
wait: 500
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const goToNextMatch = useCallback(() => {
|
||||||
|
if (searchIndex === searchedNodeCount - 1) {
|
||||||
|
setSearchIndex(0);
|
||||||
|
} else {
|
||||||
|
setSearchIndex(searchIndex + 1);
|
||||||
|
}
|
||||||
|
}, [searchIndex, searchedNodeCount]);
|
||||||
|
|
||||||
|
const goToPreviousMatch = useCallback(() => {
|
||||||
|
if (searchIndex === 0) {
|
||||||
|
setSearchIndex(searchedNodeCount - 1);
|
||||||
|
} else {
|
||||||
|
setSearchIndex(searchIndex - 1);
|
||||||
|
}
|
||||||
|
}, [searchIndex, searchedNodeCount]);
|
||||||
|
|
||||||
|
const clearSearch = useCallback(() => {
|
||||||
|
setKeyword(undefined);
|
||||||
|
setSearchIndex(0);
|
||||||
|
setSearchedNodeCount(0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (keyword === undefined) {
|
||||||
|
return (
|
||||||
|
<Box position={'absolute'} top={'72px'} left={6} zIndex={1}>
|
||||||
|
<MyTooltip label={isMac ? t('workflow:find_tip_mac') : t('workflow:find_tip')}>
|
||||||
|
<IconButton
|
||||||
|
icon={<MyIcon name="common/searchLight" w="20px" color={'#8A95A7'} />}
|
||||||
|
aria-label=""
|
||||||
|
variant="whitePrimary"
|
||||||
|
size={'mdSquare'}
|
||||||
|
borderRadius={'50%'}
|
||||||
|
bg={'white'}
|
||||||
|
_hover={{ bg: 'white', borderColor: 'primary.300' }}
|
||||||
|
boxShadow={'0px 4px 10px 0px rgba(19, 51, 107, 0.20)'}
|
||||||
|
{...props}
|
||||||
|
onClick={() => setKeyword('')}
|
||||||
|
/>
|
||||||
|
</MyTooltip>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
position="absolute"
|
||||||
|
top={3}
|
||||||
|
left="50%"
|
||||||
|
transform="translateX(-50%)"
|
||||||
|
pl={5}
|
||||||
|
pr={4}
|
||||||
|
py={4}
|
||||||
|
zIndex={1}
|
||||||
|
borderRadius={'lg'}
|
||||||
|
bg={'white'}
|
||||||
|
alignItems={'center'}
|
||||||
|
boxShadow={
|
||||||
|
'0px 20px 24px -8px rgba(19, 51, 107, 0.15), 0px 0px 1px 0px rgba(19, 51, 107, 0.15)'
|
||||||
|
}
|
||||||
|
border={'0.5px solid rgba(0, 0, 0, 0.13)'}
|
||||||
|
maxW={['90vw', '550px']}
|
||||||
|
w={'100%'}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
flex="1 0 0"
|
||||||
|
h={8}
|
||||||
|
border={'none'}
|
||||||
|
px={0}
|
||||||
|
_focus={{
|
||||||
|
border: 'none',
|
||||||
|
boxShadow: 'none'
|
||||||
|
}}
|
||||||
|
fontSize={'16px'}
|
||||||
|
value={keyword}
|
||||||
|
placeholder={t('workflow:please_enter_node_name')}
|
||||||
|
autoFocus
|
||||||
|
onFocus={onSearch}
|
||||||
|
onChange={(e) => setKeyword(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
goToNextMatch();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box fontSize="sm" color="myGray.600" whiteSpace={'nowrap'} userSelect={'none'}>
|
||||||
|
{searchedNodeCount > 0
|
||||||
|
? `${searchIndex + 1} / ${searchedNodeCount}`
|
||||||
|
: t('workflow:no_match_node')}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Border */}
|
||||||
|
<Box h={5} w={'1px'} bg={'myGray.250'} ml={3} mr={2} />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="grayGhost"
|
||||||
|
px={2}
|
||||||
|
isDisabled={searchedNodeCount <= 1}
|
||||||
|
onClick={goToPreviousMatch}
|
||||||
|
>
|
||||||
|
{t('workflow:previous')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="grayGhost"
|
||||||
|
px={2}
|
||||||
|
isDisabled={searchedNodeCount <= 1}
|
||||||
|
onClick={goToNextMatch}
|
||||||
|
>
|
||||||
|
{t('workflow:next')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Flex
|
||||||
|
ml={2}
|
||||||
|
borderRadius="sm"
|
||||||
|
_hover={{ bg: 'myGray.100' }}
|
||||||
|
p={'1'}
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={clearSearch}
|
||||||
|
>
|
||||||
|
<MyIcon name="common/closeLight" w="1.2rem" />
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(SearchButton);
|
||||||
@@ -19,7 +19,7 @@ export const useNodeTemplates = () => {
|
|||||||
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
|
||||||
|
|
||||||
const hasToolNode = useMemo(
|
const hasToolNode = useMemo(
|
||||||
() => nodeList.some((node) => node.flowNodeType === FlowNodeTypeEnum.toolSet),
|
() => nodeList.some((node) => node.flowNodeType === FlowNodeTypeEnum.tools),
|
||||||
[nodeList]
|
[nodeList]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactFlow, { type NodeProps, SelectionMode } from 'reactflow';
|
import ReactFlow, { type NodeProps, SelectionMode } from 'reactflow';
|
||||||
import { Box, IconButton, useDisclosure } from '@chakra-ui/react';
|
import { Box, IconButton, useDisclosure } from '@chakra-ui/react';
|
||||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
|
||||||
import { EDGE_TYPE, FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
import { EDGE_TYPE, FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
|
||||||
|
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
@@ -20,6 +19,8 @@ import ContextMenu from './components/ContextMenu';
|
|||||||
import { WorkflowNodeEdgeContext, WorkflowInitContext } from '../context/workflowInitContext';
|
import { WorkflowNodeEdgeContext, WorkflowInitContext } from '../context/workflowInitContext';
|
||||||
import { WorkflowEventContext } from '../context/workflowEventContext';
|
import { WorkflowEventContext } from '../context/workflowEventContext';
|
||||||
import NodeTemplatesPopover from './NodeTemplatesPopover';
|
import NodeTemplatesPopover from './NodeTemplatesPopover';
|
||||||
|
import SearchButton from '../../Workflow/components/SearchButton';
|
||||||
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
|
|
||||||
const NodeSimple = dynamic(() => import('./nodes/NodeSimple'));
|
const NodeSimple = dynamic(() => import('./nodes/NodeSimple'));
|
||||||
const nodeTypes: Record<FlowNodeTypeEnum, any> = {
|
const nodeTypes: Record<FlowNodeTypeEnum, any> = {
|
||||||
@@ -113,20 +114,22 @@ const Workflow = () => {
|
|||||||
<>
|
<>
|
||||||
<IconButton
|
<IconButton
|
||||||
position={'absolute'}
|
position={'absolute'}
|
||||||
top={5}
|
top={6}
|
||||||
left={5}
|
left={6}
|
||||||
size={'mdSquare'}
|
size={'mdSquare'}
|
||||||
borderRadius={'50%'}
|
borderRadius={'50%'}
|
||||||
icon={<SmallCloseIcon fontSize={'26px'} />}
|
icon={<MyIcon name="common/addLight" w={'26px'} />}
|
||||||
transform={isOpenTemplate ? '' : 'rotate(135deg)'}
|
|
||||||
transition={'0.2s ease'}
|
transition={'0.2s ease'}
|
||||||
aria-label={''}
|
aria-label={''}
|
||||||
zIndex={1}
|
zIndex={1}
|
||||||
boxShadow={'2px 2px 6px #85b1ff'}
|
boxShadow={
|
||||||
|
'0px 4px 10px 0px rgba(19, 51, 107, 0.20), 0px 0px 1px 0px rgba(19, 51, 107, 0.50)'
|
||||||
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
isOpenTemplate ? onCloseTemplate() : onOpenTemplate();
|
isOpenTemplate ? onCloseTemplate() : onOpenTemplate();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<SearchButton />
|
||||||
<NodeTemplatesModal isOpen={isOpenTemplate} onClose={onCloseTemplate} />
|
<NodeTemplatesModal isOpen={isOpenTemplate} onClose={onCloseTemplate} />
|
||||||
<NodeTemplatesPopover />
|
<NodeTemplatesPopover />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useCallback, useMemo, useRef } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import { Box, Button, Flex, HStack, useDisclosure, type FlexProps } from '@chakra-ui/react';
|
import { Box, Button, Flex, useDisclosure, type FlexProps } from '@chakra-ui/react';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import Avatar from '@fastgpt/web/components/common/Avatar';
|
import Avatar from '@fastgpt/web/components/common/Avatar';
|
||||||
import type { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
import type { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||||
@@ -15,7 +15,7 @@ import { ToolSourceHandle, ToolTargetHandle } from './Handle/ToolHandle';
|
|||||||
import { useEditTextarea } from '@fastgpt/web/hooks/useEditTextarea';
|
import { useEditTextarea } from '@fastgpt/web/hooks/useEditTextarea';
|
||||||
import { ConnectionSourceHandle, ConnectionTargetHandle } from './Handle/ConnectionHandle';
|
import { ConnectionSourceHandle, ConnectionTargetHandle } from './Handle/ConnectionHandle';
|
||||||
import { useDebug } from '../../hooks/useDebug';
|
import { useDebug } from '../../hooks/useDebug';
|
||||||
import { getPreviewPluginNode } from '@/web/core/app/api/plugin';
|
import { getPreviewPluginNode, getToolVersionList } from '@/web/core/app/api/plugin';
|
||||||
import { storeNode2FlowNode } from '@/web/core/workflow/utils';
|
import { storeNode2FlowNode } from '@/web/core/workflow/utils';
|
||||||
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
import { getNanoid } from '@fastgpt/global/common/string/tools';
|
||||||
import { useContextSelector } from 'use-context-selector';
|
import { useContextSelector } from 'use-context-selector';
|
||||||
@@ -36,6 +36,7 @@ import MyTag from '@fastgpt/web/components/common/Tag/index';
|
|||||||
import MySelect from '@fastgpt/web/components/common/MySelect';
|
import MySelect from '@fastgpt/web/components/common/MySelect';
|
||||||
import { useCreation } from 'ahooks';
|
import { useCreation } from 'ahooks';
|
||||||
import { formatToolError } from '@fastgpt/global/core/app/utils';
|
import { formatToolError } from '@fastgpt/global/core/app/utils';
|
||||||
|
import HighlightText from '@fastgpt/web/components/common/String/HighlightText';
|
||||||
|
|
||||||
type Props = FlowNodeItemType & {
|
type Props = FlowNodeItemType & {
|
||||||
children?: React.ReactNode | React.ReactNode[] | string;
|
children?: React.ReactNode | React.ReactNode[] | string;
|
||||||
@@ -45,6 +46,7 @@ type Props = FlowNodeItemType & {
|
|||||||
w?: string | number;
|
w?: string | number;
|
||||||
h?: string | number;
|
h?: string | number;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
|
searchedText?: string;
|
||||||
menuForbid?: {
|
menuForbid?: {
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
copy?: boolean;
|
copy?: boolean;
|
||||||
@@ -70,6 +72,7 @@ const NodeCard = (props: Props) => {
|
|||||||
h = 'full',
|
h = 'full',
|
||||||
nodeId,
|
nodeId,
|
||||||
selected,
|
selected,
|
||||||
|
searchedText,
|
||||||
menuForbid,
|
menuForbid,
|
||||||
isTool = false,
|
isTool = false,
|
||||||
isError = false,
|
isError = false,
|
||||||
@@ -104,12 +107,9 @@ const NodeCard = (props: Props) => {
|
|||||||
}, [nodeList, nodeId]);
|
}, [nodeList, nodeId]);
|
||||||
const isAppNode = node && AppNodeFlowNodeTypeMap[node?.flowNodeType];
|
const isAppNode = node && AppNodeFlowNodeTypeMap[node?.flowNodeType];
|
||||||
const showVersion = useMemo(() => {
|
const showVersion = useMemo(() => {
|
||||||
if (!isAppNode || !node?.pluginId) return false;
|
if (!isAppNode || !node?.pluginId || node?.pluginData?.error) return false;
|
||||||
if ([FlowNodeTypeEnum.tool, FlowNodeTypeEnum.toolSet].includes(node.flowNodeType)) return false;
|
if ([FlowNodeTypeEnum.tool, FlowNodeTypeEnum.toolSet].includes(node.flowNodeType)) return false;
|
||||||
if (node.pluginId.split('-').length > 1) {
|
return typeof node.version === 'string';
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}, [isAppNode, node]);
|
}, [isAppNode, node]);
|
||||||
|
|
||||||
const { data: nodeTemplate } = useRequest2(
|
const { data: nodeTemplate } = useRequest2(
|
||||||
@@ -190,7 +190,12 @@ const NodeCard = (props: Props) => {
|
|||||||
h={'24px'}
|
h={'24px'}
|
||||||
/>
|
/>
|
||||||
<Box ml={2} fontSize={'18px'} fontWeight={'medium'} color={'myGray.900'}>
|
<Box ml={2} fontSize={'18px'} fontWeight={'medium'} color={'myGray.900'}>
|
||||||
{t(name as any)}
|
<HighlightText
|
||||||
|
rawText={t(name as any)}
|
||||||
|
matchText={searchedText ?? ''}
|
||||||
|
mode={'bg'}
|
||||||
|
color={'#ffe82d'}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Button
|
<Button
|
||||||
display={'none'}
|
display={'none'}
|
||||||
@@ -283,6 +288,7 @@ const NodeCard = (props: Props) => {
|
|||||||
nodeId,
|
nodeId,
|
||||||
isFolded,
|
isFolded,
|
||||||
avatar,
|
avatar,
|
||||||
|
searchedText,
|
||||||
t,
|
t,
|
||||||
name,
|
name,
|
||||||
showVersion,
|
showVersion,
|
||||||
@@ -617,11 +623,10 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt
|
|||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
// Load version list
|
// Load version list
|
||||||
const { ScrollData, data: versionList } = useScrollPagination(getAppVersionList, {
|
const { ScrollData, data: versionList } = useScrollPagination(getToolVersionList, {
|
||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
params: {
|
params: {
|
||||||
appId: node.pluginId,
|
toolId: node.pluginId
|
||||||
isPublish: true
|
|
||||||
},
|
},
|
||||||
refreshDeps: [node.pluginId, isOpen],
|
refreshDeps: [node.pluginId, isOpen],
|
||||||
disalbed: !isOpen,
|
disalbed: !isOpen,
|
||||||
@@ -653,18 +658,23 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderList = useCreation(
|
const renderVersionList = useCreation(
|
||||||
() =>
|
() => [
|
||||||
versionList.map((item) => ({
|
{
|
||||||
|
label: t('app:keep_the_latest'),
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
...versionList.map((item) => ({
|
||||||
label: item.versionName,
|
label: item.versionName,
|
||||||
value: item._id
|
value: item._id
|
||||||
})),
|
}))
|
||||||
|
],
|
||||||
[node.isLatestVersion, node.version, t, versionList]
|
[node.isLatestVersion, node.version, t, versionList]
|
||||||
);
|
);
|
||||||
const valueLabel = useMemo(() => {
|
const valueLabel = useMemo(() => {
|
||||||
return (
|
return (
|
||||||
<Flex alignItems={'center'} gap={0.5}>
|
<Flex alignItems={'center'} gap={0.5}>
|
||||||
{node?.versionLabel}
|
{node?.version === '' ? t('app:keep_the_latest') : node?.versionLabel}
|
||||||
{!node.isLatestVersion && (
|
{!node.isLatestVersion && (
|
||||||
<MyTag type="fill" colorSchema={'adora'} fontSize={'mini'} borderRadius={'lg'}>
|
<MyTag type="fill" colorSchema={'adora'} fontSize={'mini'} borderRadius={'lg'}>
|
||||||
{t('app:not_the_newest')}
|
{t('app:not_the_newest')}
|
||||||
@@ -672,7 +682,7 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt
|
|||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}, [node.isLatestVersion, node?.versionLabel, t]);
|
}, [node.isLatestVersion, node?.version, node?.versionLabel, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MySelect
|
<MySelect
|
||||||
@@ -685,7 +695,7 @@ const NodeVersion = React.memo(function NodeVersion({ node }: { node: FlowNodeIt
|
|||||||
placeholder={node?.versionLabel}
|
placeholder={node?.versionLabel}
|
||||||
variant={'whitePrimaryOutline'}
|
variant={'whitePrimaryOutline'}
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
list={renderList}
|
list={renderVersionList}
|
||||||
ScrollData={(props) => (
|
ScrollData={(props) => (
|
||||||
<ScrollData minH={'100px'} maxH={'40vh'}>
|
<ScrollData minH={'100px'} maxH={'40vh'}>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ type WorkflowContextType = {
|
|||||||
|
|
||||||
// nodes
|
// nodes
|
||||||
nodeList: FlowNodeItemType[];
|
nodeList: FlowNodeItemType[];
|
||||||
hasToolNode: boolean;
|
|
||||||
|
|
||||||
onUpdateNodeError: (node: string, isError: Boolean) => void;
|
onUpdateNodeError: (node: string, isError: Boolean) => void;
|
||||||
onResetNode: (e: { id: string; node: FlowNodeTemplateType }) => void;
|
onResetNode: (e: { id: string; node: FlowNodeTemplateType }) => void;
|
||||||
@@ -226,7 +225,6 @@ export const WorkflowContext = createContext<WorkflowContextType>({
|
|||||||
},
|
},
|
||||||
basicNodeTemplates: [],
|
basicNodeTemplates: [],
|
||||||
nodeList: [],
|
nodeList: [],
|
||||||
hasToolNode: false,
|
|
||||||
onUpdateNodeError: function (node: string, isError: Boolean): void {
|
onUpdateNodeError: function (node: string, isError: Boolean): void {
|
||||||
throw new Error('Function not implemented.');
|
throw new Error('Function not implemented.');
|
||||||
},
|
},
|
||||||
@@ -399,10 +397,6 @@ const WorkflowContextProvider = ({
|
|||||||
[nodeListString]
|
[nodeListString]
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasToolNode = useMemo(() => {
|
|
||||||
return !!nodeList.find((node) => node.flowNodeType === FlowNodeTypeEnum.tools);
|
|
||||||
}, [nodeList]);
|
|
||||||
|
|
||||||
const onUpdateNodeError = useMemoizedFn((nodeId: string, isError: Boolean) => {
|
const onUpdateNodeError = useMemoizedFn((nodeId: string, isError: Boolean) => {
|
||||||
setNodes((state) => {
|
setNodes((state) => {
|
||||||
return state.map((item) => {
|
return state.map((item) => {
|
||||||
@@ -1011,7 +1005,6 @@ const WorkflowContextProvider = ({
|
|||||||
|
|
||||||
// node
|
// node
|
||||||
nodeList,
|
nodeList,
|
||||||
hasToolNode,
|
|
||||||
onUpdateNodeError,
|
onUpdateNodeError,
|
||||||
onResetNode,
|
onResetNode,
|
||||||
onChangeNode,
|
onChangeNode,
|
||||||
@@ -1057,7 +1050,6 @@ const WorkflowContextProvider = ({
|
|||||||
flowData2StoreDataAndCheck,
|
flowData2StoreDataAndCheck,
|
||||||
future,
|
future,
|
||||||
getNodeDynamicInputs,
|
getNodeDynamicInputs,
|
||||||
hasToolNode,
|
|
||||||
initData,
|
initData,
|
||||||
nodeList,
|
nodeList,
|
||||||
onChangeNode,
|
onChangeNode,
|
||||||
|
|||||||
@@ -100,8 +100,6 @@ const WebsiteConfigModal = ({
|
|||||||
paragraphChunkDeep: chunkSettings?.paragraphChunkDeep || defaultFormData.paragraphChunkDeep,
|
paragraphChunkDeep: chunkSettings?.paragraphChunkDeep || defaultFormData.paragraphChunkDeep,
|
||||||
paragraphChunkMinSize:
|
paragraphChunkMinSize:
|
||||||
chunkSettings?.paragraphChunkMinSize || defaultFormData.paragraphChunkMinSize,
|
chunkSettings?.paragraphChunkMinSize || defaultFormData.paragraphChunkMinSize,
|
||||||
paragraphChunkMaxSize:
|
|
||||||
chunkSettings?.paragraphChunkMaxSize || defaultFormData.paragraphChunkMaxSize,
|
|
||||||
|
|
||||||
chunkSize: chunkSettings?.chunkSize || defaultFormData.chunkSize,
|
chunkSize: chunkSettings?.chunkSize || defaultFormData.chunkSize,
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,8 @@ import {
|
|||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import MyIcon from '@fastgpt/web/components/common/Icon';
|
import MyIcon from '@fastgpt/web/components/common/Icon';
|
||||||
import LeftRadio from '@fastgpt/web/components/common/Radio/LeftRadio';
|
import LeftRadio from '@fastgpt/web/components/common/Radio/LeftRadio';
|
||||||
import type {
|
import type { ParagraphChunkAIModeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||||
ChunkTriggerConfigTypeEnum,
|
import { ChunkTriggerConfigTypeEnum } from '@fastgpt/global/core/dataset/constants';
|
||||||
ParagraphChunkAIModeEnum
|
|
||||||
} from '@fastgpt/global/core/dataset/constants';
|
|
||||||
import {
|
import {
|
||||||
DataChunkSplitModeEnum,
|
DataChunkSplitModeEnum,
|
||||||
DatasetCollectionDataProcessModeEnum,
|
DatasetCollectionDataProcessModeEnum,
|
||||||
@@ -108,7 +106,6 @@ export type CollectionChunkFormType = {
|
|||||||
paragraphChunkAIMode: ParagraphChunkAIModeEnum;
|
paragraphChunkAIMode: ParagraphChunkAIModeEnum;
|
||||||
paragraphChunkDeep: number; // Paragraph deep
|
paragraphChunkDeep: number; // Paragraph deep
|
||||||
paragraphChunkMinSize: number; // Paragraph min size, if too small, it will merge
|
paragraphChunkMinSize: number; // Paragraph min size, if too small, it will merge
|
||||||
paragraphChunkMaxSize: number; // Paragraph max size, if too large, it will split
|
|
||||||
// Size split
|
// Size split
|
||||||
chunkSize: number;
|
chunkSize: number;
|
||||||
// Char split
|
// Char split
|
||||||
@@ -130,6 +127,7 @@ const CollectionChunkForm = ({ form }: { form: UseFormReturn<CollectionChunkForm
|
|||||||
const { setValue, register, watch, getValues } = form;
|
const { setValue, register, watch, getValues } = form;
|
||||||
|
|
||||||
const trainingType = watch('trainingType');
|
const trainingType = watch('trainingType');
|
||||||
|
const chunkTriggerType = watch('chunkTriggerType');
|
||||||
const chunkSettingMode = watch('chunkSettingMode');
|
const chunkSettingMode = watch('chunkSettingMode');
|
||||||
const chunkSplitMode = watch('chunkSplitMode');
|
const chunkSplitMode = watch('chunkSplitMode');
|
||||||
const autoIndexes = watch('autoIndexes');
|
const autoIndexes = watch('autoIndexes');
|
||||||
@@ -151,6 +149,14 @@ const CollectionChunkForm = ({ form }: { form: UseFormReturn<CollectionChunkForm
|
|||||||
}));
|
}));
|
||||||
}, [t]);
|
}, [t]);
|
||||||
|
|
||||||
|
// Chunk trigger
|
||||||
|
const chunkTriggerSelectList = [
|
||||||
|
{ label: t('dataset:chunk_trigger_min_size'), value: ChunkTriggerConfigTypeEnum.minSize },
|
||||||
|
{ label: t('dataset:chunk_trigger_max_size'), value: ChunkTriggerConfigTypeEnum.maxSize },
|
||||||
|
{ label: t('dataset:chunk_trigger_force_chunk'), value: ChunkTriggerConfigTypeEnum.forceChunk }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Form max or min value
|
||||||
const {
|
const {
|
||||||
maxChunkSize,
|
maxChunkSize,
|
||||||
minChunkSize: minChunkSizeValue,
|
minChunkSize: minChunkSizeValue,
|
||||||
@@ -189,14 +195,11 @@ const CollectionChunkForm = ({ form }: { form: UseFormReturn<CollectionChunkForm
|
|||||||
{ label: '=====', value: '=====' },
|
{ label: '=====', value: '=====' },
|
||||||
{ label: t('dataset:split_sign_custom'), value: 'Other' }
|
{ label: t('dataset:split_sign_custom'), value: 'Other' }
|
||||||
];
|
];
|
||||||
const [customListSelectValue, setCustomListSelectValue] = useState(getValues('chunkSplitter'));
|
const [customListSelectValue, setCustomListSelectValue] = useState(
|
||||||
useEffect(() => {
|
customSplitList.some((item) => item.value === getValues('chunkSplitter'))
|
||||||
if (customListSelectValue === 'Other') {
|
? getValues('chunkSplitter')
|
||||||
setValue('chunkSplitter', '');
|
: 'Other'
|
||||||
} else {
|
);
|
||||||
setValue('chunkSplitter', customListSelectValue);
|
|
||||||
}
|
|
||||||
}, [customListSelectValue, setValue]);
|
|
||||||
|
|
||||||
// Index size
|
// Index size
|
||||||
const indexSizeSeletorList = useMemo(() => getIndexSizeSelectList(maxIndexSize), [maxIndexSize]);
|
const indexSizeSeletorList = useMemo(() => getIndexSizeSelectList(maxIndexSize), [maxIndexSize]);
|
||||||
@@ -243,6 +246,41 @@ const CollectionChunkForm = ({ form }: { form: UseFormReturn<CollectionChunkForm
|
|||||||
gridTemplateColumns={'repeat(2, 1fr)'}
|
gridTemplateColumns={'repeat(2, 1fr)'}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{trainingType === DatasetCollectionDataProcessModeEnum.chunk && (
|
||||||
|
<Box mt={6}>
|
||||||
|
<HStack fontSize={'sm'} mb={2} color={'myGray.600'} spacing={1}>
|
||||||
|
<Box>{t('dataset:chunk_trigger')}</Box>
|
||||||
|
<QuestionTip label={t('dataset:chunk_trigger_tips')} />
|
||||||
|
</HStack>
|
||||||
|
<HStack>
|
||||||
|
<Box flex={'1 0 0'} h={'34px'}>
|
||||||
|
<MySelect
|
||||||
|
borderRadius={'md'}
|
||||||
|
list={chunkTriggerSelectList}
|
||||||
|
value={chunkTriggerType}
|
||||||
|
onChange={(e) => {
|
||||||
|
setValue('chunkTriggerType', e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
{chunkTriggerType === ChunkTriggerConfigTypeEnum.minSize && (
|
||||||
|
<Box flex={'1 0 0'}>
|
||||||
|
<MyNumberInput
|
||||||
|
h={'34px'}
|
||||||
|
bg={'white'}
|
||||||
|
min={100}
|
||||||
|
max={100000}
|
||||||
|
register={register}
|
||||||
|
name={'chunkTriggerMinSize'}
|
||||||
|
step={100}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
{trainingType === DatasetCollectionDataProcessModeEnum.chunk &&
|
{trainingType === DatasetCollectionDataProcessModeEnum.chunk &&
|
||||||
feConfigs?.show_dataset_enhance !== false && (
|
feConfigs?.show_dataset_enhance !== false && (
|
||||||
<Box mt={6}>
|
<Box mt={6}>
|
||||||
@@ -287,7 +325,7 @@ const CollectionChunkForm = ({ form }: { form: UseFormReturn<CollectionChunkForm
|
|||||||
)}
|
)}
|
||||||
<Box mt={6}>
|
<Box mt={6}>
|
||||||
<Box fontSize={'sm'} mb={2} color={'myGray.600'}>
|
<Box fontSize={'sm'} mb={2} color={'myGray.600'}>
|
||||||
{t('dataset:params_setting')}
|
{t('dataset:chunk_process_params')}
|
||||||
</Box>
|
</Box>
|
||||||
<LeftRadio<ChunkSettingModeEnum>
|
<LeftRadio<ChunkSettingModeEnum>
|
||||||
list={[
|
list={[
|
||||||
@@ -305,6 +343,11 @@ const CollectionChunkForm = ({ form }: { form: UseFormReturn<CollectionChunkForm
|
|||||||
<Box>
|
<Box>
|
||||||
<RadioGroup<DataChunkSplitModeEnum>
|
<RadioGroup<DataChunkSplitModeEnum>
|
||||||
list={[
|
list={[
|
||||||
|
{
|
||||||
|
title: t('dataset:paragraph_split'),
|
||||||
|
value: DataChunkSplitModeEnum.paragraph,
|
||||||
|
tooltip: t('dataset:paragraph_split_tip')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t('dataset:split_chunk_size'),
|
title: t('dataset:split_chunk_size'),
|
||||||
value: DataChunkSplitModeEnum.size
|
value: DataChunkSplitModeEnum.size
|
||||||
@@ -321,30 +364,76 @@ const CollectionChunkForm = ({ form }: { form: UseFormReturn<CollectionChunkForm
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{chunkSplitMode === DataChunkSplitModeEnum.size && (
|
{chunkSplitMode === DataChunkSplitModeEnum.paragraph && (
|
||||||
<Box
|
<>
|
||||||
mt={1.5}
|
<Box mt={1.5}>
|
||||||
css={{
|
<Box>{t('dataset:paragraph_max_deep')}</Box>
|
||||||
'& > span': {
|
|
||||||
display: 'block'
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MyTooltip
|
|
||||||
label={t('common:core.dataset.import.Chunk Range', {
|
|
||||||
min: minChunkSizeValue,
|
|
||||||
max: maxChunkSize
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<MyNumberInput
|
<MyNumberInput
|
||||||
register={register}
|
|
||||||
name={'chunkSize'}
|
|
||||||
min={minChunkSizeValue}
|
|
||||||
max={maxChunkSize}
|
|
||||||
size={'sm'}
|
size={'sm'}
|
||||||
step={100}
|
bg={'myGray.50'}
|
||||||
|
register={register}
|
||||||
|
name={'paragraphChunkDeep'}
|
||||||
|
min={1}
|
||||||
|
max={8}
|
||||||
|
step={1}
|
||||||
|
h={'32px'}
|
||||||
/>
|
/>
|
||||||
</MyTooltip>
|
</Box>
|
||||||
|
<Box mt={1.5}>
|
||||||
|
<Box>{t('dataset:max_chunk_size')}</Box>
|
||||||
|
<Box
|
||||||
|
css={{
|
||||||
|
'& > span': {
|
||||||
|
display: 'block'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MyTooltip
|
||||||
|
label={t('common:core.dataset.import.Chunk Range', {
|
||||||
|
min: minChunkSizeValue,
|
||||||
|
max: maxChunkSize
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<MyNumberInput
|
||||||
|
register={register}
|
||||||
|
name={'chunkSize'}
|
||||||
|
min={minChunkSizeValue}
|
||||||
|
max={maxChunkSize}
|
||||||
|
size={'sm'}
|
||||||
|
step={100}
|
||||||
|
/>
|
||||||
|
</MyTooltip>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{chunkSplitMode === DataChunkSplitModeEnum.size && (
|
||||||
|
<Box mt={1.5}>
|
||||||
|
<Box>{t('dataset:chunk_size')}</Box>
|
||||||
|
<Box
|
||||||
|
css={{
|
||||||
|
'& > span': {
|
||||||
|
display: 'block'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MyTooltip
|
||||||
|
label={t('common:core.dataset.import.Chunk Range', {
|
||||||
|
min: minChunkSizeValue,
|
||||||
|
max: maxChunkSize
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<MyNumberInput
|
||||||
|
register={register}
|
||||||
|
name={'chunkSize'}
|
||||||
|
min={minChunkSizeValue}
|
||||||
|
max={maxChunkSize}
|
||||||
|
size={'sm'}
|
||||||
|
step={100}
|
||||||
|
/>
|
||||||
|
</MyTooltip>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -358,6 +447,11 @@ const CollectionChunkForm = ({ form }: { form: UseFormReturn<CollectionChunkForm
|
|||||||
value={customListSelectValue}
|
value={customListSelectValue}
|
||||||
h={'32px'}
|
h={'32px'}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
|
if (val === 'Other') {
|
||||||
|
setValue('chunkSplitter', '');
|
||||||
|
} else {
|
||||||
|
setValue('chunkSplitter', val);
|
||||||
|
}
|
||||||
setCustomListSelectValue(val);
|
setCustomListSelectValue(val);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -51,11 +51,10 @@ export const defaultFormData: ImportFormType = {
|
|||||||
autoIndexes: false,
|
autoIndexes: false,
|
||||||
|
|
||||||
chunkSettingMode: ChunkSettingModeEnum.auto,
|
chunkSettingMode: ChunkSettingModeEnum.auto,
|
||||||
chunkSplitMode: DataChunkSplitModeEnum.size,
|
chunkSplitMode: DataChunkSplitModeEnum.paragraph,
|
||||||
paragraphChunkAIMode: ParagraphChunkAIModeEnum.auto,
|
paragraphChunkAIMode: ParagraphChunkAIModeEnum.auto,
|
||||||
paragraphChunkDeep: 4,
|
paragraphChunkDeep: 5,
|
||||||
paragraphChunkMinSize: 100,
|
paragraphChunkMinSize: 100,
|
||||||
paragraphChunkMaxSize: chunkAutoChunkSize,
|
|
||||||
|
|
||||||
chunkSize: chunkAutoChunkSize,
|
chunkSize: chunkAutoChunkSize,
|
||||||
chunkSplitter: '',
|
chunkSplitter: '',
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const CustomTextInput = () => {
|
|||||||
createStatus: 'waiting',
|
createStatus: 'waiting',
|
||||||
rawText: data.value,
|
rawText: data.value,
|
||||||
sourceName: data.name,
|
sourceName: data.name,
|
||||||
icon: 'file/fill/manual'
|
icon: 'file/fill/txt'
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
goToNext();
|
goToNext();
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ import { useRouter } from 'next/router';
|
|||||||
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
|
||||||
import { getDatasetCollectionById } from '@/web/core/dataset/api';
|
import { getDatasetCollectionById } from '@/web/core/dataset/api';
|
||||||
import MyBox from '@fastgpt/web/components/common/MyBox';
|
import MyBox from '@fastgpt/web/components/common/MyBox';
|
||||||
import { ChunkSettingModeEnum } from '@fastgpt/global/core/dataset/constants';
|
|
||||||
import { getCollectionIcon } from '@fastgpt/global/core/dataset/utils';
|
import { getCollectionIcon } from '@fastgpt/global/core/dataset/utils';
|
||||||
import { Box } from '@chakra-ui/react';
|
import { Box } from '@chakra-ui/react';
|
||||||
import { DataChunkSplitModeEnum } from '@fastgpt/global/core/dataset/constants';
|
|
||||||
import { Prompt_AgentQA } from '@fastgpt/global/core/ai/prompt/agent';
|
import { Prompt_AgentQA } from '@fastgpt/global/core/ai/prompt/agent';
|
||||||
|
|
||||||
const Upload = dynamic(() => import('../commonProgress/Upload'));
|
const Upload = dynamic(() => import('../commonProgress/Upload'));
|
||||||
@@ -68,8 +66,6 @@ const ReTraining = () => {
|
|||||||
paragraphChunkDeep: collection.paragraphChunkDeep || defaultFormData.paragraphChunkDeep,
|
paragraphChunkDeep: collection.paragraphChunkDeep || defaultFormData.paragraphChunkDeep,
|
||||||
paragraphChunkMinSize:
|
paragraphChunkMinSize:
|
||||||
collection.paragraphChunkMinSize || defaultFormData.paragraphChunkMinSize,
|
collection.paragraphChunkMinSize || defaultFormData.paragraphChunkMinSize,
|
||||||
paragraphChunkMaxSize:
|
|
||||||
collection.paragraphChunkMaxSize || defaultFormData.paragraphChunkMaxSize,
|
|
||||||
|
|
||||||
chunkSize: collection.chunkSize || defaultFormData.chunkSize,
|
chunkSize: collection.chunkSize || defaultFormData.chunkSize,
|
||||||
|
|
||||||
@@ -85,11 +81,13 @@ const ReTraining = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<MyBox isLoading={loading} h={'100%'}>
|
<MyBox isLoading={loading} h={'100%'}>
|
||||||
<Box h={'100%'} overflow={'auto'}>
|
{!loading && (
|
||||||
{activeStep === 0 && <DataProcess />}
|
<Box h={'100%'} overflow={'auto'}>
|
||||||
{activeStep === 1 && <PreviewData />}
|
{activeStep === 0 && <DataProcess />}
|
||||||
{activeStep === 2 && <Upload />}
|
{activeStep === 1 && <PreviewData />}
|
||||||
</Box>
|
{activeStep === 2 && <Upload />}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</MyBox>
|
</MyBox>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
|||||||
import { NextAPI } from '@/service/middleware/entry';
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||||
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
import { CommonErrEnum } from '@fastgpt/global/common/error/code/common';
|
||||||
import { checkNode } from '@/service/core/app/utils';
|
|
||||||
import { rewriteAppWorkflowToDetail } from '@fastgpt/service/core/app/utils';
|
import { rewriteAppWorkflowToDetail } from '@fastgpt/service/core/app/utils';
|
||||||
/* 获取应用详情 */
|
/* 获取应用详情 */
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||||
@@ -23,6 +22,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
|||||||
await rewriteAppWorkflowToDetail({
|
await rewriteAppWorkflowToDetail({
|
||||||
nodes: app.modules,
|
nodes: app.modules,
|
||||||
teamId,
|
teamId,
|
||||||
|
ownerTmbId: app.tmbId,
|
||||||
isRoot
|
isRoot
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -34,12 +34,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return app;
|
||||||
...app,
|
|
||||||
modules: await Promise.all(
|
|
||||||
app.modules.map((node) => checkNode({ node, ownerTmbId: app.tmbId }))
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default NextAPI(handler);
|
export default NextAPI(handler);
|
||||||
|
|||||||
@@ -138,18 +138,20 @@ async function handler(req: ApiRequestProps<ListAppBody>): Promise<AppListItemTy
|
|||||||
})();
|
})();
|
||||||
const limit = (() => {
|
const limit = (() => {
|
||||||
if (getRecentlyChat) return 15;
|
if (getRecentlyChat) return 15;
|
||||||
if (searchKey) return 20;
|
if (searchKey) return 50;
|
||||||
return 1000;
|
return;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const myApps = await MongoApp.find(
|
const myApps = await MongoApp.find(
|
||||||
findAppsQuery,
|
findAppsQuery,
|
||||||
'_id parentId avatar type name intro tmbId updateTime pluginData inheritPermission'
|
'_id parentId avatar type name intro tmbId updateTime pluginData inheritPermission',
|
||||||
|
{
|
||||||
|
limit: limit
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.sort({
|
.sort({
|
||||||
updateTime: -1
|
updateTime: -1
|
||||||
})
|
})
|
||||||
.limit(limit)
|
|
||||||
.lean();
|
.lean();
|
||||||
|
|
||||||
// Add app permission and filter apps by read permission
|
// Add app permission and filter apps by read permission
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import type { NextApiResponse } from 'next';
|
import type { NextApiResponse } from 'next';
|
||||||
import {
|
import {
|
||||||
getChildAppPreviewNode,
|
getChildAppPreviewNode,
|
||||||
splitCombinePluginId
|
splitCombineToolId
|
||||||
} from '@fastgpt/service/core/app/plugin/controller';
|
} from '@fastgpt/service/core/app/plugin/controller';
|
||||||
import { type FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node.d';
|
import { type FlowNodeTemplateType } from '@fastgpt/global/core/workflow/type/node.d';
|
||||||
import { NextAPI } from '@/service/middleware/entry';
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
@@ -21,7 +21,7 @@ async function handler(
|
|||||||
): Promise<FlowNodeTemplateType> {
|
): Promise<FlowNodeTemplateType> {
|
||||||
const { appId, versionId } = req.query;
|
const { appId, versionId } = req.query;
|
||||||
|
|
||||||
const { source } = await splitCombinePluginId(appId);
|
const { source } = splitCombineToolId(appId);
|
||||||
|
|
||||||
if (source === PluginSourceEnum.personal) {
|
if (source === PluginSourceEnum.personal) {
|
||||||
await authApp({ req, authToken: true, appId, per: ReadPermissionVal });
|
await authApp({ req, authToken: true, appId, per: ReadPermissionVal });
|
||||||
|
|||||||
86
projects/app/src/pages/api/core/app/plugin/getVersionList.ts
Normal file
86
projects/app/src/pages/api/core/app/plugin/getVersionList.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import type { NextApiResponse } from 'next';
|
||||||
|
import { NextAPI } from '@/service/middleware/entry';
|
||||||
|
import { MongoAppVersion } from '@fastgpt/service/core/app/version/schema';
|
||||||
|
import { type PaginationProps, type PaginationResponse } from '@fastgpt/web/common/fetch/type';
|
||||||
|
import { type ApiRequestProps } from '@fastgpt/service/type/next';
|
||||||
|
import { authApp } from '@fastgpt/service/support/permission/app/auth';
|
||||||
|
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
|
||||||
|
import { parsePaginationRequest } from '@fastgpt/service/common/api/pagination';
|
||||||
|
import { splitCombineToolId } from '@fastgpt/service/core/app/plugin/controller';
|
||||||
|
import { PluginSourceEnum } from '@fastgpt/global/core/plugin/constants';
|
||||||
|
import { getSystemPluginTemplates } from '@fastgpt/plugins/register';
|
||||||
|
import { PluginErrEnum } from '@fastgpt/global/common/error/code/plugin';
|
||||||
|
import { Types } from '@fastgpt/service/common/mongo';
|
||||||
|
|
||||||
|
export type getToolVersionListProps = PaginationProps<{
|
||||||
|
toolId?: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type getToolVersionResponse = PaginationResponse<{
|
||||||
|
_id: string;
|
||||||
|
versionName: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
async function handler(
|
||||||
|
req: ApiRequestProps<getToolVersionListProps>,
|
||||||
|
_res: NextApiResponse<any>
|
||||||
|
): Promise<getToolVersionResponse> {
|
||||||
|
const { toolId } = req.body;
|
||||||
|
const { offset, pageSize } = parsePaginationRequest(req);
|
||||||
|
|
||||||
|
if (!toolId) {
|
||||||
|
return {
|
||||||
|
total: 0,
|
||||||
|
list: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { source, pluginId: formatToolId } = splitCombineToolId(toolId);
|
||||||
|
|
||||||
|
// Auth
|
||||||
|
const appId = await (async () => {
|
||||||
|
if (source === PluginSourceEnum.personal) {
|
||||||
|
const { app } = await authApp({
|
||||||
|
appId: formatToolId,
|
||||||
|
req,
|
||||||
|
per: ReadPermissionVal,
|
||||||
|
authToken: true
|
||||||
|
});
|
||||||
|
return app._id;
|
||||||
|
} else {
|
||||||
|
const item = getSystemPluginTemplates().find((plugin) => plugin.id === formatToolId);
|
||||||
|
if (!item) return Promise.reject(PluginErrEnum.unAuth);
|
||||||
|
return item.associatedPluginId;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (!appId || !Types.ObjectId.isValid(appId)) {
|
||||||
|
return {
|
||||||
|
total: 0,
|
||||||
|
list: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = {
|
||||||
|
appId,
|
||||||
|
isPublish: true
|
||||||
|
};
|
||||||
|
|
||||||
|
const [result, total] = await Promise.all([
|
||||||
|
await MongoAppVersion.find(match, 'versionName')
|
||||||
|
.sort({
|
||||||
|
time: -1
|
||||||
|
})
|
||||||
|
.skip(offset)
|
||||||
|
.limit(pageSize)
|
||||||
|
.lean(),
|
||||||
|
MongoAppVersion.countDocuments(match)
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
total,
|
||||||
|
list: result
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NextAPI(handler);
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user