4.8.8 test fix (#2143)

* perf: transcriptions api

* perf: variable picker tip

* perf: variable picker tip

* perf: chat select app

* feat: router to app detail

* perf: variable avoid space

* perf: variable picker

* perf: doc2x icon and params

* perf: sandbox support countToken

* feat: sandbox support delay and countToken
This commit is contained in:
Archer
2024-07-24 16:02:53 +08:00
committed by GitHub
parent a478621730
commit 45b8d7e8de
49 changed files with 521 additions and 527 deletions

View File

@@ -4,3 +4,5 @@ dist
node_modules
docSite/
*.md
cl100l_base.ts

View File

@@ -20,5 +20,8 @@
"i18n-ally.displayLanguage": "zh", // 显示语言
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{locale}/{namespaces}.json",
"i18n-ally.extract.targetPickingStrategy": "most-similar-by-key"
"i18n-ally.extract.targetPickingStrategy": "most-similar-by-key",
"[typescript]": {
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
}
}

View File

@@ -18,13 +18,17 @@ weight: 816
-------
## V4.8. 8 更新说明
## V4.8.8 更新说明
1. 新增 - 重构系统插件的结构。允许向开源社区 PR 系统插件,具体可见: [如何向 FastGPT 社区提交系统插件](https://fael3z0zfze.feishu.cn/wiki/ERZnw9R26iRRG0kXZRec6WL9nwh)。
2. 新增 - DuckDuckGo 系统插件。
3. 优化 - 节点图标
4. 优化 - 对话框引用增加额外复制案件,便于复制。增加引用内容折叠
5. 修复 - Permission 表声明问题
6. 修复 - 并行执行节点,运行时间未正确记录
7. 修复 - 简易模式,首次进入,无法正确获取知识库配置
8. 修复 - Log level 配置
3. 新增 - 修改变量填写方式。提示词输入框以以及工作流中所有 Textarea 输入框,支持输入 / 唤起变量选择,可直接选择所有上游输出值,无需动态引入
4. 优化 - 移动端快速切换应用交互
5. 优化 - 节点图标
6. 优化 - 对话框引用增加额外复制案件,便于复制。增加引用内容折叠
7. 优化 - 对话框底部增加复制,简便复制交互,无需滚动到消息开头
8. 优化 - OpenAI sdk 升级,并自定义了 whisper 模型接口(未仔细查看 sdk 实现,但 sdk 中 whisper 接口,似乎无法适配一般 fastapi 接口)
9. 修复 - Permission 表声明问题。
10. 修复 - 并行执行节点,运行时间未正确记录。
11. 修复 - 简易模式,首次进入,无法正确获取知识库配置。
12. 修复 - Log debug level 配置无效。

View File

@@ -11,7 +11,7 @@
"jschardet": "3.1.1",
"nanoid": "^4.0.1",
"next": "14.2.5",
"openai": "4.28.0",
"openai": "4.53.0",
"openapi-types": "^12.1.3",
"timezones-list": "^3.0.2"
},

View File

@@ -7,7 +7,7 @@ import { cloneDeep } from 'lodash';
import { WorkerNameEnum, runWorker } from '@fastgpt/service/worker/utils';
// Run in main thread
const staticPluginList = ['getTime', 'fetchUrl'];
const staticPluginList = ['getTime', 'fetchUrl', 'Doc2X', 'Doc2X/URLPDF2text', 'Doc2X/URLImg2text'];
// Run in worker thread (Have npm packages)
const packagePluginList = [
'mathExprVal',
@@ -15,10 +15,7 @@ const packagePluginList = [
'duckduckgo/search',
'duckduckgo/searchImg',
'duckduckgo/searchNews',
'duckduckgo/searchVideo',
'Doc2X',
'Doc2X/URLPDF2text',
'Doc2X/URLImg2text'
'duckduckgo/searchVideo'
];
const list = [...staticPluginList, ...packagePluginList];

View File

@@ -1,8 +1,8 @@
{
"author": "",
"version": "486",
"version": "488",
"name": "Doc2X 图像(URL)识别",
"avatar": "/imgs/workflow/textEditor.svg",
"avatar": "plugins/doc2x",
"intro": "将传入的图片(URL)发送至Doc2X进行解析返回带LaTeX公式的markdown格式的文本",
"showStatus": true,
"weight": 10,
@@ -26,9 +26,7 @@
"version": "481",
"inputs": [
{
"renderTypeList": [
"input"
],
"renderTypeList": ["input"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
@@ -36,13 +34,11 @@
"label": "apikey",
"description": "Doc2X的验证密匙对于个人用户可以从Doc2X官网 - 个人信息 - 身份令牌获得",
"required": true,
"toolDescription": "Doc2X的验证密匙对于个人用户可以从Doc2X官网 - 个人信息 - 身份令牌获得",
"toolDescription": "",
"defaultValue": ""
},
{
"renderTypeList": [
"reference"
],
"renderTypeList": ["reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
@@ -53,9 +49,7 @@
"toolDescription": "待处理图片的URL"
},
{
"renderTypeList": [
"switch"
],
"renderTypeList": ["switch"],
"selectedTypeIndex": 0,
"valueType": "boolean",
"canEdit": true,
@@ -63,13 +57,11 @@
"label": "img_correction",
"description": "是否启用图形矫正功能",
"required": true,
"toolDescription": "是否启用图形矫正功能",
"toolDescription": "",
"defaultValue": false
},
{
"renderTypeList": [
"switch"
],
"renderTypeList": ["switch"],
"selectedTypeIndex": 0,
"valueType": "boolean",
"canEdit": true,
@@ -77,7 +69,7 @@
"label": "formula",
"description": "是否开启纯公式识别(仅适用于图片内容仅有公式时)",
"required": true,
"toolDescription": "是否开启纯公式识别(仅适用于图片内容仅有公式时)",
"toolDescription": "",
"defaultValue": false
}
],
@@ -126,32 +118,22 @@
"version": "481",
"inputs": [
{
"renderTypeList": [
"reference"
],
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "result",
"label": "result",
"description": "处理结果(或者是报错信息)",
"value": [
"zHG5jJBkXmjB",
"xWQuEf50F3mr"
]
"value": ["zHG5jJBkXmjB", "xWQuEf50F3mr"]
},
{
"renderTypeList": [
"reference"
],
"renderTypeList": ["reference"],
"valueType": "boolean",
"canEdit": true,
"key": "success",
"label": "success",
"description": "是否处理成功",
"value": [
"zHG5jJBkXmjB",
"m6CJJj7GFud5"
]
"value": ["zHG5jJBkXmjB", "m6CJJj7GFud5"]
}
],
"outputs": []
@@ -171,9 +153,7 @@
"inputs": [
{
"key": "system_addInputParam",
"renderTypeList": [
"addInputParam"
],
"renderTypeList": ["addInputParam"],
"valueType": "dynamic",
"label": "",
"required": false,
@@ -201,9 +181,7 @@
},
{
"key": "system_httpMethod",
"renderTypeList": [
"custom"
],
"renderTypeList": ["custom"],
"valueType": "string",
"label": "",
"value": "POST",
@@ -211,9 +189,7 @@
},
{
"key": "system_httpReqUrl",
"renderTypeList": [
"hidden"
],
"renderTypeList": ["hidden"],
"valueType": "string",
"label": "",
"description": "core.module.input.description.Http Request Url",
@@ -223,9 +199,7 @@
},
{
"key": "system_httpHeader",
"renderTypeList": [
"custom"
],
"renderTypeList": ["custom"],
"valueType": "any",
"value": [],
"label": "",
@@ -235,9 +209,7 @@
},
{
"key": "system_httpParams",
"renderTypeList": [
"hidden"
],
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
@@ -245,18 +217,14 @@
},
{
"key": "system_httpJsonBody",
"renderTypeList": [
"hidden"
],
"renderTypeList": ["hidden"],
"valueType": "any",
"value": "{\n \"apikey\": \"{{apikey}}\",\n \"url\": \"{{url}}\",\n \"img_correction\": \"{{img_correction}}\",\n \"formula\": \"{{img_correction}}\"\n}",
"label": "",
"required": false
},
{
"renderTypeList": [
"reference"
],
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "apikey",
@@ -282,15 +250,10 @@
"showDefaultValue": true
},
"required": true,
"value": [
"pluginInput",
"apikey"
]
"value": ["pluginInput", "apikey"]
},
{
"renderTypeList": [
"reference"
],
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "url",
@@ -316,15 +279,10 @@
"showDefaultValue": true
},
"required": true,
"value": [
"pluginInput",
"url"
]
"value": ["pluginInput", "url"]
},
{
"renderTypeList": [
"reference"
],
"renderTypeList": ["reference"],
"valueType": "boolean",
"canEdit": true,
"key": "img_correction",
@@ -350,15 +308,10 @@
"showDefaultValue": true
},
"required": true,
"value": [
"pluginInput",
"img_correction"
]
"value": ["pluginInput", "img_correction"]
},
{
"renderTypeList": [
"reference"
],
"renderTypeList": ["reference"],
"valueType": "boolean",
"canEdit": true,
"key": "formula",
@@ -384,10 +337,7 @@
"showDefaultValue": true
},
"required": true,
"value": [
"pluginInput",
"formula"
]
"value": ["pluginInput", "formula"]
}
],
"outputs": [

View File

@@ -1,8 +1,8 @@
{
"author": "",
"version": "486",
"version": "488",
"name": "Doc2X PDF文件(URL)识别",
"avatar": "/imgs/workflow/textEditor.svg",
"avatar": "plugins/doc2x",
"intro": "将传入的PDF文件(URL)发送至Doc2X进行解析返回带LaTeX公式的markdown格式的文本",
"showStatus": true,
"weight": 10,
@@ -26,9 +26,7 @@
"version": "481",
"inputs": [
{
"renderTypeList": [
"input"
],
"renderTypeList": ["input"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
@@ -36,13 +34,11 @@
"label": "apikey",
"description": "Doc2X的验证密匙对于个人用户可以从Doc2X官网 - 个人信息 - 身份令牌获得",
"required": true,
"toolDescription": "Doc2X的验证密匙对于个人用户可以从Doc2X官网 - 个人信息 - 身份令牌获得",
"toolDescription": "",
"defaultValue": ""
},
{
"renderTypeList": [
"reference"
],
"renderTypeList": ["reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
@@ -53,9 +49,7 @@
"toolDescription": "待处理PDF文件的URL"
},
{
"renderTypeList": [
"switch"
],
"renderTypeList": ["switch"],
"selectedTypeIndex": 0,
"valueType": "boolean",
"canEdit": true,
@@ -63,7 +57,7 @@
"label": "ocr",
"description": "是否开启对PDF文件内图片的OCR识别建议开启",
"required": true,
"toolDescription": "是否开启对PDF文件内图片的OCR识别建议开启",
"toolDescription": "",
"defaultValue": true
}
],
@@ -105,32 +99,22 @@
"version": "481",
"inputs": [
{
"renderTypeList": [
"reference"
],
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "result",
"label": "result",
"description": "处理结果(或者是报错信息)",
"value": [
"zHG5jJBkXmjB",
"xWQuEf50F3mr"
]
"value": ["zHG5jJBkXmjB", "xWQuEf50F3mr"]
},
{
"renderTypeList": [
"reference"
],
"renderTypeList": ["reference"],
"valueType": "boolean",
"canEdit": true,
"key": "success",
"label": "success",
"description": "是否处理成功",
"value": [
"zHG5jJBkXmjB",
"m6CJJj7GFud5"
]
"value": ["zHG5jJBkXmjB", "m6CJJj7GFud5"]
}
],
"outputs": []
@@ -150,9 +134,7 @@
"inputs": [
{
"key": "system_addInputParam",
"renderTypeList": [
"addInputParam"
],
"renderTypeList": ["addInputParam"],
"valueType": "dynamic",
"label": "",
"required": false,
@@ -180,9 +162,7 @@
},
{
"key": "system_httpMethod",
"renderTypeList": [
"custom"
],
"renderTypeList": ["custom"],
"valueType": "string",
"label": "",
"value": "POST",
@@ -190,9 +170,7 @@
},
{
"key": "system_httpReqUrl",
"renderTypeList": [
"hidden"
],
"renderTypeList": ["hidden"],
"valueType": "string",
"label": "",
"description": "core.module.input.description.Http Request Url",
@@ -202,9 +180,7 @@
},
{
"key": "system_httpHeader",
"renderTypeList": [
"custom"
],
"renderTypeList": ["custom"],
"valueType": "any",
"value": [],
"label": "",
@@ -214,9 +190,7 @@
},
{
"key": "system_httpParams",
"renderTypeList": [
"hidden"
],
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
@@ -224,18 +198,14 @@
},
{
"key": "system_httpJsonBody",
"renderTypeList": [
"hidden"
],
"renderTypeList": ["hidden"],
"valueType": "any",
"value": "{\n \"apikey\": \"{{apikey}}\",\n \"url\": \"{{url}}\",\n \"ocr\": \"{{ocr}}\"\n}",
"label": "",
"required": false
},
{
"renderTypeList": [
"reference"
],
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "apikey",
@@ -261,15 +231,10 @@
"showDefaultValue": true
},
"required": true,
"value": [
"pluginInput",
"apikey"
]
"value": ["pluginInput", "apikey"]
},
{
"renderTypeList": [
"reference"
],
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "url",
@@ -295,15 +260,10 @@
"showDefaultValue": true
},
"required": true,
"value": [
"pluginInput",
"url"
]
"value": ["pluginInput", "url"]
},
{
"renderTypeList": [
"reference"
],
"renderTypeList": ["reference"],
"valueType": "boolean",
"canEdit": true,
"key": "ocr",
@@ -329,10 +289,7 @@
"showDefaultValue": true
},
"required": true,
"value": [
"pluginInput",
"formula"
]
"value": ["pluginInput", "formula"]
}
],
"outputs": [

View File

@@ -1,8 +1,8 @@
{
"author": "",
"version": "486",
"version": "488",
"name": "Doc2X服务",
"avatar": "/imgs/workflow/textEditor.svg",
"avatar": "plugins/doc2x",
"intro": "传入的URL形式的图片或PDF文件发送至Doc2X进行解析返回带LaTeX公式的markdown格式的文本。",
"showStatus": true,
"weight": 10,

View File

@@ -0,0 +1,30 @@
import fs from 'fs';
import { getAxiosConfig } from '../config';
import axios from 'axios';
import FormData from 'form-data';
export const aiTranscriptions = async ({
model,
fileStream
}: {
model: string;
fileStream: fs.ReadStream;
}) => {
const data = new FormData();
data.append('model', model);
data.append('file', fileStream);
const aiAxiosConfig = getAxiosConfig();
const { data: result } = await axios<{ text: string }>({
method: 'post',
baseURL: aiAxiosConfig.baseUrl,
url: '/audio/transcriptions',
headers: {
Authorization: aiAxiosConfig.authorization,
...data.getHeaders()
},
data: data
});
return result;
};

View File

@@ -21,3 +21,16 @@ export const getAIApi = (props?: {
maxRetries: 2
});
};
export const getAxiosConfig = (props?: { userKey?: UserModelSchema['openaiAccount'] }) => {
const { userKey } = props || {};
const baseUrl =
userKey?.baseUrl || global?.systemEnv?.oneapiUrl || process.env.ONEAPI_URL || openaiBaseUrl;
const apiKey = userKey?.key || global?.systemEnv?.chatApiKey || process.env.CHAT_API_KEY || '';
return {
baseUrl,
authorization: `Bearer ${apiKey}`
};
};

View File

@@ -55,7 +55,6 @@ import { surrenderProcess } from '../../../common/system/tools';
import { dispatchRunCode } from './code/run';
import { dispatchTextEditor } from './tools/textEditor';
import { dispatchCustomFeedback } from './tools/customFeedback';
import { ReferenceValueProps } from '@fastgpt/global/core/workflow/type/io';
const callbackMap: Record<FlowNodeTypeEnum, Function> = {
[FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart,

View File

@@ -16,6 +16,7 @@
"domino-ext": "^2.1.4",
"encoding": "^0.1.13",
"file-type": "^19.0.0",
"form-data": "^4.0.0",
"iconv-lite": "^0.6.3",
"joplin-turndown-plugin-gfm": "^1.0.12",
"json5": "^2.2.3",

View File

@@ -29,6 +29,7 @@ export const iconPaths = {
'common/gitLight': () => import('./icons/common/gitLight.svg'),
'common/googleFill': () => import('./icons/common/googleFill.svg'),
'common/importLight': () => import('./icons/common/importLight.svg'),
'common/info': () => import('./icons/common/info.svg'),
'common/inviteLight': () => import('./icons/common/inviteLight.svg'),
'common/language/en': () => import('./icons/common/language/en.svg'),
'common/language/zh': () => import('./icons/common/language/zh.svg'),
@@ -97,24 +98,24 @@ export const iconPaths = {
'core/app/variable/textarea': () => import('./icons/core/app/variable/textarea.svg'),
'core/chat/QGFill': () => import('./icons/core/chat/QGFill.svg'),
'core/chat/cancelSpeak': () => import('./icons/core/chat/cancelSpeak.svg'),
'core/chat/chevronSelector': () => import('./icons/core/chat/chevronSelector.svg'),
'core/chat/sideLine': () => import('./icons/core/chat/sideLine.svg'),
'core/chat/chatFill': () => import('./icons/core/chat/chatFill.svg'),
'core/chat/chatLight': () => import('./icons/core/chat/chatLight.svg'),
'core/chat/chatModelTag': () => import('./icons/core/chat/chatModelTag.svg'),
'core/chat/chevronDown': () => import('./icons/core/chat/chevronDown.svg'),
'core/chat/chevronSelector': () => import('./icons/core/chat/chevronSelector.svg'),
'core/chat/chevronUp': () => import('./icons/core/chat/chevronUp.svg'),
'core/chat/export/pdf': () => import('./icons/core/chat/export/pdf.svg'),
'core/chat/feedback/badLight': () => import('./icons/core/chat/feedback/badLight.svg'),
'core/chat/feedback/goodLight': () => import('./icons/core/chat/feedback/goodLight.svg'),
'core/chat/fileSelect': () => import('./icons/core/chat/fileSelect.svg'),
'core/chat/finishSpeak': () => import('./icons/core/chat/finishSpeak.svg'),
'core/chat/quoteFill': () => import('./icons/core/chat/quoteFill.svg'),
'core/chat/chevronDown': () => import('./icons/core/chat/chevronDown.svg'),
'core/chat/chevronUp': () => import('./icons/core/chat/chevronUp.svg'),
'core/chat/quoteSign': () => import('./icons/core/chat/quoteSign.svg'),
'core/chat/recordFill': () => import('./icons/core/chat/recordFill.svg'),
'core/chat/sendFill': () => import('./icons/core/chat/sendFill.svg'),
'core/chat/sendLight': () => import('./icons/core/chat/sendLight.svg'),
'core/chat/setTopLight': () => import('./icons/core/chat/setTopLight.svg'),
'core/chat/sideLine': () => import('./icons/core/chat/sideLine.svg'),
'core/chat/speaking': () => import('./icons/core/chat/speaking.svg'),
'core/chat/stopSpeech': () => import('./icons/core/chat/stopSpeech.svg'),
'core/dataset/commonDataset': () => import('./icons/core/dataset/commonDataset.svg'),
@@ -250,6 +251,7 @@ export const iconPaths = {
'phoneTabbar/me': () => import('./icons/phoneTabbar/me.svg'),
'phoneTabbar/tool': () => import('./icons/phoneTabbar/tool.svg'),
'phoneTabbar/toolFill': () => import('./icons/phoneTabbar/toolFill.svg'),
'plugins/doc2x': () => import('./icons/plugins/doc2x.svg'),
'plugins/textEditor': () => import('./icons/plugins/textEditor.svg'),
'price/bg': () => import('./icons/price/bg.svg'),
'price/right': () => import('./icons/price/right.svg'),

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 24" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.8999 3.51941C8.21617 3.51941 4.41928 7.3163 4.41928 12C4.41928 16.6837 8.21617 20.4806 12.8999 20.4806C17.5836 20.4806 21.3805 16.6837 21.3805 12C21.3805 7.3163 17.5836 3.51941 12.8999 3.51941ZM2.41928 12C2.41928 6.21173 7.1116 1.51941 12.8999 1.51941C18.6881 1.51941 23.3805 6.21173 23.3805 12C23.3805 17.7883 18.6881 22.4806 12.8999 22.4806C7.1116 22.4806 2.41928 17.7883 2.41928 12ZM11.8999 8.20776C11.8999 7.65548 12.3476 7.20776 12.8999 7.20776H12.9094C13.4616 7.20776 13.9094 7.65548 13.9094 8.20776C13.9094 8.76005 13.4616 9.20776 12.9094 9.20776H12.8999C12.3476 9.20776 11.8999 8.76005 11.8999 8.20776ZM12.8999 11C13.4522 11 13.8999 11.4477 13.8999 12V15.7922C13.8999 16.3445 13.4522 16.7922 12.8999 16.7922C12.3476 16.7922 11.8999 16.3445 11.8999 15.7922V12C11.8999 11.4477 12.3476 11 12.8999 11Z" />
</svg>

After

Width:  |  Height:  |  Size: 944 B

View File

@@ -1,3 +1,3 @@
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.28082 5.72828C6.95538 6.05372 6.95538 6.58136 7.28082 6.90679C7.60626 7.23223 8.1339 7.23223 8.45933 6.90679L10.5 4.86615L12.5406 6.90679C12.8661 7.23223 13.3937 7.23223 13.7191 6.90679C14.0446 6.58136 14.0446 6.05372 13.7191 5.72828L11.0892 3.09839C10.7638 2.77295 10.2362 2.77295 9.91072 3.09839L7.28082 5.72828ZM7.28082 13.0893C6.95538 13.4147 6.95538 13.9424 7.28082 14.2678L9.91072 16.8977C9.9514 16.9384 9.99524 16.974 10.0414 17.0045C10.3649 17.2181 10.8045 17.1825 11.0892 16.8977L13.7191 14.2678C14.0446 13.9424 14.0446 13.4147 13.7191 13.0893C13.3937 12.7639 12.8661 12.7639 12.5406 13.0893L10.5 15.1299L8.45933 13.0893C8.1339 12.7639 7.60626 12.7639 7.28082 13.0893Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 836 B

After

Width:  |  Height:  |  Size: 814 B

View File

@@ -0,0 +1,6 @@
<svg viewBox="0 0 42 42" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="28.6606" y="8.44495" width="6.92163" height="12.0376" rx="3.46082" transform="rotate(45 28.6606 8.44495)" fill="#7748F9"/>
<rect x="16.957" y="20.1488" width="6.92163" height="12.0376" rx="3.46082" transform="rotate(45 16.957 20.1488)" fill="#7748F9"/>
<rect x="20.1489" y="25.0432" width="6.92163" height="12.0376" rx="3.46082" transform="rotate(-45 20.1489 25.0432)" fill="#BFABFB"/>
<rect x="8.44482" y="13.3394" width="6.92163" height="12.0376" rx="3.46082" transform="rotate(-45 8.44482 13.3394)" fill="#BFABFB"/>
</svg>

After

Width:  |  Height:  |  Size: 609 B

View File

@@ -52,6 +52,7 @@ const LightRowTabs = <ValueType = string,>({
fontSize={sizeMap.fontSize}
overflowX={'auto'}
userSelect={'none'}
display={'inline-grid'}
{...props}
>
{list.map((item) => (

View File

@@ -94,8 +94,7 @@ export default function VariableLabelPickerPlugin({
<Box
bg={'white'}
boxShadow={'lg'}
borderWidth={'1px'}
borderColor={'borderColor.base'}
border={'base'}
p={2}
borderRadius={'md'}
position={'absolute'}

View File

@@ -29,9 +29,9 @@ export default function VariableLabel({
<span>
<Avatar
src={nodeAvatar as any}
w={'16px'}
w={'1rem'}
mr={1}
borderRadius={'2.8px'}
borderRadius={'xs'}
display={'inline-flex'}
verticalAlign={'middle'}
mb={'3px'}

View File

@@ -1,23 +1,19 @@
{
"add_new": "Add new",
"App": "App",
"all_apps": "All Apps",
"click_to_resume": "Resume",
"code_editor": "Code edit",
"Export": "Export",
"field_name": "Name",
"Folder": "Folder",
"is_open": "Opened",
"Login": "Login",
"Move": "Move",
"Name": "Name",
"new_create": "Create New",
"no_data": "No data",
"Rename": "Rename",
"Resume": "Resume",
"Running": "Running",
"UnKnow": "Unknown",
"Warning": "Warning",
"add_new": "Add new",
"common": {
"Action": "Action",
"Add": "Add",
@@ -294,7 +290,7 @@
},
"tip": {
"Add a intro to app": "Come and give the app an introduction~",
"chatNodeSystemPromptTip": "Model fixed guide words, by adjusting this content, you can guide the model's chat direction. This content will be fixed at the beginning of the context. Variables can be used, for example, {{language}}\nIf a knowledge base is associated, you can also guide the model when to call the knowledge base search by appropriately describing it. For example:\nYou are the assistant for the movie 'Interstellar', when the user asks about content related to 'Interstellar', please search the knowledge base and combine the search results for answers.",
"chatNodeSystemPromptTip": "The model has a fixed guide word. By adjusting this content, you can guide the model in the chat direction. \nThe content will be anchored at the beginning of the context. \nVariables can be selected via input/insert\n\nIf a knowledge base is associated, you can also use appropriate descriptions to guide the model when to call the knowledge base search. \nFor example:\n\nYou are an assistant in the movie \"Interstellar\". When users ask about content related to \"Interstellar\", please search the knowledge base and answer based on the search results.",
"variableTip": "You can ask the user to fill in some content as specific variables for this round of conversation before the conversation starts. This module is located after the opening guide.\nVariables can be injected into other module string type inputs through the form of {{variable key}}, such as: prompt words, limiting words, etc.",
"welcomeTextTip": "Send an initial content before each conversation starts. Supports standard Markdown syntax, additional tags available:\n[Shortcut key]: User can click to send the question directly"
},
@@ -902,7 +898,9 @@
"overSize": "Team members exceed the limit"
}
},
"field_name": "Name",
"invalid_variable": "Invalid variable",
"is_open": "Opened",
"navbar": {
"Account": "Account",
"Chat": "Chat",
@@ -910,6 +908,8 @@
"Studio": "Studio",
"Tools": "Tools"
},
"new_create": "Create New",
"no_data": "No data",
"permission": {
"Collaborator": "collaborator",
"Default permission": "Default permission",
@@ -1148,6 +1148,7 @@
"Quote Content Tip": "You can customize the structure of the quote content to better adapt to different scenarios. You can use some variables for template configuration:\n{{q}} - search content, {{a}} - expected content, {{source}} - source, {{sourceId}} - source file name, {{index}} - the nth quote, they are all optional, here are the default values:\n{{default}}",
"Quote Prompt Tip": "You can use {{quote}} to insert the quote content template, and use {{question}} to insert the question. Here are the default values:\n{{default}}"
},
"textarea_variable_picker_tip": "Input / to select variables",
"unusable_variable": "no usable variable",
"user": {
"Account": "Account",

View File

@@ -28,5 +28,5 @@
"Error": "Error"
},
"tool_input": "Tool",
"variable_picker_tips": "tips: enter node name or variable name to search"
"variable_picker_tips": "enter node name or variable name to search"
}

View File

@@ -1,23 +1,19 @@
{
"add_new": "新增",
"App": "应用",
"all_apps": "全部应用",
"click_to_resume": "点击恢复",
"code_editor": "代码编辑",
"Export": "导出",
"field_name": "字段名",
"Folder": "文件夹",
"is_open": "是否开启",
"Login": "登录",
"Move": "移动",
"Name": "名称",
"new_create": "新建",
"no_data": "暂无数据",
"Rename": "重命名",
"Resume": "恢复",
"Running": "运行中",
"UnKnow": "未知",
"Warning": "提示",
"add_new": "新增",
"common": {
"Action": "操作",
"Add": "添加",
@@ -294,7 +290,7 @@
},
"tip": {
"Add a intro to app": "快来给应用一个介绍~",
"chatNodeSystemPromptTip": "模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可使用变量,例如 {{language}}\n如果关联了知识库你还可以通过适当的描述来引导模型何时去调用知识库搜索。例如\n你是电影《星际穿越》的助手当用户询问与《星际穿越》相关的内容时请搜索知识库并结合搜索结果进行回答。",
"chatNodeSystemPromptTip": "模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可通过输入 / 插入选择变量\n如果关联了知识库你还可以通过适当的描述来引导模型何时去调用知识库搜索。例如\n你是电影《星际穿越》的助手当用户询问与《星际穿越》相关的内容时请搜索知识库并结合搜索结果进行回答。",
"variableTip": "可以在对话开始前,要求用户填写一些内容作为本轮对话的特定变量。该模块位于开场引导之后。\n变量可以通过 {{变量key}} 的形式注入到其他模块 string 类型的输入中,例如:提示词、限定词等",
"welcomeTextTip": "每次对话开始前,发送一个初始内容。支持标准 Markdown 语法,可使用的额外标记:\n[快捷按键]:用户点击后可以直接发送该问题"
},
@@ -902,7 +898,9 @@
"overSize": "团队成员超出上限"
}
},
"field_name": "字段名",
"invalid_variable": "无效变量",
"is_open": "是否开启",
"navbar": {
"Account": "账号",
"Chat": "聊天",
@@ -910,6 +908,8 @@
"Studio": "工作台",
"Tools": "工具"
},
"new_create": "新建",
"no_data": "暂无数据",
"permission": {
"Collaborator": "协作者",
"Default permission": "默认权限",
@@ -1148,6 +1148,7 @@
"Quote Content Tip": "可以自定义引用内容的结构,以更好的适配不同场景。可以使用一些变量来进行模板配置:\n{{q}} - 检索内容,{{a}} - 预期内容,{{source}} - 来源,{{sourceId}} - 来源文件名,{{index}} - 第 n 个引用,他们都是可选的,下面是默认值:\n{{default}}",
"Quote Prompt Tip": "可以用 {{quote}} 来插入引用内容模板,使用 {{question}} 来插入问题。下面是默认值:\n{{default}}"
},
"textarea_variable_picker_tip": "输入 / 可选择变量",
"unusable_variable": "无可用变量",
"user": {
"Account": "账号",

View File

@@ -28,5 +28,5 @@
"Error": "错误信息"
},
"tool_input": "工具参数",
"variable_picker_tips": "tips: 可输入节点名或变量名搜索"
"variable_picker_tips": "可输入节点名或变量名搜索"
}

63
pnpm-lock.yaml generated
View File

@@ -63,8 +63,8 @@ importers:
specifier: 14.2.5
version: 14.2.5(@babel/core@7.24.9)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
openai:
specifier: 4.28.0
version: 4.28.0(encoding@0.1.13)
specifier: 4.53.0
version: 4.53.0(encoding@0.1.13)
openapi-types:
specifier: ^12.1.3
version: 12.1.3
@@ -151,6 +151,9 @@ importers:
file-type:
specifier: ^19.0.0
version: 19.1.1
form-data:
specifier: ^4.0.0
version: 4.0.0
iconv-lite:
specifier: ^0.6.3
version: 0.6.3
@@ -577,6 +580,9 @@ importers:
'@nestjs/swagger':
specifier: ^7.3.1
version: 7.4.0(@fastify/static@7.0.4)(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)
dayjs:
specifier: ^1.11.7
version: 1.11.11
fastify:
specifier: ^4.27.0
version: 4.28.1
@@ -592,6 +598,9 @@ importers:
rxjs:
specifier: ^7.8.1
version: 7.8.1
tiktoken:
specifier: ^1.0.15
version: 1.0.15
devDependencies:
'@nestjs/cli':
specifier: ^10.0.0
@@ -3864,9 +3873,6 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
base-64@0.1.0:
resolution: {integrity: sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==}
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
@@ -4027,9 +4033,6 @@ packages:
chardet@0.7.0:
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
charenc@0.0.2:
resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==}
check-error@1.0.3:
resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
@@ -4303,9 +4306,6 @@ packages:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
crypt@0.0.2:
resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
css-box-model@1.2.1:
resolution: {integrity: sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==}
@@ -4654,9 +4654,6 @@ packages:
resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
engines: {node: '>=0.3.1'}
digest-fetch@1.3.0:
resolution: {integrity: sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==}
dingbat-to-unicode@1.0.1:
resolution: {integrity: sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==}
@@ -5619,9 +5616,6 @@ packages:
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
engines: {node: '>= 0.4'}
is-buffer@1.1.6:
resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==}
is-buffer@2.0.5:
resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==}
engines: {node: '>=4'}
@@ -6287,9 +6281,6 @@ packages:
markdown-table@3.0.3:
resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==}
md5@2.3.0:
resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
mdast-util-definitions@5.1.2:
resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==}
@@ -6849,8 +6840,8 @@ packages:
resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
engines: {node: '>=12'}
openai@4.28.0:
resolution: {integrity: sha512-JM8fhcpmpGN0vrUwGquYIzdcEQHtFuom6sRCbbCM6CfzZXNuRk33G7KfeRAIfnaCxSpzrP5iHtwJzIm6biUZ2Q==}
openai@4.53.0:
resolution: {integrity: sha512-XoMaJsSLuedW5eoMEMmZbdNoXgML3ujcU5KfwRnC6rnbmZkHE2Q4J/SArwhqCxQRqJwHnQUj1LpiROmKPExZJA==}
hasBin: true
openapi-types@12.1.3:
@@ -12661,8 +12652,6 @@ snapshots:
balanced-match@1.0.2: {}
base-64@0.1.0: {}
base64-js@1.5.1: {}
big.js@5.2.2: {}
@@ -12849,8 +12838,6 @@ snapshots:
chardet@0.7.0: {}
charenc@0.0.2: {}
check-error@1.0.3:
dependencies:
get-func-name: 2.0.2
@@ -13129,8 +13116,6 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
crypt@0.0.2: {}
css-box-model@1.2.1:
dependencies:
tiny-invariant: 1.3.3
@@ -13519,11 +13504,6 @@ snapshots:
diff@5.2.0: {}
digest-fetch@1.3.0:
dependencies:
base-64: 0.1.0
md5: 2.3.0
dingbat-to-unicode@1.0.1: {}
dir-glob@3.0.1:
@@ -13826,7 +13806,7 @@ snapshots:
eslint: 8.56.0
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0)
eslint-plugin-jsx-a11y: 6.9.0(eslint@8.56.0)
eslint-plugin-react: 7.34.4(eslint@8.56.0)
eslint-plugin-react-hooks: 4.6.2(eslint@8.56.0)
@@ -13850,7 +13830,7 @@ snapshots:
enhanced-resolve: 5.17.0
eslint: 8.56.0
eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0)
fast-glob: 3.3.2
get-tsconfig: 4.7.5
is-core-module: 2.14.0
@@ -13872,7 +13852,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0):
eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0):
dependencies:
array-includes: 3.1.8
array.prototype.findlastindex: 1.2.5
@@ -14838,8 +14818,6 @@ snapshots:
call-bind: 1.0.7
has-tostringtag: 1.0.2
is-buffer@1.1.6: {}
is-buffer@2.0.5: {}
is-callable@1.2.7: {}
@@ -15689,12 +15667,6 @@ snapshots:
markdown-table@3.0.3: {}
md5@2.3.0:
dependencies:
charenc: 0.0.2
crypt: 0.0.2
is-buffer: 1.1.6
mdast-util-definitions@5.1.2:
dependencies:
'@types/mdast': 3.0.15
@@ -16442,13 +16414,12 @@ snapshots:
dependencies:
mimic-fn: 4.0.0
openai@4.28.0(encoding@0.1.13):
openai@4.53.0(encoding@0.1.13):
dependencies:
'@types/node': 18.19.40
'@types/node-fetch': 2.6.11
abort-controller: 3.0.0
agentkeepalive: 4.5.0
digest-fetch: 1.3.0
form-data-encoder: 1.7.2
formdata-node: 4.4.1
node-fetch: 2.7.0(encoding@0.1.13)

View File

@@ -35,22 +35,22 @@
"formidable": "^2.1.1",
"framer-motion": "9.1.7",
"hyperdown": "^2.4.29",
"i18next": "23.11.5",
"immer": "^9.0.19",
"js-yaml": "^4.1.0",
"json5": "^2.2.3",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21",
"mermaid": "^10.2.3",
"nanoid": "^4.0.1",
"next": "14.2.5",
"json5": "^2.2.3",
"next-i18next": "15.3.0",
"nextjs-node-loader": "^1.1.5",
"nprogress": "^0.2.0",
"react": "18.3.1",
"react-day-picker": "^8.7.1",
"react-dom": "18.3.1",
"react-hook-form": "7.43.1",
"i18next": "23.11.5",
"next-i18next": "15.3.0",
"react-i18next": "14.1.2",
"react-markdown": "^8.0.7",
"react-syntax-highlighter": "^15.5.0",

View File

@@ -0,0 +1,16 @@
import { Box, HStack, StackProps } from '@chakra-ui/react';
import React from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'next-i18next';
const VariableTip = (props: StackProps) => {
const { t } = useTranslation();
return (
<HStack fontSize={'xs'} spacing={1} {...props}>
<MyIcon name={'common/info'} w={'0.9rem'} transform={'translateY(1px)'} />
<Box>{t('common:textarea_variable_picker_tip')}</Box>
</HStack>
);
};
export default VariableTip;

View File

@@ -127,6 +127,7 @@ const DatasetParamsModal = ({
>
<ModalBody flex={'auto'} overflow={'auto'}>
<LightRowTabs<SearchSettingTabEnum>
width={'100%'}
mb={3}
list={[
{

View File

@@ -299,6 +299,8 @@ const VariableEdit = ({
</Button>
<Button
onClick={handleSubmitEdit(({ variable }) => {
variable.key = variable.key.trim();
// check select
if (variable.type === VariableInputEnum.select) {
const enums = variable.enums.filter((item) => item.value);

View File

@@ -157,6 +157,7 @@ export const ResponseBox = React.memo(function ResponseBox({
{!hideTabs && (
<Box>
<LightRowTabs
w={'100%'}
list={list}
value={currentTab}
inlineStyles={{ pt: 0 }}

View File

@@ -3,15 +3,14 @@ import { jsonRes } from '@fastgpt/service/common/response';
import { getUploadModel } from '@fastgpt/service/common/file/multer';
import { removeFilesByPaths } from '@fastgpt/service/common/file/utils';
import fs from 'fs';
import { getAIApi } from '@fastgpt/service/core/ai/config';
import { pushWhisperUsage } from '@/service/support/wallet/usage/push';
import { authChatCert } from '@/service/support/permission/auth/chat';
import { MongoApp } from '@fastgpt/service/core/app/schema';
import { OutLinkChatAuthProps } from '@fastgpt/global/support/permission/chat';
import { NextAPI } from '@/service/middleware/entry';
import { aiTranscriptions } from '@fastgpt/service/core/ai/audio/transcriptions';
const upload = getUploadModel({
maxSize: 2
maxSize: 20
});
async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -45,6 +44,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
// auth role
const { teamId, tmbId } = await authChatCert({ req, authToken: true });
// auth app
// const app = await MongoApp.findById(appId, 'modules').lean();
// if (!app) {
@@ -54,11 +54,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
// throw new Error('Whisper is not open in the app');
// }
const ai = getAIApi();
const result = await ai.audio.transcriptions.create({
file: fs.createReadStream(file.path),
model: global.whisperModel.model
const result = await aiTranscriptions({
model: global.whisperModel.model,
fileStream: fs.createReadStream(file.path)
});
pushWhisperUsage({

View File

@@ -34,6 +34,7 @@ import { useContextSelector } from 'use-context-selector';
import { AppContext } from '@/pages/app/detail/components/context';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import VariableTip from '@/components/common/Textarea/MyTextarea/VariableTip';
const DatasetSelectModal = dynamic(() => import('@/components/core/app/DatasetSelectModal'));
const DatasetParamsModal = dynamic(() => import('@/components/core/app/DatasetParamsModal'));
@@ -57,7 +58,8 @@ const LabelStyles: BoxProps = {
w: ['60px', '100px'],
whiteSpace: 'nowrap',
flexShrink: 0,
fontSize: 'xs'
fontSize: 'sm',
color: 'myGray.900'
};
const EditForm = ({
@@ -162,10 +164,13 @@ const EditForm = ({
</Box>
</Flex>
<Box mt={3}>
<HStack {...LabelStyles}>
<Box mt={4}>
<HStack {...LabelStyles} w={'100%'}>
<Box>{t('common:core.ai.Prompt')}</Box>
<QuestionTip label={t('common:core.app.tip.chatNodeSystemPromptTip')} />
<Box flex={1} />
<VariableTip color={'myGray.500'} />
</HStack>
<Box mt={1}>
<PromptEditor

View File

@@ -309,6 +309,7 @@ export function RenderHttpProps({
></QuestionTip>
</Flex>
<LightRowTabs<TabEnum>
width={'100%'}
list={[
{ label: <RenderPropsItem text="Params" num={paramsLength} />, value: TabEnum.params },
...(!['GET', 'DELETE'].includes(requestMethods)

View File

@@ -1,7 +1,7 @@
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io.d';
import React, { useCallback, useMemo } from 'react';
import { useTranslation } from 'next-i18next';
import { Box, Flex } from '@chakra-ui/react';
import { Box, Flex, HStack } from '@chakra-ui/react';
import NodeInputSelect from '@fastgpt/web/components/core/workflow/NodeInputSelect';
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
@@ -10,6 +10,10 @@ import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '@/pages/app/detail/components/WorkflowComponents/context';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import MyIcon from '@fastgpt/web/components/common/Icon';
import MyTag from '@fastgpt/web/components/common/Tag/index';
import VariableTip from '@/components/common/Textarea/MyTextarea/VariableTip';
type Props = {
nodeId: string;
@@ -68,6 +72,14 @@ const InputLabel = ({ nodeId, input }: Props) => {
/>
</Box>
)}
{/* Variable picker tip */}
{input.renderTypeList[input.selectedTypeIndex ?? 0] === FlowNodeInputTypeEnum.textarea && (
<>
<Box flex={1} />
<VariableTip transform={'translateY(2px)'} />
</>
)}
</Flex>
);
}, [

View File

@@ -27,20 +27,19 @@ const ChatHeader = ({
chatData,
history,
showHistory,
onRoute2AppDetail,
apps
apps,
onRouteToAppDetail
}: {
history: ChatItemType[];
showHistory?: boolean;
onRoute2AppDetail?: () => void;
apps?: AppListItemType[];
chatData: InitChatResponse;
apps?: AppListItemType[];
onRouteToAppDetail?: () => void;
}) => {
const isPlugin = chatData.app.type === AppTypeEnum.plugin;
const { isPc } = useSystem();
return (
<>
{isPc && isPlugin ? null : (
return isPc && isPlugin ? null : (
<Flex
alignItems={'center'}
px={[3, 5]}
@@ -50,16 +49,14 @@ const ChatHeader = ({
fontSize={'sm'}
>
{isPc ? (
<PcHeader
title={chatData.title}
chatModels={chatData.app.chatModels}
history={history}
/>
<>
<PcHeader title={chatData.title} chatModels={chatData.app.chatModels} history={history} />
<Box flex={1} />
</>
) : (
<MobileHeader
apps={apps}
appId={chatData.appId}
go2AppDetail={onRoute2AppDetail}
name={chatData.app.name}
avatar={chatData.app.avatar}
showHistory={showHistory}
@@ -67,10 +64,8 @@ const ChatHeader = ({
)}
{/* control */}
{!isPlugin && <ToolMenu history={history} />}
{!isPlugin && <ToolMenu history={history} onRouteToAppDetail={onRouteToAppDetail} />}
</Flex>
)}
</>
);
};
@@ -88,7 +83,6 @@ const MobileDrawer = ({
app = 'app'
}
const { t } = useTranslation();
const { isPc } = useSystem();
const router = useRouter();
const isTeamChat = router.pathname === '/chat/team';
const [currentTab, setCurrentTab] = useState<TabEnum>(TabEnum.recently);
@@ -103,8 +97,12 @@ const MobileDrawer = ({
);
}, []);
const { onChangeAppId } = useContextSelector(ChatContext, (v) => v);
const onclickApp = (id: string) => {
onChangeAppId(id);
onCloseDrawer();
};
return (
<>
<Box
position={'absolute'}
top={'45px'}
@@ -126,13 +124,10 @@ const MobileDrawer = ({
background={'white'}
position={'relative'}
>
{!isPc && appId && (
<LightRowTabs<TabEnum>
flex={'1 0 0'}
width={isTeamChat ? '30%' : '60%'}
mr={10}
gap={3}
inlineStyles={{
px: 1
px: 2
}}
list={[
...(isTeamChat
@@ -145,26 +140,22 @@ const MobileDrawer = ({
value={currentTab}
onChange={setCurrentTab}
/>
)}
</Box>
<Box
width={'100vw'}
height={'auto'}
width={'100%'}
minH={'10vh'}
maxH={'60vh'}
overflow={'auto'}
background={'white'}
zIndex={3}
onClick={(e) => e.stopPropagation()}
borderRadius={'0 0 10px 10px'}
position={'relative'}
padding={3}
py={3}
pt={0}
pb={4}
h={'65vh'}
>
{/* history */}
{currentTab === TabEnum.recently && (
<>
<Box px={3} overflow={'auto'} h={'100%'}>
{Array.isArray(apps) &&
apps.map((item) => (
<Flex justify={'center'} key={item._id}>
@@ -180,7 +171,7 @@ const MobileDrawer = ({
color: 'primary.600'
}
: {
onClick: () => onChangeAppId(item._id)
onClick: () => onclickApp(item._id)
})}
>
<Avatar src={item.avatar} w={'24px'} />
@@ -190,42 +181,36 @@ const MobileDrawer = ({
</Flex>
</Flex>
))}
</>
</Box>
)}
{currentTab === TabEnum.app && !isPc && (
<>
{currentTab === TabEnum.app && (
<SelectOneResource
value={appId}
onSelect={(id) => {
if (!id) return;
onChangeAppId(id);
onclickApp(id);
}}
server={getAppList}
/>
</>
)}
</Box>
</Box>
</>
);
};
const MobileHeader = ({
showHistory,
go2AppDetail,
name,
avatar,
appId,
apps
}: {
showHistory?: boolean;
go2AppDetail?: () => void;
avatar: string;
name: string;
apps?: AppListItemType[];
appId: string;
}) => {
const { isPc } = useSystem();
const router = useRouter();
const onOpenSlider = useContextSelector(ChatContext, (v) => v.onOpenSlider);
const { isOpen: isOpenDrawer, onToggle: toggleDrawer, onClose: onCloseDrawer } = useDisclosure();
@@ -237,22 +222,21 @@ const MobileHeader = ({
<MyIcon name={'menu'} w={'20px'} h={'20px'} color={'myGray.900'} onClick={onOpenSlider} />
)}
<Flex px={3} alignItems={'center'} flex={'1 0 0'} w={0} justifyContent={'center'}>
<Avatar src={avatar} w={'16px'} />
<Box ml={1} className="textEllipsis" onClick={go2AppDetail}>
<Flex alignItems={'center'} onClick={toggleDrawer}>
<Avatar src={avatar} w={'1rem'} />
<Box ml={1} className="textEllipsis">
{name}
</Box>
{isShareChat ? null : (
<MyIcon
_active={{ transform: 'scale(0.9)' }}
name={'core/chat/chevronSelector'}
w={'20px'}
h={'20px'}
w={'1.25rem'}
color={isOpenDrawer ? 'primary.600' : 'myGray.900'}
onClick={toggleDrawer}
/>
)}
</Flex>
{!isPc && isOpenDrawer && !isShareChat && (
</Flex>
{isOpenDrawer && !isShareChat && (
<MobileDrawer apps={apps} appId={appId} onCloseDrawer={onCloseDrawer} />
)}
</>
@@ -292,7 +276,6 @@ const PcHeader = ({
</MyTag>
</MyTooltip>
)}
<Box flex={1} />
</>
);
};

View File

@@ -27,7 +27,6 @@ const ChatHistorySlider = ({
appId,
appName,
appAvatar,
apps = [],
confirmClearText,
onDelHistory,
onClearHistory,
@@ -37,7 +36,6 @@ const ChatHistorySlider = ({
appId?: string;
appName: string;
appAvatar: string;
apps?: AppListItemType[];
confirmClearText: string;
onDelHistory: (e: { chatId: string }) => void;
onClearHistory: () => void;
@@ -46,10 +44,9 @@ const ChatHistorySlider = ({
}) => {
const theme = useTheme();
const router = useRouter();
const isTeamChat = router.pathname === '/chat/team';
const isUserChatPage = router.pathname === '/chat';
const { t } = useTranslation();
const { appT } = useI18n();
const { isPc } = useSystem();
const { userInfo } = useUserStore();
@@ -103,7 +100,7 @@ const ChatHistorySlider = ({
whiteSpace={'nowrap'}
>
{isPc && (
<MyTooltip label={canRouteToDetail ? appT('app_detail') : ''} offset={[0, 0]}>
<MyTooltip label={canRouteToDetail ? t('app:app_detail') : ''} offset={[0, 0]}>
<Flex
pt={5}
pb={2}
@@ -136,7 +133,7 @@ const ChatHistorySlider = ({
justify={['space-between', '']}
alignItems={'center'}
>
{!isPc && appId && (
{!isPc && (
<Flex height={'100%'} align={'center'} justify={'center'}>
<MyIcon ml={2} name="core/chat/sideLine" />
<Box ml={2} fontWeight={'bold'}>
@@ -147,8 +144,9 @@ const ChatHistorySlider = ({
<Button
variant={'whitePrimary'}
flex={[appId ? '0 0 auto' : 1, 1]}
flex={['0 0 auto', 1]}
h={'100%'}
px={6}
color={'primary.600'}
borderRadius={'xl'}
leftIcon={<MyIcon name={'core/chat/chatLight'} w={'16px'} />}
@@ -158,7 +156,7 @@ const ChatHistorySlider = ({
{t('common:core.chat.New Chat')}
</Button>
{/* Clear */}
{isPc && (
{isPc && histories.length > 0 && (
<IconButton
ml={3}
h={'100%'}
@@ -288,7 +286,7 @@ const ChatHistorySlider = ({
</Box>
{/* exec */}
{!isPc && appId && !isTeamChat && (
{!isPc && isUserChatPage && (
<Flex
mt={2}
borderTop={theme.borders.base}

View File

@@ -7,7 +7,13 @@ import MyIcon from '@fastgpt/web/components/common/Icon';
import { useRouter } from 'next/router';
import MyMenu from '@fastgpt/web/components/common/MyMenu';
const ToolMenu = ({ history }: { history: ChatItemType[] }) => {
const ToolMenu = ({
history,
onRouteToAppDetail
}: {
history: ChatItemType[];
onRouteToAppDetail?: () => void;
}) => {
const { t } = useTranslation();
const { onExportChat } = useChatBox();
const router = useRouter();
@@ -57,7 +63,20 @@ const ToolMenu = ({ history }: { history: ChatItemType[] }) => {
// onClick: () => onExportChat({ type: 'pdf', history })
// }
]
},
...(onRouteToAppDetail
? [
{
children: [
{
icon: 'core/app/aiLight',
label: t('app:app_detail'),
onClick: onRouteToAppDetail
}
]
}
]
: [])
]}
/>
) : (

View File

@@ -195,7 +195,6 @@ const Chat = ({
);
})(
<ChatHistorySlider
apps={myApps}
confirmClearText={t('common:core.chat.Confirm to clear history')}
appId={appId}
appName={chatData.app.name}
@@ -229,8 +228,8 @@ const Chat = ({
apps={myApps}
chatData={chatData}
history={chatRecords}
onRoute2AppDetail={() => router.push(`/app/detail?appId=${appId}`)}
showHistory
onRouteToAppDetail={() => router.push(`/app/detail?appId=${appId}`)}
/>
{/* chat box */}
@@ -341,7 +340,7 @@ export async function getServerSideProps(context: any) {
props: {
appId: context?.query?.appId || '',
chatId: context?.query?.chatId || '',
...(await serviceSideProps(context, ['file']))
...(await serviceSideProps(context, ['file', 'app']))
}
};
}

View File

@@ -292,7 +292,7 @@ const OutLink = ({ appName, appIntro, appAvatar }: Props) => {
{showHead === '1' ? (
<ChatHeader
chatData={chatData}
history={chatData.history}
history={chatRecords}
showHistory={showHistory === '1'}
/>
) : null}
@@ -396,7 +396,7 @@ export async function getServerSideProps(context: any) {
appIntro: app?.appId?.intro ?? 'intro',
shareId: shareId ?? '',
authToken: authToken ?? '',
...(await serviceSideProps(context, ['file']))
...(await serviceSideProps(context, ['file', 'app']))
}
};
}

View File

@@ -199,7 +199,6 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
})(
<ChatHistorySlider
appId={appId}
apps={myApps}
appName={chatData.app.name}
appAvatar={chatData.app.avatar}
confirmClearText={t('common:core.chat.Confirm to clear history')}
@@ -230,7 +229,7 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
flexDirection={'column'}
>
{/* header */}
<ChatHeader apps={myApps} chatData={chatData} history={chatData.history} showHistory />
<ChatHeader apps={myApps} chatData={chatData} history={chatRecords} showHistory />
{/* chat box */}
<Box flex={1}>
{chatData.app.type === AppTypeEnum.plugin ? (
@@ -340,7 +339,7 @@ export async function getServerSideProps(context: any) {
chatId: context?.query?.chatId || '',
teamId: context?.query?.teamId || '',
teamToken: context?.query?.teamToken || '',
...(await serviceSideProps(context, ['file']))
...(await serviceSideProps(context, ['file', 'app']))
}
};
}

View File

@@ -128,7 +128,7 @@ const Login = () => {
export async function getServerSideProps(context: any) {
return {
props: { ...(await serviceSideProps(context)) }
props: { ...(await serviceSideProps(context, ['app'])) }
};
}

View File

@@ -439,7 +439,7 @@ export const simpleBotTemplates: TemplateType = [
label: 'core.ai.Prompt',
description: 'core.app.tip.chatNodeSystemPromptTip',
placeholder: 'core.app.tip.chatNodeSystemPromptTip',
value: '请直接将我的问题翻译成{{language}},不需要回答问题。'
value: '请直接将我的问题翻译成{{$VARIABLE_NODE_ID.language$}},不需要回答问题。'
},
{
key: 'history',

View File

@@ -25,7 +25,9 @@
"@nestjs/platform-fastify": "^10.3.8",
"@nestjs/swagger": "^7.3.1",
"fastify": "^4.27.0",
"dayjs": "^1.11.7",
"isolated-vm": "^4.7.2",
"tiktoken": "^1.0.15",
"node-gyp": "^10.1.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1"

View File

@@ -0,0 +1,10 @@
export const timeDelay = (time: number) => {
return new Promise((resolve, reject) => {
if (time > 10000) {
reject('Delay time must be less than 10');
}
setTimeout(() => {
resolve('');
}, time);
});
};

View File

@@ -0,0 +1,8 @@
import { Tiktoken } from 'tiktoken/lite';
const cl100k_base = require('tiktoken/encoders/cl100k_base');
export const countToken = (text: string = '') => {
const enc = new Tiktoken(cl100k_base.bpe_ranks, cl100k_base.special_tokens, cl100k_base.pat_str);
const encodeText = enc.encode(text);
return encodeText.length;
};

View File

@@ -1,15 +1,14 @@
import { Controller, Post, Body, HttpCode } from '@nestjs/common';
import { SandboxService } from './sandbox.service';
import { RunCodeDto, RunCodeResponse } from './dto/create-sandbox.dto';
import { WorkerNameEnum, runWorker } from 'src/worker/utils';
import { RunCodeDto } from './dto/create-sandbox.dto';
import { runSandbox } from './utils';
@Controller('sandbox')
export class SandboxController {
constructor(private readonly sandboxService: SandboxService) {}
constructor() {}
@Post('/js')
@HttpCode(200)
runJs(@Body() codeProps: RunCodeDto) {
return runWorker<RunCodeResponse>(WorkerNameEnum.runJs, codeProps);
return runSandbox(codeProps);
}
}

View File

@@ -1,10 +1,9 @@
import { Injectable } from '@nestjs/common';
import { RunCodeDto } from './dto/create-sandbox.dto';
import { WorkerNameEnum, runWorker } from 'src/worker/utils';
@Injectable()
export class SandboxService {
runJs(params: RunCodeDto) {
return runWorker(WorkerNameEnum.runJs, params);
return {};
}
}

View File

@@ -0,0 +1,102 @@
import { RunCodeDto, RunCodeResponse } from 'src/sandbox/dto/create-sandbox.dto';
import IsolatedVM, { ExternalCopy, Isolate, Reference } from 'isolated-vm';
import { countToken } from './jsFn/tiktoken';
import { timeDelay } from './jsFn/delay';
const CustomLogStr = 'CUSTOM_LOG';
/*
Rewrite code to add custom functions: Promise function; Log.
*/
function getFnCode(code: string) {
const rewriteSystemFn = `
const thisDelay = (...args) => global_delay.applySyncPromise(undefined,args)
`;
// rewrite delay
code = code.replace(/delay\((.*)\)/g, `thisDelay($1)`);
// rewrite log
code = code.replace(/console\.log/g, `${CustomLogStr}`);
const runCode = `
(async() => {
try {
${rewriteSystemFn}
${code}
const res = await main(variables, {})
return JSON.stringify(res);
} catch(err) {
return JSON.stringify({ERROR: err?.message ?? err})
}
})
`;
return runCode;
}
function registerSystemFn(jail: IsolatedVM.Reference<Record<string | number | symbol, any>>) {
return Promise.all([
// delay
jail.set('global_delay', new Reference(timeDelay)),
jail.set('countToken', countToken)
]);
}
export const runSandbox = async ({
code,
variables = {}
}: RunCodeDto): Promise<RunCodeResponse> => {
const logData = [];
const isolate = new Isolate({ memoryLimit: 32 });
const context = await isolate.createContext();
const jail = context.global;
try {
// Add global variables
await Promise.all([
jail.set('variables', new ExternalCopy(variables).copyInto()),
jail.set(CustomLogStr, function (...args) {
logData.push(
args
.map((item) => (typeof item === 'object' ? JSON.stringify(item, null, 2) : item))
.join(', ')
);
}),
registerSystemFn(jail)
]);
// Run code
const fn = await context.eval(getFnCode(code), { reference: true, timeout: 10000 });
try {
// Get result and parse
const value = await fn.apply(undefined, [], { result: { promise: true } });
const result = JSON.parse(value.toLocaleString());
// release memory
context.release();
isolate.dispose();
if (result.ERROR) {
return Promise.reject(result.ERROR);
}
return {
codeReturn: result,
log: logData.join('\n')
};
} catch (error) {
context.release();
isolate.dispose();
return Promise.reject('Not an invalid response.You must return an object');
}
} catch (err) {
console.log(err);
context.release();
isolate.dispose();
return Promise.reject(err);
}
};

View File

@@ -1,53 +0,0 @@
import { RunCodeDto, RunCodeResponse } from 'src/sandbox/dto/create-sandbox.dto';
import { parentPort } from 'worker_threads';
import { workerResponse } from './utils';
// @ts-ignore
const ivm = require('isolated-vm');
parentPort?.on('message', ({ code, variables = {} }: RunCodeDto) => {
const resolve = (data: RunCodeResponse) => workerResponse({ parentPort, type: 'success', data });
const reject = (error: any) => workerResponse({ parentPort, type: 'error', data: error });
try {
const isolate = new ivm.Isolate({ memoryLimit: 32 });
const context = isolate.createContextSync();
const jail = context.global;
// custom function
const logData = [];
const CustomLogStr = 'CUSTOM_LOG';
code = code.replace(/console\.log/g, `${CustomLogStr}`);
jail.setSync(CustomLogStr, function (...args) {
logData.push(
args
.map((item) => (typeof item === 'object' ? JSON.stringify(item, null, 2) : item))
.join(', ')
);
});
jail.setSync('responseData', function (args: any): any {
if (typeof args === 'object') {
resolve({
codeReturn: args,
log: logData.join('\n')
});
} else {
reject('Not an invalid response, must return an object');
}
});
// Add global variables
jail.setSync('variables', new ivm.ExternalCopy(variables).copyInto());
const scriptCode = `
${code}
responseData(main(variables))`;
context.evalSync(scriptCode, { timeout: 6000 });
} catch (err) {
console.log(err);
reject(err);
}
process.exit();
});

View File

@@ -1,47 +0,0 @@
import { type MessagePort, Worker } from 'worker_threads';
import * as path from 'path';
export enum WorkerNameEnum {
runJs = 'runJs',
runPy = 'runPy'
}
type WorkerResponseType = { type: 'success' | 'error'; data: any };
export const getWorker = (name: WorkerNameEnum) => {
const baseUrl =
process.env.NODE_ENV === 'production' ? 'projects/sandbox/dist/worker' : 'dist/worker';
const workerPath = path.join(process.cwd(), baseUrl, `${name}.js`);
return new Worker(workerPath);
};
export const runWorker = <T = any>(name: WorkerNameEnum, params?: Record<string, any>) => {
return new Promise<T>((resolve, reject) => {
const worker = getWorker(name);
worker.postMessage(params);
worker.on('message', (msg: WorkerResponseType) => {
if (msg.type === 'error') return reject(msg.data);
resolve(msg.data);
worker.terminate();
});
worker.on('error', (err) => {
reject(err);
worker.terminate();
});
worker.on('messageerror', (err) => {
reject(err);
worker.terminate();
});
});
};
export const workerResponse = ({
parentPort,
...data
}: WorkerResponseType & { parentPort?: MessagePort }) => {
parentPort?.postMessage(data);
};