Compare commits

...

14 Commits

Author SHA1 Message Date
heheer
7c829febec feat: add context menu & comment node (#2834)
* feat: add comment node

* useMemo
2024-09-29 10:08:19 +08:00
heheer
7bdff9ce9c perf: change tool params type label & enum input conditional rendering (#2835) 2024-09-28 18:00:22 +08:00
heheer
1599d144ce feat: add tool params node & tool params add array type (#2824)
* add tool params node

* add tool params enum

* node response

* tool add array type params

* fix tool params

* fix

* fix

* fix
2024-09-28 15:58:55 +08:00
Archer
f2749cbb00 4.8.11 perf (#2832)
* save toast

* perf: surya ocr

* perf: remove same model name

* fix: indexes

* perf: ip check

* feat: Fixed the version number of the subapplication

* feat: simple app get latest child version

* perf: update child dispatch variables

* feat: variables update doc
2024-09-28 15:31:25 +08:00
heheer
f7a8203454 fix:variable not update in nested workflow runs (#2830) 2024-09-28 13:59:35 +08:00
Archer
d95f71e9e3 Update dataset_engine.md (#2829) 2024-09-28 10:08:54 +08:00
Deepturn
a43d845298 Update configuration.md (#2828)
修改 claude
2024-09-28 09:57:16 +08:00
papapatrick
3e64f46d92 fix: change ip detect url (#2827) 2024-09-27 22:19:48 +08:00
yiming-alicloud
0335f16742 submit ocr module (#2815) 2024-09-27 16:07:28 +08:00
Archer
98dbec2cf7 4.8.11 fix (#2822)
* fix: tool choice hostiry error

* fix: chat page auth error redirect

* perf: ip redirect tip

* feat: fedomain env

* fix: tool desc empty

* feat: 4811 doc
2024-09-27 15:52:33 +08:00
silencezhang7
d259eda6b4 新增BI圖標功能-柱狀圖 (#2779)
* 新增BI圖標功能-柱狀圖

* 新增BI图表功能-柱狀圖,优化代码,删除无用代码

* 优化生成逻辑,支持插件手动选择图表类型:目前支持柱状图,折线图,饼图

* 修改包名称,完成基础图表
2024-09-27 13:50:42 +08:00
heheer
7c8f2ab6f5 perf: support prompt editor dynamic height increase & modify aichat placeholder (#2817) 2024-09-27 13:45:44 +08:00
papapatrick
691476c821 feat: add login page ip detect (#2819)
* feat: add login page ip detect

* code perf
2024-09-27 13:39:10 +08:00
Archer
efcb53cd6d Rename node(#2814) 2024-09-26 18:25:42 +08:00
105 changed files with 2449 additions and 451 deletions

View File

@@ -42,7 +42,7 @@ FastGPT 采用了 RAG 中的 Embedding 方案构建知识库,要使用好 Fast
FastGPT 采用了`PostgresSQL``PG Vector`插件作为向量检索器,索引为`HNSW`。且`PostgresSQL`仅用于向量检索(该引擎可以替换成其它数据库),`MongoDB`用于其他数据的存取。
`MongoDB``dataset.datas`表中,会存储向量原数据的信息,同时有一个`indexes`字段会记录其对应的向量ID这是一个数组也就是说一组向量可以对应多组数据
`MongoDB``dataset.datas`表中,会存储向量原数据的信息,同时有一个`indexes`字段会记录其对应的向量ID这是一个数组也就是说一组数据可以对应多个向量
`PostgresSQL`的表中,设置一个`vector`字段用于存储向量。在检索时会先召回向量再根据向量的ID`MongoDB`中寻找原数据内容,如果对应了同一组原数据,则进行合并,向量得分取最高得分。

View File

@@ -192,7 +192,7 @@ weight: 708
- /imgs/model/baichuan.svg - 百川
- /imgs/model/chatglm.svg - 智谱
- /imgs/model/calude.svg - calude
- /imgs/model/claude.svg - claude
- /imgs/model/ernie.svg - 文心一言
- /imgs/model/moonshot.svg - 月之暗面
- /imgs/model/openai.svg - OpenAI GPT

View File

@@ -80,7 +80,9 @@ weight: 813
### 3. 修改镜像 tag 并重启
- 更新 FastGPT 镜像 tag: v4.8.11-alpha
- 更新 FastGPT 商业版镜像 tag: v4.8.11-alpha
- 更新 FastGPT Sandbox 镜像 tag: v4.8.11-alpha
## V4.8.11 更新说明
@@ -93,16 +95,19 @@ weight: 813
7. 新增 - 支持 Openai o1 模型,需增加模型的 `defaultConfig` 配置,覆盖 `temperature``max_tokens``stream`配置o1 不支持 stream 模式, 详细可重新拉取 `config.json` 配置文件查看。
8. 新增 - AI 对话节点知识库引用,支持配置 role=system 和 role=user已配置的过自定义提示词的节点将会保持 user 模式,其余用户将转成 system 模式。
9. 新增 - 插件支持上传系统文件。
10. 新增 - 插件输出,支持指定字段作为工具响应
11. 新增 - 支持工作流嵌套子应用时,可以设置`非流模式`,同时简易模式也可以选择工作流作为插件了,简易模式调用子应用时,都将强制使用非流模式
12. 新增 - 调试模式下,子应用调用,支持返回详细运行数据
13. 新增 - 保留所有模式下子应用嵌套调用的日志
14. 优化 - 工作流嵌套层级限制 20 层,避免因编排不合理导致的无限死循环
15. 优化 - 工作流 handler 性能优化
16. 优化 - 工作流快捷键,避免调试测试时也会触发复制和回退
17. 优化 - 流输出,切换浏览器 Tab 后仍可以继续输出
18. 优化 - 完善外部文件知识库相关 API
19. 修复 - 知识库选择权限问题
20. 修复 - 空 chatId 发起对话,首轮携带用户选择时会异常。
21. 修复 - createDataset 接口intro 为赋值
22. 修复 - 对话框渲染性能问题。
10. 新增 - 子应用嵌套调用时,版本锁定。主应用未主动更新版本时,不会取最新版进行执行,保证主应用服务稳定
11. 新增 - 插件输出,支持指定字段作为工具响应
12. 新增 - 支持工作流嵌套子应用时,可以设置`非流模式`,同时简易模式也可以选择工作流作为插件了,简易模式调用子应用时,都将强制使用非流模式
13. 新增 - 调试模式下子应用调用,支持返回详细运行数据
14. 新增 - 保留所有模式下子应用嵌套调用的日志
15. 优化 - 工作流嵌套层级限制 20 层,避免因编排不合理导致的无限死循环
16. 优化 - 工作流 handler 性能优化
17. 优化 - 工作流快捷键,避免调试测试时也会触发复制和回退
18. 修复 - 工作流工具调用中修改全局变量后,无法传递到后续流程。
19. 优化 - 流输出,切换浏览器 Tab 后仍可以继续输出
20. 优化 - 完善外部文件知识库相关 API
21. 修复 - 知识库选择权限问题
22. 修复 - 空 chatId 发起对话,首轮携带用户选择时会异常。
23. 修复 - createDataset 接口intro 为赋值。
24. 修复 - 对话框渲染性能问题。
25. 修复 - 工具调用历史记录存储不正确。

View File

@@ -16,6 +16,6 @@ export const bucketNameMap = {
}
};
export const ReadFileBaseUrl = '/api/common/file/read';
export const ReadFileBaseUrl = `${process.env.FE_DOMAIN || ''}/api/common/file/read`;
export const documentFileType = '.txt, .docx, .csv, .xlsx, .pdf, .md, .html, .pptx';

View File

@@ -289,7 +289,7 @@ export const GPTMessages2Chats = (
})
.filter((item) => item.value.length > 0);
// Merge data with the same dataId
// Merge data with the same dataIdSequential obj merging
const result = chatMessages.reduce((result: ChatItemType[], currentItem) => {
const lastItem = result[result.length - 1];

View File

@@ -151,7 +151,11 @@ export enum NodeInputKeyEnum {
loopEndInput = 'loopEndInput',
// form input
userInputForms = 'userInputForms'
userInputForms = 'userInputForms',
// comment
commentText = 'commentText',
commentSize = 'commentSize'
}
export enum NodeOutputKeyEnum {

View File

@@ -118,6 +118,7 @@ export enum FlowNodeTypeEnum {
queryExtension = 'cfr',
tools = 'tools',
stopTool = 'stopTool',
toolParams = 'toolParams',
lafModule = 'lafModule',
ifElseNode = 'ifElseNode',
variableUpdate = 'variableUpdate',
@@ -129,7 +130,8 @@ export enum FlowNodeTypeEnum {
loop = 'loop',
loopStart = 'loopStart',
loopEnd = 'loopEnd',
formInput = 'formInput'
formInput = 'formInput',
comment = 'comment'
}
// node IO value type

View File

@@ -25,7 +25,8 @@ export enum DispatchNodeResponseKeyEnum {
rewriteHistories = 'rewriteHistories', // If have the response, workflow histories will be rewrite
interactive = 'INTERACTIVE', // is interactive
runTimes = 'runTimes' // run times
runTimes = 'runTimes', // run times
newVariables = 'newVariables' // new variables
}
export const needReplaceReferenceInputTypeList = [

View File

@@ -79,6 +79,7 @@ export type RuntimeNodeItemType = {
outputs: FlowNodeOutputItemType[];
pluginId?: string; // workflow id / plugin id
version: string;
};
export type PluginRuntimeType = {
@@ -185,6 +186,9 @@ export type DispatchNodeResponseType = {
// form input
formInputResult?: string;
// tool params
toolParamsResult?: Record<string, any>;
};
export type DispatchNodeResultType<T = {}> = {
@@ -196,6 +200,7 @@ export type DispatchNodeResultType<T = {}> = {
[DispatchNodeResponseKeyEnum.assistantResponses]?: AIChatItemValueItemType[]; // Assistant response(Store to db)
[DispatchNodeResponseKeyEnum.rewriteHistories]?: ChatItemType[];
[DispatchNodeResponseKeyEnum.runTimes]?: number;
[DispatchNodeResponseKeyEnum.newVariables]?: Record<string, any>;
} & T;
/* Single node props */

View File

@@ -124,7 +124,8 @@ export const storeNodes2RuntimeNodes = (
isEntry: entryNodeIds.includes(node.nodeId),
inputs: node.inputs,
outputs: node.outputs,
pluginId: node.pluginId
pluginId: node.pluginId,
version: node.version
};
}) || []
);
@@ -233,7 +234,8 @@ export const getReferenceVariableValue = ({
nodes: RuntimeNodeItemType[];
variables: Record<string, any>;
}) => {
if (!isReferenceValue(value)) {
const nodeIds = nodes.map((node) => node.nodeId);
if (!isReferenceValue(value, nodeIds)) {
return value;
}
const sourceNodeId = value[0];

View File

@@ -33,17 +33,19 @@ import { LoopNode } from './system/loop/loop';
import { LoopStartNode } from './system/loop/loopStart';
import { LoopEndNode } from './system/loop/loopEnd';
import { FormInputNode } from './system/interactive/formInput';
import { ToolParamsNode } from './system/toolParams';
const systemNodes: FlowNodeTemplateType[] = [
AiChatModule,
TextEditorNode,
AssignedAnswerModule,
DatasetSearchModule,
DatasetConcatModule,
ToolModule,
StopToolNode,
ClassifyQuestionModule,
ContextExtractModule,
DatasetConcatModule,
ToolModule,
ToolParamsNode,
StopToolNode,
ReadFilesNode,
HttpNode468,
AiQueryExtension,

View File

@@ -1,7 +1,7 @@
import { NodeInputKeyEnum } from '../constants';
import { FlowNodeInputTypeEnum } from '../node/constant';
import { WorkflowIOValueTypeEnum } from '../constants';
import { chatNodeSystemPromptTip } from './tip';
import { chatNodeSystemPromptTip, systemPromptTip } from './tip';
import { FlowNodeInputItemType } from '../type/io';
import { i18nT } from '../../../../web/i18n/utils';
@@ -55,7 +55,7 @@ export const Input_Template_System_Prompt: FlowNodeInputItemType = {
max: 3000,
valueType: WorkflowIOValueTypeEnum.string,
label: i18nT('common:core.ai.Prompt'),
description: chatNodeSystemPromptTip,
description: systemPromptTip,
placeholder: chatNodeSystemPromptTip
};

View File

@@ -19,7 +19,7 @@ import {
Input_Template_UserChatInput,
Input_Template_Text_Quote
} from '../../input';
import { chatNodeSystemPromptTip } from '../../tip';
import { chatNodeSystemPromptTip, systemPromptTip } from '../../tip';
import { getHandleConfig } from '../../utils';
import { i18nT } from '../../../../../../web/i18n/utils';
@@ -94,7 +94,7 @@ export const AiChatModule: FlowNodeTemplateType = {
{
...Input_Template_System_Prompt,
label: i18nT('common:core.ai.Prompt'),
description: chatNodeSystemPromptTip,
description: systemPromptTip,
placeholder: chatNodeSystemPromptTip
},
Input_Template_History,

View File

@@ -0,0 +1,40 @@
import { FlowNodeTypeEnum } from '../../node/constant';
import { FlowNodeTemplateType } from '../../type/node.d';
import {
FlowNodeTemplateTypeEnum,
NodeInputKeyEnum,
WorkflowIOValueTypeEnum
} from '../../constants';
import { getHandleConfig } from '../utils';
export const CommentNode: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.comment,
templateType: FlowNodeTemplateTypeEnum.systemInput,
flowNodeType: FlowNodeTypeEnum.comment,
sourceHandle: getHandleConfig(false, false, false, false),
targetHandle: getHandleConfig(false, false, false, false),
avatar: '',
name: '',
intro: '',
version: '4811',
inputs: [
{
key: NodeInputKeyEnum.commentText,
renderTypeList: [],
valueType: WorkflowIOValueTypeEnum.string,
label: '',
value: ''
},
{
key: NodeInputKeyEnum.commentSize,
renderTypeList: [],
valueType: WorkflowIOValueTypeEnum.object,
label: '',
value: {
width: 240,
height: 140
}
}
],
outputs: []
};

View File

@@ -0,0 +1,20 @@
import { FlowNodeTypeEnum } from '../../node/constant';
import { FlowNodeTemplateType } from '../../type/node';
import { FlowNodeTemplateTypeEnum } from '../../constants';
import { getHandleConfig } from '../utils';
import { i18nT } from '../../../../../web/i18n/utils';
export const ToolParamsNode: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.toolParams,
templateType: FlowNodeTemplateTypeEnum.ai,
flowNodeType: FlowNodeTypeEnum.toolParams,
sourceHandle: getHandleConfig(true, true, true, true),
targetHandle: getHandleConfig(true, true, true, true),
avatar: 'core/workflow/template/toolParams',
name: i18nT('workflow:tool_params_config'),
intro: i18nT('workflow:intro_tool_params_config'),
version: '4811',
isTool: true,
inputs: [],
outputs: []
};

View File

@@ -16,7 +16,7 @@ import {
Input_Template_System_Prompt,
Input_Template_UserChatInput
} from '../input';
import { chatNodeSystemPromptTip } from '../tip';
import { chatNodeSystemPromptTip, systemPromptTip } from '../tip';
import { LLMModelTypeEnum } from '../../../ai/constants';
import { getHandleConfig } from '../utils';
import { i18nT } from '../../../../../web/i18n/utils';
@@ -62,7 +62,7 @@ export const ToolModule: FlowNodeTemplateType = {
{
...Input_Template_System_Prompt,
label: i18nT('common:core.ai.Prompt'),
description: chatNodeSystemPromptTip,
description: systemPromptTip,
placeholder: chatNodeSystemPromptTip
},
Input_Template_History,

View File

@@ -1 +1,2 @@
export const chatNodeSystemPromptTip = 'core.app.tip.chatNodeSystemPromptTip';
export const systemPromptTip = 'core.app.tip.systemPromptTip';

View File

@@ -50,6 +50,7 @@ export type FlowNodeInputItemType = InputComponentPropsType & {
description?: string; // field desc
required?: boolean;
toolDescription?: string; // If this field is not empty, it is entered as a tool
enum?: string;
// render components params
canEdit?: boolean; // dynamic inputs

View File

@@ -297,8 +297,8 @@ export const formatEditorVariablePickerIcon = (
}));
};
export const isReferenceValue = (value: any): boolean => {
return Array.isArray(value) && value.length === 2 && typeof value[0] === 'string';
export const isReferenceValue = (value: any, nodeIds: string[]): boolean => {
return Array.isArray(value) && value.length === 2 && nodeIds.includes(value[0]);
};
export const getElseIFLabel = (i: number) => {

View File

@@ -1,11 +1,13 @@
{
"name": "@fastgpt/plugins",
"version": "1.0.0",
"type": "module",
"dependencies": {
"duck-duck-scrape": "^2.2.5",
"lodash": "^4.17.21",
"axios": "^1.5.1",
"expr-eval": "^2.0.2"
"expr-eval": "^2.0.2",
"echarts": "5.4.1"
},
"devDependencies": {
"@fastgpt/global": "workspace:*",

View File

@@ -22,7 +22,9 @@ const packagePluginList = [
'duckduckgo/search',
'duckduckgo/searchImg',
'duckduckgo/searchNews',
'duckduckgo/searchVideo'
'duckduckgo/searchVideo',
'drawing',
'drawing/baseChart'
];
export const list = [...staticPluginList, ...packagePluginList];

View File

@@ -0,0 +1,94 @@
import * as echarts from 'echarts';
type Props = {
title: string;
xAxis: string;
yAxis: string;
chartType: string;
};
type Response = Promise<{
result: string;
}>;
type SeriesData = {
name: string;
type: 'bar' | 'line' | 'pie'; // 只允许这三种类型
data: number[] | { value: number; name: string }[]; // 根据图表类型的数据结构
};
type Option = {
backgroundColor: string;
title: { text: string };
tooltip: {};
xAxis: { data: string[] };
yAxis: {};
series: SeriesData[]; // 使用定义的类型
};
const generateChart = async (title: string, xAxis: string, yAxis: string, chartType: string) => {
// @ts-ignore 无法使用dom如使用jsdom会出现生成图片无法正常展示有高手可以帮忙解决
const chart = echarts.init(undefined, undefined, {
renderer: 'svg', // 必须使用 SVG 模式
ssr: true, // 开启 SSR
width: 400, // 需要指明高和宽
height: 300
});
let parsedXAxis: string[] = [];
let parsedYAxis: number[] = [];
try {
parsedXAxis = JSON.parse(xAxis);
parsedYAxis = JSON.parse(yAxis);
} catch (error: any) {
console.error('解析数据时出错:', error);
return Promise.reject('Data error');
}
const option: Option = {
backgroundColor: '#f5f5f5',
title: { text: title },
tooltip: {},
xAxis: { data: parsedXAxis },
yAxis: {},
series: [] // 初始化为空数组
};
// 根据 chartType 生成不同的图表
switch (chartType) {
case '柱状图':
option.series.push({ name: 'Sample', type: 'bar', data: parsedYAxis });
break;
case '折线图':
option.series.push({ name: 'Sample', type: 'line', data: parsedYAxis });
break;
case '饼图':
option.series.push({
name: 'Sample',
type: 'pie',
data: parsedYAxis.map((value, index) => ({
value,
name: parsedXAxis[index] // 使用 xAxis 作为饼图的名称
}))
});
break;
default:
console.error('不支持的图表类型:', chartType);
return '';
}
chart.setOption(option);
// 生成 Base64 图像
const base64Image = chart.getDataURL({ type: 'png' });
// 释放图表实例
chart.dispose();
return base64Image;
};
const main = async ({ title, xAxis, yAxis, chartType }: Props): Response => {
return {
result: await generateChart(title, xAxis, yAxis, chartType)
};
};
export default main;

View File

@@ -0,0 +1,500 @@
{
"author": "",
"version": "486",
"name": "基础图表",
"avatar": "core/workflow/template/baseChart",
"intro": "根据数据生成图表可根据chartType生成柱状图折线图饼图",
"showStatus": true,
"weight": 10,
"isTool": true,
"templateType": "search",
"workflow": {
"nodes": [
{
"nodeId": "pluginInput",
"name": "common:core.module.template.self_input",
"intro": "workflow:intro_plugin_input",
"avatar": "core/workflow/template/workflowStart",
"flowNodeType": "pluginInput",
"showStatus": false,
"position": {
"x": 613.7921798611637,
"y": -124.66724109717275
},
"version": "481",
"inputs": [
{
"renderTypeList": ["reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "title",
"label": "title",
"description": "BI图表的标题",
"defaultValue": "",
"list": [
{
"label": "",
"value": ""
}
],
"required": true,
"toolDescription": "BI图表的标题"
},
{
"renderTypeList": ["reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "xAxis",
"label": "xAxis",
"description": "x轴数据",
"defaultValue": "",
"required": true,
"toolDescription": "x轴数据例如['A', 'B', 'C']",
"list": [
{
"label": "",
"value": ""
}
]
},
{
"renderTypeList": ["reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "yAxis",
"label": "yAxis",
"description": "y轴数据",
"defaultValue": "",
"list": [
{
"label": "",
"value": ""
}
],
"required": true,
"toolDescription": "y轴数据例如['A', 'B', 'C']"
},
{
"renderTypeList": ["select"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "chartType",
"label": "chartType",
"description": "图表类型:柱状图,折线图,饼图",
"defaultValue": "",
"required": true,
"list": [
{
"label": "柱状图",
"value": "柱状图"
},
{
"label": "折线图",
"value": "折线图"
},
{
"label": "饼图",
"value": "饼图"
}
],
"toolDescription": "图表类型,目前支持三种: 柱状图,折线图,饼图"
}
],
"outputs": [
{
"id": "title",
"valueType": "string",
"key": "title",
"label": "title",
"type": "hidden"
},
{
"id": "xAxis",
"valueType": "string",
"key": "xAxis",
"label": "xAxis",
"type": "hidden"
},
{
"id": "yAxis",
"valueType": "string",
"key": "yAxis",
"label": "yAxis",
"type": "hidden"
},
{
"id": "chartType",
"valueType": "string",
"key": "chartType",
"label": "chartType",
"type": "hidden"
}
]
},
{
"nodeId": "pluginOutput",
"name": "common:core.module.template.self_output",
"intro": "workflow:intro_custom_plugin_output",
"avatar": "core/workflow/template/pluginOutput",
"flowNodeType": "pluginOutput",
"showStatus": false,
"position": {
"x": 2122.252754006148,
"y": -63.5218674613718
},
"version": "481",
"inputs": [
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "相对路径URL",
"label": "相对路径URL",
"description": "可用使用markdown格式展示图片![图片](url)",
"value": ["ws0DFKJnCPhk", "bzaYjKyQFOw2"]
}
],
"outputs": []
},
{
"nodeId": "ws0DFKJnCPhk",
"name": "HTTP 请求",
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
"avatar": "core/workflow/template/httpRequest",
"flowNodeType": "httpRequest468",
"showStatus": true,
"position": {
"x": 1216.5166647574395,
"y": -206.30162946606856
},
"version": "481",
"inputs": [
{
"key": "system_addInputParam",
"renderTypeList": ["addInputParam"],
"valueType": "dynamic",
"label": "",
"required": false,
"description": "接收前方节点的输出值作为变量,这些变量可以被 HTTP 请求参数使用。",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"valueDesc": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpMethod",
"renderTypeList": ["custom"],
"valueType": "string",
"label": "",
"value": "POST",
"required": true,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpTimeout",
"renderTypeList": ["custom"],
"valueType": "number",
"label": "",
"value": 30,
"min": 5,
"max": 600,
"required": true,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpReqUrl",
"renderTypeList": ["hidden"],
"valueType": "string",
"label": "",
"description": "新的 HTTP 请求地址。如果出现两个“请求地址”,可以删除该模块重新加入,会拉取最新的模块配置。",
"placeholder": "https://api.ai.com/getInventory",
"required": false,
"valueDesc": "",
"debugLabel": "",
"toolDescription": "",
"value": "drawing/baseChart"
},
{
"key": "system_httpHeader",
"renderTypeList": ["custom"],
"valueType": "any",
"value": [],
"label": "",
"description": "自定义请求头,请严格填入 JSON 字符串。\n1. 确保最后一个属性没有逗号\n2. 确保 key 包含双引号\n例如{\"Authorization\":\"Bearer xxx\"}",
"placeholder": "common:core.module.input.description.Http Request Header",
"required": false,
"valueDesc": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpParams",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpJsonBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": "{\r\n \"title\": \"{{title-plugin}}\",\r\n \"xAxis\": \"{{xAxis-plugin}}\",\r\n \"yAxis\": \"{{yAxis-plugin}}\",\r\n \"chartType\": \"{{chartType-plugin}}\"\r\n}",
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpFormBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"key": "system_httpContentType",
"renderTypeList": ["hidden"],
"valueType": "string",
"value": "json",
"label": "",
"required": false,
"valueDesc": "",
"description": "",
"debugLabel": "",
"toolDescription": ""
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "title-plugin",
"label": "title-plugin",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "title"]
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "xAxis-plugin",
"label": "xAxis-plugin",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "xAxis"]
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "yAxis-plugin",
"label": "yAxis-plugin",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "yAxis"]
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "chartType-plugin",
"label": "chartType-plugin",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "chartType"]
}
],
"outputs": [
{
"id": "system_addOutputParam",
"key": "system_addOutputParam",
"type": "dynamic",
"valueType": "dynamic",
"label": "",
"customFieldConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": false
},
"valueDesc": "",
"description": ""
},
{
"id": "error",
"key": "error",
"label": "请求错误",
"description": "HTTP请求错误信息成功时返回空",
"valueType": "object",
"type": "static",
"valueDesc": ""
},
{
"id": "httpRawResponse",
"key": "httpRawResponse",
"required": true,
"label": "原始响应",
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
"valueType": "any",
"type": "static",
"valueDesc": ""
},
{
"id": "bzaYjKyQFOw2",
"valueType": "string",
"type": "dynamic",
"key": "result",
"label": "result"
}
]
}
],
"edges": [
{
"source": "pluginInput",
"target": "ws0DFKJnCPhk",
"sourceHandle": "pluginInput-source-right",
"targetHandle": "ws0DFKJnCPhk-target-left"
},
{
"source": "ws0DFKJnCPhk",
"target": "pluginOutput",
"sourceHandle": "ws0DFKJnCPhk-source-right",
"targetHandle": "pluginOutput-target-left"
}
]
}
}

View File

@@ -0,0 +1,17 @@
{
"author": "",
"version": "486",
"name": "BI图表功能",
"avatar": "core/workflow/template/BI",
"intro": "BI图表功能可以生成一些常用的图表如饼图柱状图折线图等",
"showStatus": false,
"weight": 100,
"isTool": true,
"templateType": "tools",
"workflow": {
"nodes": [],
"edges": []
}
}

View File

@@ -51,6 +51,7 @@ export function reRankRecall({
}));
})
.catch((err) => {
console.log(err);
addLog.error('rerank error', err);
return [];

View File

@@ -2,7 +2,6 @@ import { AppSchema } from '@fastgpt/global/core/app/type';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { getLLMModel } from '../ai/model';
import { MongoAppVersion } from './version/schema';
import { MongoApp } from './schema';
export const beforeUpdateAppFormat = <T extends AppSchema['modules'] | undefined>({
@@ -46,30 +45,6 @@ export const beforeUpdateAppFormat = <T extends AppSchema['modules'] | undefined
};
};
export const getAppLatestVersion = async (appId: string, app?: AppSchema) => {
const version = await MongoAppVersion.findOne({
appId,
isPublish: true
})
.sort({
time: -1
})
.lean();
if (version) {
return {
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig || app?.chatConfig || {}
};
}
return {
nodes: app?.modules || [],
edges: app?.edges || [],
chatConfig: app?.chatConfig || {}
};
};
/* Get apps */
export async function findAppAndAllChildren({
teamId,

View File

@@ -10,6 +10,7 @@ import { cloneDeep } from 'lodash';
import { MongoApp } from '../schema';
import { SystemPluginTemplateItemType } from '@fastgpt/global/core/workflow/type';
import { getSystemPluginTemplates } from '../../../../plugins/register';
import { getAppLatestVersion, getAppVersionById } from '../version/controller';
/*
plugin id rule:
@@ -34,38 +35,14 @@ export async function splitCombinePluginId(id: string) {
return { source, pluginId: id };
}
const getChildAppTemplateById = async (
id: string
): Promise<SystemPluginTemplateItemType & { teamId?: string }> => {
const { source, pluginId } = await splitCombinePluginId(id);
type ChildAppType = SystemPluginTemplateItemType & { teamId?: string };
const getSystemPluginTemplateById = async (
pluginId: string
): Promise<SystemPluginTemplateItemType> => {
const item = getSystemPluginTemplates().find((plugin) => plugin.id === pluginId);
if (!item) return Promise.reject('plugin not found');
if (source === PluginSourceEnum.personal) {
const item = await MongoApp.findById(id).lean();
if (!item) return Promise.reject('plugin not found');
return {
id: String(item._id),
teamId: String(item.teamId),
name: item.name,
avatar: item.avatar,
intro: item.intro,
showStatus: true,
workflow: {
nodes: item.modules,
edges: item.edges,
chatConfig: item.chatConfig
},
templateType: FlowNodeTemplateTypeEnum.teamApp,
version: item?.pluginData?.nodeVersion || defaultNodeVersion,
originCost: 0,
currentCost: 0
};
} else {
const item = getSystemPluginTemplates().find((plugin) => plugin.id === pluginId);
if (!item) return Promise.reject('plugin not found');
return cloneDeep(item);
}
return cloneDeep(item);
};
/* format plugin modules to plugin preview module */
@@ -74,7 +51,39 @@ export async function getChildAppPreviewNode({
}: {
id: string;
}): Promise<FlowNodeTemplateType> {
const app = await getChildAppTemplateById(id);
const app: ChildAppType = await (async () => {
const { source, pluginId } = await splitCombinePluginId(id);
if (source === PluginSourceEnum.personal) {
const item = await MongoApp.findById(id).lean();
if (!item) return Promise.reject('plugin not found');
const version = await getAppLatestVersion(id, item);
if (!version.versionId) return Promise.reject('App version not found');
return {
id: String(item._id),
teamId: String(item.teamId),
name: item.name,
avatar: item.avatar,
intro: item.intro,
showStatus: true,
workflow: {
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig
},
templateType: FlowNodeTemplateTypeEnum.teamApp,
version: version.versionId,
originCost: 0,
currentCost: 0
};
} else {
return getSystemPluginTemplateById(pluginId);
}
})();
const isPlugin = !!app.workflow.nodes.find(
(node) => node.flowNodeType === FlowNodeTypeEnum.pluginInput
);
@@ -99,9 +108,51 @@ export async function getChildAppPreviewNode({
};
}
/* run plugin time */
export async function getChildAppRuntimeById(id: string): Promise<PluginRuntimeType> {
const app = await getChildAppTemplateById(id);
/*
Get runtime plugin data
System plugin: plugin id
Personal plugin: Version id
*/
export async function getChildAppRuntimeById(
id: string,
versionId?: string
): Promise<PluginRuntimeType> {
const app: ChildAppType = await (async () => {
const { source, pluginId } = await splitCombinePluginId(id);
if (source === PluginSourceEnum.personal) {
const item = await MongoApp.findById(id).lean();
if (!item) return Promise.reject('plugin not found');
const version = await getAppVersionById({
appId: id,
versionId,
app: item
});
return {
id: String(item._id),
teamId: String(item.teamId),
name: item.name,
avatar: item.avatar,
intro: item.intro,
showStatus: true,
workflow: {
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig
},
templateType: FlowNodeTemplateTypeEnum.teamApp,
// 用不到
version: item?.pluginData?.nodeVersion || defaultNodeVersion,
originCost: 0,
currentCost: 0
};
} else {
return getSystemPluginTemplateById(pluginId);
}
})();
return {
id: app.id,

View File

@@ -0,0 +1,59 @@
import { AppSchema } from '@fastgpt/global/core/app/type';
import { MongoAppVersion } from './schema';
import { Types } from '../../../common/mongo';
export const getAppLatestVersion = async (appId: string, app?: AppSchema) => {
const version = await MongoAppVersion.findOne({
appId,
isPublish: true
})
.sort({
time: -1
})
.lean();
if (version) {
return {
versionId: version._id,
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig || app?.chatConfig || {}
};
}
return {
versionId: app?.pluginData?.nodeVersion,
nodes: app?.modules || [],
edges: app?.edges || [],
chatConfig: app?.chatConfig || {}
};
};
export const getAppVersionById = async ({
appId,
versionId,
app
}: {
appId: string;
versionId?: string;
app?: AppSchema;
}) => {
// 检查 versionId 是否符合 ObjectId 格式
if (versionId && Types.ObjectId.isValid(versionId)) {
const version = await MongoAppVersion.findOne({
_id: versionId,
appId
}).lean();
if (version) {
return {
versionId: version._id,
nodes: version.nodes,
edges: version.edges,
chatConfig: version.chatConfig || app?.chatConfig || {}
};
}
}
// If the version does not exist, the latest version is returned
return getAppLatestVersion(appId, app);
};

View File

@@ -127,7 +127,7 @@ export const loadRequestMessages = async ({
})();
// If imgUrl is a local path, load image from local, and set url to base64
if (imgUrl.startsWith('/') || process.env.VISION_FOCUS_BASE64 === 'true') {
if (imgUrl.startsWith('/')) {
addLog.debug('Load image from local server', {
baseUrl: serverRequestBaseUrl,
requestUrl: imgUrl
@@ -234,7 +234,13 @@ export const loadRequestMessages = async ({
}
}
if (item.role === ChatCompletionRequestMessageRoleEnum.Assistant) {
if (item.content !== undefined && !item.content) return;
if (
item.content !== undefined &&
!item.content &&
!item.tool_calls &&
!item.function_call
)
return;
if (Array.isArray(item.content) && item.content.length === 0) return;
}

View File

@@ -60,12 +60,16 @@ export const runToolWithFunctionCall = async (
type: string;
description: string;
required?: boolean;
enum?: string[];
}
> = {};
item.toolParams.forEach((item) => {
const isArray = item.valueType?.startsWith('array');
properties[item.key] = {
type: item.valueType || 'string',
description: item.toolDescription || ''
type: isArray ? 'array' : item.valueType || 'string',
...(isArray && { items: { type: item.valueType?.slice(5).toLowerCase() || 'string' } }),
description: item.toolDescription || '',
enum: item.enum?.split('\n').filter(Boolean) || []
};
});
@@ -244,33 +248,36 @@ export const runToolWithFunctionCall = async (
role: ChatCompletionRequestMessageRoleEnum.Assistant,
function_call: functionCall
};
/*
...
user
assistant: tool data
*/
const concatToolMessages = [
...requestMessages,
assistantToolMsgParams
] as ChatCompletionMessageParam[];
// Only toolCall tokens are counted here, Tool response tokens count towards the next reply
const tokens = await countGptMessagesTokens(concatToolMessages, undefined, functions);
/*
...
user
assistant: tool data
tool: tool response
*/
const completeMessages = [
...concatToolMessages,
...toolsRunResponse.map((item) => item?.functionCallMsg)
];
// console.log(tokens, 'tool');
// tool assistant
const toolAssistants = toolsRunResponse
.map((item) => {
const assistantResponses = item.toolRunResponse.assistantResponses || [];
return assistantResponses;
})
.flat();
// tool node assistant
const adaptChatMessages = GPTMessages2Chats(completeMessages);
const toolNodeAssistant = adaptChatMessages.pop() as AIChatItemType;
const toolNodeAssistant = GPTMessages2Chats([
assistantToolMsgParams,
...toolsRunResponse.map((item) => item?.functionCallMsg)
])[0] as AIChatItemType;
const toolNodeAssistants = [
...assistantResponses,
...toolAssistants,
...toolNodeAssistant.value
];
const toolNodeAssistants = [...assistantResponses, ...toolNodeAssistant.value];
// concat tool responses
const dispatchFlowResponse = response
@@ -285,7 +292,7 @@ export const runToolWithFunctionCall = async (
return {
dispatchFlowResponse,
totalTokens: response?.totalTokens ? response.totalTokens + tokens : tokens,
completeMessages: filterMessages,
completeMessages,
assistantResponses: toolNodeAssistants,
runTimes:
(response?.runTimes || 0) +

View File

@@ -183,7 +183,18 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
});
// flat child tool response
const childToolResponse = dispatchFlowResponse.map((item) => item.flowResponses).flat();
let newVariables: Record<string, any> = props.variables;
const childToolResponse = dispatchFlowResponse
.map((item) => {
// Computed new variables
newVariables = {
...newVariables,
...item.newVariables
};
return item.flowResponses;
})
.flat();
// concat tool usage
const totalPointsUsage =
@@ -219,6 +230,7 @@ export const dispatchRunTools = async (props: DispatchToolModuleProps): Promise<
tokens: totalTokens
},
...flatUsages
]
],
[DispatchNodeResponseKeyEnum.newVariables]: newVariables
};
};

View File

@@ -68,12 +68,16 @@ export const runToolWithPromptCall = async (
type: string;
description: string;
required?: boolean;
enum?: string[];
}
> = {};
item.toolParams.forEach((item) => {
const isArray = item.valueType?.startsWith('array');
properties[item.key] = {
type: 'string',
description: item.toolDescription || ''
type: isArray ? 'array' : item.valueType || 'string',
...(isArray && { items: { type: item.valueType?.slice(5).toLowerCase() || 'string' } }),
description: item.toolDescription || '',
enum: item.enum?.split('\n').filter(Boolean) || []
};
});
@@ -280,27 +284,37 @@ export const runToolWithPromptCall = async (
role: ChatCompletionRequestMessageRoleEnum.Assistant,
function_call: toolJson
};
/*
...
user
assistant: tool data
*/
const concatToolMessages = [
...requestMessages,
assistantToolMsgParams
] as ChatCompletionMessageParam[];
// Only toolCall tokens are counted here, Tool response tokens count towards the next reply
const tokens = await countGptMessagesTokens(concatToolMessages, undefined);
const completeMessages: ChatCompletionMessageParam[] = [
...concatToolMessages,
{
role: ChatCompletionRequestMessageRoleEnum.Function,
name: toolJson.name,
content: toolsRunResponse.toolResponsePrompt
}
];
// tool assistant
const toolAssistants = toolsRunResponse.moduleRunResponse.assistantResponses || [];
/*
...
user
assistant: tool data
function: tool response
*/
const functionResponseMessage: ChatCompletionMessageParam = {
role: ChatCompletionRequestMessageRoleEnum.Function,
name: toolJson.name,
content: toolsRunResponse.toolResponsePrompt
};
// tool node assistant
const adaptChatMessages = GPTMessages2Chats(completeMessages);
const toolNodeAssistant = adaptChatMessages.pop() as AIChatItemType;
const toolNodeAssistants = [...assistantResponses, ...toolAssistants, ...toolNodeAssistant.value];
const toolNodeAssistant = GPTMessages2Chats([
assistantToolMsgParams,
functionResponseMessage
])[0] as AIChatItemType;
const toolNodeAssistants = [...assistantResponses, ...toolNodeAssistant.value];
const dispatchFlowResponse = response
? response.dispatchFlowResponse.concat(toolsRunResponse.moduleRunResponse)

View File

@@ -6,7 +6,6 @@ import {
ChatCompletionMessageToolCall,
StreamChatType,
ChatCompletionToolMessageParam,
ChatCompletionAssistantToolParam,
ChatCompletionMessageParam,
ChatCompletionTool,
ChatCompletionAssistantMessageParam
@@ -54,7 +53,6 @@ export const runToolWithToolChoice = async (
res,
requestOrigin,
runtimeNodes,
node,
stream,
workflowStreamResponse,
params: { temperature = 0, maxToken = 4000, aiChatVision }
@@ -72,13 +70,20 @@ export const runToolWithToolChoice = async (
{
type: string;
description: string;
enum?: string[];
required?: boolean;
items?: {
type: string;
};
}
> = {};
item.toolParams.forEach((item) => {
const isArray = item.valueType?.startsWith('array');
properties[item.key] = {
type: item.valueType || 'string',
description: item.toolDescription || ''
type: isArray ? 'array' : item.valueType || 'string',
...(isArray && { items: { type: item.valueType?.slice(5).toLowerCase() || 'string' } }),
description: item.toolDescription || '',
enum: item.enum?.split('\n').filter(Boolean) || []
};
});
@@ -86,7 +91,7 @@ export const runToolWithToolChoice = async (
type: 'function',
function: {
name: item.nodeId,
description: item.intro,
description: item.intro || item.name,
parameters: {
type: 'object',
properties,
@@ -140,7 +145,6 @@ export const runToolWithToolChoice = async (
toolModel
);
// console.log(JSON.stringify(requestBody, null, 2));
/* Run llm */
const ai = getAIApi({
timeout: 480000
@@ -282,12 +286,24 @@ export const runToolWithToolChoice = async (
).filter(Boolean) as ToolRunResponseType;
const flatToolsResponseData = toolsRunResponse.map((item) => item.toolRunResponse).flat();
if (toolCalls.length > 0 && !res?.closed) {
// Run the tool, combine its results, and perform another round of AI calls
const assistantToolMsgParams: ChatCompletionAssistantToolParam = {
role: ChatCompletionRequestMessageRoleEnum.Assistant,
tool_calls: toolCalls
};
const assistantToolMsgParams: ChatCompletionAssistantMessageParam[] = [
...(answer
? [
{
role: ChatCompletionRequestMessageRoleEnum.Assistant as 'assistant',
content: answer
}
]
: []),
{
role: ChatCompletionRequestMessageRoleEnum.Assistant,
tool_calls: toolCalls
}
];
/*
...
user
@@ -295,8 +311,10 @@ export const runToolWithToolChoice = async (
*/
const concatToolMessages = [
...requestMessages,
assistantToolMsgParams
...assistantToolMsgParams
] as ChatCompletionMessageParam[];
// Only toolCall tokens are counted here, Tool response tokens count towards the next reply
const tokens = await countGptMessagesTokens(concatToolMessages, tools);
/*
...
@@ -309,25 +327,12 @@ export const runToolWithToolChoice = async (
...toolsRunResponse.map((item) => item?.toolMsgParams)
];
// console.log(tokens, 'tool');
// tool assistant
const toolAssistants = toolsRunResponse
.map((item) => {
const assistantResponses = item.toolRunResponse.assistantResponses || [];
return assistantResponses;
})
.flat();
// tool node assistant
const adaptChatMessages = GPTMessages2Chats(completeMessages);
const toolNodeAssistant = adaptChatMessages.pop() as AIChatItemType;
const toolNodeAssistants = [
...assistantResponses,
...toolAssistants,
...toolNodeAssistant.value
];
// Assistant tool response adapt to chatStore
const toolNodeAssistant = GPTMessages2Chats([
...assistantToolMsgParams,
...toolsRunResponse.map((item) => item?.toolMsgParams)
])[0] as AIChatItemType;
const toolNodeAssistants = [...assistantResponses, ...toolNodeAssistant.value];
// concat tool responses
const dispatchFlowResponse = response
@@ -373,7 +378,6 @@ export const runToolWithToolChoice = async (
};
const completeMessages = filterMessages.concat(gptAssistantResponse);
const tokens = await countGptMessagesTokens(completeMessages, tools);
// console.log(tokens, 'response token');
// concat tool assistant
const toolNodeAssistant = GPTMessages2Chats([gptAssistantResponse])[0] as AIChatItemType;

View File

@@ -0,0 +1,17 @@
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/type';
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
export type Props = ModuleDispatchProps<{}>;
export type Response = DispatchNodeResultType<{}>;
export const dispatchToolParams = (props: Props): Response => {
const { params } = props;
return {
...params,
[DispatchNodeResponseKeyEnum.nodeResponse]: {
toolParamsResult: params
}
};
};

View File

@@ -70,6 +70,7 @@ import { dispatchLoop } from './loop/runLoop';
import { dispatchLoopEnd } from './loop/runLoopEnd';
import { dispatchLoopStart } from './loop/runLoopStart';
import { dispatchFormInput } from './interactive/formInput';
import { dispatchToolParams } from './agent/runTool/toolParams';
const callbackMap: Record<FlowNodeTypeEnum, Function> = {
[FlowNodeTypeEnum.workflowStart]: dispatchWorkflowStart,
@@ -87,6 +88,7 @@ const callbackMap: Record<FlowNodeTypeEnum, Function> = {
[FlowNodeTypeEnum.queryExtension]: dispatchQueryExtension,
[FlowNodeTypeEnum.tools]: dispatchRunTools,
[FlowNodeTypeEnum.stopTool]: dispatchStopToolCall,
[FlowNodeTypeEnum.toolParams]: dispatchToolParams,
[FlowNodeTypeEnum.lafModule]: dispatchLafRequest,
[FlowNodeTypeEnum.ifElseNode]: dispatchIfElse,
[FlowNodeTypeEnum.variableUpdate]: dispatchUpdateVariable,
@@ -105,6 +107,7 @@ const callbackMap: Record<FlowNodeTypeEnum, Function> = {
[FlowNodeTypeEnum.pluginConfig]: () => Promise.resolve(),
[FlowNodeTypeEnum.emptyNode]: () => Promise.resolve(),
[FlowNodeTypeEnum.globalVariable]: () => Promise.resolve(),
[FlowNodeTypeEnum.comment]: () => Promise.resolve(),
[FlowNodeTypeEnum.runApp]: dispatchAppRequest // abandoned
};
@@ -555,6 +558,14 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
dispatchRes[item.key] = valueTypeFormat(item.defaultValue, item.valueType);
});
// Update new variables
if (dispatchRes[DispatchNodeResponseKeyEnum.newVariables]) {
variables = {
...variables,
...dispatchRes[DispatchNodeResponseKeyEnum.newVariables]
};
}
return {
node,
runStatus: 'run',

View File

@@ -38,6 +38,7 @@ export const dispatchLoop = async (props: Props): Promise<Response> => {
const loopDetail: ChatHistoryItemResType[] = [];
let assistantResponses: AIChatItemValueItemType[] = [];
let totalPoints = 0;
let newVariables: Record<string, any> = props.variables;
for await (const item of loopInputArray) {
const response = await dispatchWorkFlow({
@@ -72,6 +73,10 @@ export const dispatchLoop = async (props: Props): Promise<Response> => {
assistantResponses.push(...response.assistantResponses);
totalPoints = response.flowUsages.reduce((acc, usage) => acc + usage.totalPoints, 0);
newVariables = {
...newVariables,
...response.newVariables
};
}
return {
@@ -88,6 +93,7 @@ export const dispatchLoop = async (props: Props): Promise<Response> => {
moduleName: name
}
],
[NodeOutputKeyEnum.loopArray]: outputValueArr
[NodeOutputKeyEnum.loopArray]: outputValueArr,
[DispatchNodeResponseKeyEnum.newVariables]: newVariables
};
};

View File

@@ -26,7 +26,7 @@ type RunPluginResponse = DispatchNodeResultType<{}>;
export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPluginResponse> => {
const {
node: { pluginId },
node: { pluginId, version },
runningAppInfo,
query,
params: { system_forbid_stream = false, ...data } // Plugin input
@@ -45,7 +45,7 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
per: ReadPermissionVal
});
const plugin = await getChildAppRuntimeById(pluginId);
const plugin = await getChildAppRuntimeById(pluginId, version);
const outputFilterMap =
plugin.nodes
@@ -91,8 +91,9 @@ export const dispatchRunPlugin = async (props: RunPluginProps): Promise<RunPlugi
: {}),
runningAppInfo: {
id: String(plugin.id),
teamId: plugin.teamId || '',
tmbId: pluginData?.tmbId || ''
// 如果是系统插件,则使用当前团队的 teamId 和 tmbId
teamId: plugin.teamId || runningAppInfo.teamId,
tmbId: pluginData?.tmbId || runningAppInfo.tmbId
},
variables: runtimeVariables,
query: getPluginRunUserQuery({

View File

@@ -16,7 +16,7 @@ import { chatValue2RuntimePrompt, runtimePrompt2ChatsValue } from '@fastgpt/glob
import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/type';
import { authAppByTmbId } from '../../../../support/permission/app/auth';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { getAppLatestVersion } from '../../../app/controller';
import { getAppVersionById } from '../../../app/version/controller';
type Props = ModuleDispatchProps<{
[NodeInputKeyEnum.userChatInput]: string;
@@ -34,8 +34,7 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
runningAppInfo,
histories,
query,
mode,
node: { pluginId },
node: { pluginId: appId, version },
workflowStreamResponse,
params,
variables
@@ -45,19 +44,23 @@ export const dispatchRunAppNode = async (props: Props): Promise<Response> => {
if (!userChatInput) {
return Promise.reject('Input is empty');
}
if (!pluginId) {
if (!appId) {
return Promise.reject('pluginId is empty');
}
// Auth the app by tmbId(Not the user, but the workflow user)
const { app: appData } = await authAppByTmbId({
appId: pluginId,
appId: appId,
tmbId: runningAppInfo.tmbId,
per: ReadPermissionVal
});
const { nodes, edges, chatConfig } = await getAppLatestVersion(pluginId);
const childStreamResponse = system_forbid_stream ? false : props.stream;
const { nodes, edges, chatConfig } = await getAppVersionById({
appId,
versionId: version,
app: appData
});
const childStreamResponse = system_forbid_stream ? false : props.stream;
// Auto line
if (childStreamResponse) {
workflowStreamResponse?.({

View File

@@ -77,8 +77,18 @@ try {
// timer task. Get standard plan;Get free plan;Clear expired extract plan
SubSchema.index({ type: 1, expiredTime: -1, currentSubLevel: 1 });
// unique
SubSchema.index({ teamId: 1, type: 1, currentSubLevel: 1 }, { unique: true });
// 修改后的唯一索引
SubSchema.index(
{
teamId: 1,
type: 1,
currentSubLevel: 1
},
{
unique: true,
partialFilterExpression: { type: SubTypeEnum.standard }
}
);
} catch (error) {
console.log(error);
}

View File

@@ -7,6 +7,7 @@ export const iconPaths = {
closeSolid: () => import('./icons/closeSolid.svg'),
collectionLight: () => import('./icons/collectionLight.svg'),
collectionSolid: () => import('./icons/collectionSolid.svg'),
comment: () => import('./icons/comment.svg'),
'common/add2': () => import('./icons/common/add2.svg'),
'common/addCircleLight': () => import('./icons/common/addCircleLight.svg'),
'common/addLight': () => import('./icons/common/addLight.svg'),
@@ -37,6 +38,8 @@ export const iconPaths = {
'common/importLight': () => import('./icons/common/importLight.svg'),
'common/info': () => import('./icons/common/info.svg'),
'common/inviteLight': () => import('./icons/common/inviteLight.svg'),
'common/language/America': () => import('./icons/common/language/America.svg'),
'common/language/China': () => import('./icons/common/language/China.svg'),
'common/language/en': () => import('./icons/common/language/en.svg'),
'common/language/zh': () => import('./icons/common/language/zh.svg'),
'common/leftArrowLight': () => import('./icons/common/leftArrowLight.svg'),
@@ -210,8 +213,10 @@ export const iconPaths = {
'core/workflow/runSkip': () => import('./icons/core/workflow/runSkip.svg'),
'core/workflow/runSuccess': () => import('./icons/core/workflow/runSuccess.svg'),
'core/workflow/running': () => import('./icons/core/workflow/running.svg'),
'core/workflow/template/BI': () => import('./icons/core/workflow/template/BI.svg'),
'core/workflow/template/FileRead': () => import('./icons/core/workflow/template/FileRead.svg'),
'core/workflow/template/aiChat': () => import('./icons/core/workflow/template/aiChat.svg'),
'core/workflow/template/baseChart': () => import('./icons/core/workflow/template/baseChart.svg'),
'core/workflow/template/codeRun': () => import('./icons/core/workflow/template/codeRun.svg'),
'core/workflow/template/customFeedback': () =>
import('./icons/core/workflow/template/customFeedback.svg'),
@@ -245,15 +250,17 @@ export const iconPaths = {
'core/workflow/template/reply': () => import('./icons/core/workflow/template/reply.svg'),
'core/workflow/template/runApp': () => import('./icons/core/workflow/template/runApp.svg'),
'core/workflow/template/stopTool': () => import('./icons/core/workflow/template/stopTool.svg'),
'core/workflow/template/toolkitActive': () =>
import('./icons/core/workflow/template/toolkitActive.svg'),
'core/workflow/template/toolkitInactive': () =>
import('./icons/core/workflow/template/toolkitInactive.svg'),
'core/workflow/template/systemConfig': () =>
import('./icons/core/workflow/template/systemConfig.svg'),
'core/workflow/template/textConcat': () =>
import('./icons/core/workflow/template/textConcat.svg'),
'core/workflow/template/toolCall': () => import('./icons/core/workflow/template/toolCall.svg'),
'core/workflow/template/toolParams': () =>
import('./icons/core/workflow/template/toolParams.svg'),
'core/workflow/template/toolkitActive': () =>
import('./icons/core/workflow/template/toolkitActive.svg'),
'core/workflow/template/toolkitInactive': () =>
import('./icons/core/workflow/template/toolkitInactive.svg'),
'core/workflow/template/userSelect': () =>
import('./icons/core/workflow/template/userSelect.svg'),
'core/workflow/template/variable': () => import('./icons/core/workflow/template/variable.svg'),

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 17" >
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.7746 2.90224H5.22543C4.67152 2.90224 4.33762 2.90331 4.08927 2.92394C3.91796 2.93817 3.85352 2.95783 3.83939 2.96308C3.74318 3.01345 3.66463 3.09199 3.61426 3.18821C3.60902 3.20234 3.58935 3.26677 3.57512 3.43809C3.5545 3.68644 3.55343 4.02034 3.55343 4.57424V12.4544C3.55343 13.1464 3.55481 13.5712 3.58283 13.8723C3.58462 13.8915 3.58646 13.9093 3.58832 13.9258C3.60297 13.9181 3.61873 13.9096 3.63562 13.9002C3.90035 13.754 4.2523 13.5161 4.82438 13.1268L6.49832 11.9875C6.51469 11.9764 6.53278 11.9638 6.55248 11.9502C6.7415 11.8192 7.07919 11.5851 7.47593 11.4903C7.79844 11.4133 8.13446 11.4124 8.45734 11.4879C8.85455 11.5808 9.19338 11.8131 9.38304 11.9432C9.40281 11.9568 9.42096 11.9692 9.43739 11.9803L11.1789 13.153C11.7501 13.5377 12.1011 13.7724 12.365 13.9165C12.3817 13.9256 12.3974 13.934 12.4119 13.9415C12.4137 13.9252 12.4156 13.9076 12.4173 13.8886C12.4452 13.5892 12.4466 13.167 12.4466 12.4784V4.57424C12.4466 4.02034 12.4455 3.68644 12.4249 3.43809C12.4107 3.26677 12.391 3.20233 12.3857 3.18821C12.3354 3.09199 12.2568 3.01345 12.1606 2.96308C12.1465 2.95783 12.082 2.93816 11.9107 2.92394C11.6624 2.90331 11.3285 2.90224 10.7746 2.90224ZM2.43025 2.57509C2.22009 2.97967 2.22009 3.51119 2.22009 4.57424V12.4544C2.22009 13.7844 2.22009 14.4495 2.50121 14.8107C2.73556 15.1118 3.08767 15.2981 3.46845 15.3224C3.92523 15.3516 4.47501 14.9774 5.57457 14.229L7.24851 13.0898C7.51151 12.9108 7.64301 12.8213 7.78582 12.7871C7.90675 12.7582 8.03276 12.7579 8.15384 12.7862C8.29681 12.8197 8.42875 12.9085 8.69263 13.0862L10.4342 14.259C11.5318 14.9981 12.0805 15.3677 12.5361 15.337C12.9159 15.3115 13.2667 15.1248 13.5 14.824C13.7799 14.4633 13.7799 13.8017 13.7799 12.4784V4.57424C13.7799 3.51119 13.7799 2.97967 13.5697 2.57509C13.3926 2.23416 13.1147 1.95617 12.7737 1.77907C12.3691 1.56891 11.8376 1.56891 10.7746 1.56891H5.22543C4.16238 1.56891 3.63085 1.56891 3.22627 1.77907C2.88534 1.95617 2.60736 2.23416 2.43025 2.57509Z" />
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,56 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 10" fill="none">
<path d="M14 0H0V9.31222H14V0Z" fill="#B31942"/>
<path d="M0 1.07422H14H0ZM14 2.50687H0H14ZM0 3.93952H14H0ZM14 5.37217H0H14ZM0 6.80481H14H0ZM14 8.23746H0H14Z" fill="#000008"/>
<path d="M0 1.07422H14M14 2.50687H0M0 3.93952H14M14 5.37217H0M0 6.80481H14M14 8.23746H0" stroke="white" stroke-width="0.716325"/>
<path d="M7.07729 0H0V5.01427H7.07729V0Z" fill="#0A3161"/>
<path d="M0.589645 0.214844L0.758063 0.733181L0.317139 0.412831H0.862151L0.421227 0.733181L0.589645 0.214844Z" fill="white"/>
<path d="M0.589645 1.21777L0.758063 1.73611L0.317139 1.41576H0.862151L0.421227 1.73611L0.589645 1.21777Z" fill="white"/>
<path d="M0.589645 2.2207L0.758063 2.73904L0.317139 2.41869H0.862151L0.421227 2.73904L0.589645 2.2207Z" fill="white"/>
<path d="M0.589645 3.22363L0.758063 3.74197L0.317139 3.42162H0.862151L0.421227 3.74197L0.589645 3.22363Z" fill="white"/>
<path d="M0.589645 4.22607L0.758063 4.74441L0.317139 4.42406H0.862151L0.421227 4.74441L0.589645 4.22607Z" fill="white"/>
<path d="M1.17924 0.71582L1.34766 1.23416L0.906738 0.913808H1.45175L1.01083 1.23416L1.17924 0.71582Z" fill="white"/>
<path d="M1.17924 1.71875L1.34766 2.23709L0.906738 1.91674H1.45175L1.01083 2.23709L1.17924 1.71875Z" fill="white"/>
<path d="M1.17924 2.72168L1.34766 3.24002L0.906738 2.91967H1.45175L1.01083 3.24002L1.17924 2.72168Z" fill="white"/>
<path d="M1.17924 3.72461L1.34766 4.24295L0.906738 3.9226H1.45175L1.01083 4.24295L1.17924 3.72461Z" fill="white"/>
<path d="M1.76811 0.215332L1.93653 0.733669L1.49561 0.413319H2.04062L1.59969 0.733669L1.76811 0.215332Z" fill="white"/>
<path d="M1.76811 1.21826L1.93653 1.7366L1.49561 1.41625H2.04062L1.59969 1.7366L1.76811 1.21826Z" fill="white"/>
<path d="M1.76811 2.22119L1.93653 2.73953L1.49561 2.41918H2.04062L1.59969 2.73953L1.76811 2.22119Z" fill="white"/>
<path d="M1.76811 3.22412L1.93653 3.74246L1.49561 3.42211H2.04062L1.59969 3.74246L1.76811 3.22412Z" fill="white"/>
<path d="M1.76836 4.22656L1.93677 4.7449L1.49585 4.42455H2.04086L1.59994 4.7449L1.76836 4.22656Z" fill="white"/>
<path d="M2.3582 0.716309L2.52662 1.23465L2.08569 0.914296H2.63071L2.18978 1.23465L2.3582 0.716309Z" fill="white"/>
<path d="M2.3582 1.71924L2.52662 2.23758L2.08569 1.91723H2.63071L2.18978 2.23758L2.3582 1.71924Z" fill="white"/>
<path d="M2.3582 2.72217L2.52662 3.24051L2.08569 2.92016H2.63071L2.18978 3.24051L2.3582 2.72217Z" fill="white"/>
<path d="M2.3582 3.7251L2.52662 4.24343L2.08569 3.92308H2.63071L2.18978 4.24343L2.3582 3.7251Z" fill="white"/>
<path d="M2.94682 0.21582L3.11524 0.734158L2.67432 0.413808H3.21933L2.7784 0.734158L2.94682 0.21582Z" fill="white"/>
<path d="M2.94682 1.21875L3.11524 1.73709L2.67432 1.41674H3.21933L2.7784 1.73709L2.94682 1.21875Z" fill="white"/>
<path d="M2.94682 2.22168L3.11524 2.74002L2.67432 2.41967H3.21933L2.7784 2.74002L2.94682 2.22168Z" fill="white"/>
<path d="M2.94682 3.22461L3.11524 3.74295L2.67432 3.4226H3.21933L2.7784 3.74295L2.94682 3.22461Z" fill="white"/>
<path d="M2.94731 4.22705L3.11573 4.74539L2.6748 4.42504H3.21982L2.77889 4.74539L2.94731 4.22705Z" fill="white"/>
<path d="M3.53715 0.716797L3.70557 1.23513L3.26465 0.914784H3.80966L3.36874 1.23513L3.53715 0.716797Z" fill="white"/>
<path d="M3.53715 1.71973L3.70557 2.23806L3.26465 1.91771H3.80966L3.36874 2.23806L3.53715 1.71973Z" fill="white"/>
<path d="M3.53715 2.72266L3.70557 3.24099L3.26465 2.92064H3.80966L3.36874 3.24099L3.53715 2.72266Z" fill="white"/>
<path d="M3.53715 3.72559L3.70557 4.24392L3.26465 3.92357H3.80966L3.36874 4.24392L3.53715 3.72559Z" fill="white"/>
<path d="M4.12895 0.216309L4.29737 0.734646L3.85645 0.414296H4.40146L3.96053 0.734646L4.12895 0.216309Z" fill="white"/>
<path d="M4.12895 1.21924L4.29737 1.73758L3.85645 1.41723H4.40146L3.96053 1.73758L4.12895 1.21924Z" fill="white"/>
<path d="M4.12895 2.22217L4.29737 2.74051L3.85645 2.42016H4.40146L3.96053 2.74051L4.12895 2.22217Z" fill="white"/>
<path d="M4.12895 3.2251L4.29737 3.74343L3.85645 3.42308H4.40146L3.96053 3.74343L4.12895 3.2251Z" fill="white"/>
<path d="M4.12846 4.22754L4.29688 4.74588L3.85596 4.42553H4.40097L3.96005 4.74588L4.12846 4.22754Z" fill="white"/>
<path d="M4.71831 0.716797L4.88673 1.23513L4.4458 0.914784H4.99081L4.54989 1.23513L4.71831 0.716797Z" fill="white"/>
<path d="M4.71831 1.71973L4.88673 2.23806L4.4458 1.91771H4.99081L4.54989 2.23806L4.71831 1.71973Z" fill="white"/>
<path d="M4.71831 2.72266L4.88673 3.24099L4.4458 2.92064H4.99081L4.54989 3.24099L4.71831 2.72266Z" fill="white"/>
<path d="M4.71831 3.72559L4.88673 4.24392L4.4458 3.92357H4.99081L4.54989 4.24392L4.71831 3.72559Z" fill="white"/>
<path d="M5.30839 0.21582L5.47681 0.734158L5.03589 0.413808H5.5809L5.13998 0.734158L5.30839 0.21582Z" fill="white"/>
<path d="M5.30839 1.21875L5.47681 1.73709L5.03589 1.41674H5.5809L5.13998 1.73709L5.30839 1.21875Z" fill="white"/>
<path d="M5.30839 2.22168L5.47681 2.74002L5.03589 2.41967H5.5809L5.13998 2.74002L5.30839 2.22168Z" fill="white"/>
<path d="M5.30839 3.22461L5.47681 3.74295L5.03589 3.4226H5.5809L5.13998 3.74295L5.30839 3.22461Z" fill="white"/>
<path d="M5.30815 4.22705L5.47657 4.74539L5.03564 4.42504H5.58066L5.13973 4.74539L5.30815 4.22705Z" fill="white"/>
<path d="M5.89799 0.716309L6.06641 1.23465L5.62549 0.914296H6.1705L5.72958 1.23465L5.89799 0.716309Z" fill="white"/>
<path d="M5.89799 1.71924L6.06641 2.23758L5.62549 1.91723H6.1705L5.72958 2.23758L5.89799 1.71924Z" fill="white"/>
<path d="M5.89799 2.72217L6.06641 3.24051L5.62549 2.92016H6.1705L5.72958 3.24051L5.89799 2.72217Z" fill="white"/>
<path d="M5.89799 3.72461L6.06641 4.24295L5.62549 3.9226H6.1705L5.72958 4.24295L5.89799 3.72461Z" fill="white"/>
<path d="M6.48759 0.215332L6.65601 0.733669L6.21509 0.413319H6.7601L6.31918 0.733669L6.48759 0.215332Z" fill="white"/>
<path d="M6.48735 1.21826L6.65577 1.7366L6.21484 1.41625H6.75986L6.31893 1.7366L6.48735 1.21826Z" fill="white"/>
<path d="M6.48735 2.22119L6.65577 2.73953L6.21484 2.41918H6.75986L6.31893 2.73953L6.48735 2.22119Z" fill="white"/>
<path d="M6.48735 3.22412L6.65577 3.74246L6.21484 3.42211H6.75986L6.31893 3.74246L6.48735 3.22412Z" fill="white"/>
<path d="M6.48735 4.22656L6.65577 4.7449L6.21484 4.42455H6.75986L6.31893 4.7449L6.48735 4.22656Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 10" fill="none">
<path d="M0 0H14V9.31222H0V0Z" fill="#EE1C25"/>
<path d="M1.28535 3.71454L2.16821 1.06592L3.05108 3.71454L0.696777 2.09594H3.63965L1.28535 3.71454Z" fill="#FFFF00"/>
<path d="M5.11473 1.11669L4.20615 1.31803L4.8122 0.611803L4.75276 1.5623L4.24856 0.720824L5.11473 1.11669Z" fill="#FFFF00"/>
<path d="M6.03166 2.27839L5.11611 2.11159L5.94863 1.69569L5.5252 2.54873L5.38682 1.57757L6.03166 2.27839Z" fill="#FFFF00"/>
<path d="M5.89789 3.90911L5.12998 3.3834L6.05973 3.34321L5.32522 3.94939L5.59495 3.00623L5.89789 3.90911Z" fill="#FFFF00"/>
<path d="M4.74297 4.97429L4.23769 4.19279L5.11081 4.51482L4.19918 4.79026L4.81225 4.02447L4.74297 4.97429Z" fill="#FFFF00"/>
</svg>

After

Width:  |  Height:  |  Size: 763 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1727082813249" class="icon" viewBox="0 0 1102 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8007" xmlns:xlink="http://www.w3.org/1999/xlink" width="215.234375" height="200"><path d="M78.613268 173.292308v598.96123a98.461538 98.461538 0 0 0 97.831384 98.461539h749.883077a97.831385 97.831385 0 0 0 97.831385-98.461539V173.371077A98.461538 98.461538 0 0 0 926.327729 74.830769H176.444652A97.831385 97.831385 0 0 0 78.613268 173.292308zM0.001575 158.562462A157.853538 157.853538 0 0 1 157.855114 0h787.062154A158.562462 158.562462 0 0 1 1102.770806 158.483692v628.578462a157.853538 157.853538 0 0 1-157.853538 158.562461H157.855114A158.562462 158.562462 0 0 1 0.001575 787.062154v-628.578462z" fill="#13227a" fill-opacity=".651" p-id="8008"></path><path d="M596.127114 425.038769H419.447729a14.966154 14.966154 0 0 1-11.815384-14.178461V234.338462a14.966154 14.966154 0 0 1 14.966153-14.966154 182.902154 182.902154 0 0 1 188.416 190.857846 14.966154 14.966154 0 0 1-14.966153 14.966154zM363.915422 682.614154a215.118769 215.118769 0 0 1-208.029539-211.889231 198.656 198.656 0 0 1 193.929846-204.169846 15.753846 15.753846 0 0 1 15.753846 16.541538v171.08677c0 15.753846 17.250462 16.541538 17.250462 16.541538h173.449846a15.753846 15.753846 0 0 1 16.541539 15.753846 204.878769 204.878769 0 0 1-208.896 196.135385z m303.970461-185.895385v149.897846c0 20.48 12.603077 36.076308 27.490462 36.076308h29.065846c14.887385 0 27.411692-16.462769 27.411692-36.076308V496.64c0-20.401231-12.524308-36.076308-27.411692-36.076308h-29.932308c-14.887385 0-27.411692 16.462769-27.411692 36.076308h0.787692z m182.980923 145.959385c0 20.401231 11.815385 36.076308 26.702769 36.076308h29.065847c14.887385 0 27.490462-16.541538 27.490461-36.076308V283.096615c0-20.48-11.815385-36.155077-27.569231-36.155077h-28.987077c-14.966154 0-26.702769 16.541538-26.702769 36.155077v362.653539-3.150769z" fill="#13227a" fill-opacity=".651" p-id="8009"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1727254154161" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4326" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M732.928 241.1008c0 8.5504 9.8816 15.4624 22.1696 15.4624 12.2368 0 22.1696-6.912 22.1696-15.4624V162.5088c0-8.5504-9.9328-15.4624-22.1696-15.4624H642.56c-12.288 0-22.1696 6.912-22.1696 15.4624 0 8.6016 9.9328 15.4624 22.1696 15.4624h60.8768l-194.0992 135.68-60.0576-41.984a27.904 27.904 0 0 0-15.6672-4.4544h-0.0512a27.4432 27.4432 0 0 0-15.616 4.5568L211.968 416.256c-8.6528 6.0416-8.6528 15.872 0.1024 21.8624a27.8016 27.8016 0 0 0 15.616 4.5056 27.648 27.648 0 0 0 15.7184-4.5568L433.664 304.5888l60.0064 41.8816c8.2944 5.8368 23.04 5.8368 31.3344 0l207.9232-145.2032v39.8336z m-47.5648 69.9392H778.24v297.728h-92.8768V311.04zM530.0736 372.224h92.928v236.544H530.0736V372.224zM374.7328 431.9744h92.9792v176.7936H374.7328V431.9744zM219.4432 497.4592h92.928v111.3088H219.4432V497.4592z" fill="#1296db" p-id="4327"></path><path d="M926.9248 1024H89.9584A64.4096 64.4096 0 0 1 25.6 959.6416V64.3584C25.6 28.8256 54.4256 0 89.9584 0h836.9152c35.4816 0 64.3072 28.8256 64.3584 64.3584v895.232c0 35.584-28.8256 64.4096-64.3072 64.4096z m0-956.0064H89.9584v892.0576h836.9152V67.9936z" fill="#1296db" p-id="4328"></path><path d="M629.1456 768h-394.24v-64h394.24V768z m168.96 128.0512h-563.2v-64h563.2v64z" fill="#1296db" p-id="4329"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,10 @@
<svg viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="36" height="36" fill="url(#paint0_linear_10998_23397)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.9999 8C9.79074 8 7.99988 9.79086 7.99988 12V24C7.99988 26.2091 9.79074 28 11.9999 28H23.9999C26.209 28 27.9999 26.2091 27.9999 24V12C27.9999 9.79086 26.209 8 23.9999 8H11.9999ZM17.3939 13.6H24.1231C24.6202 13.6 25.0231 14.0029 25.0231 14.5C25.0231 14.997 24.6202 15.4 24.1231 15.4H17.3939C17.0436 16.223 16.2275 16.8 15.2767 16.8C14.3258 16.8 13.5097 16.223 13.1594 15.4H11.8767C11.3796 15.4 10.9767 14.997 10.9767 14.5C10.9767 14.0029 11.3796 13.6 11.8767 13.6H13.1594C13.5097 12.777 14.3258 12.2 15.2767 12.2C16.2275 12.2 17.0436 12.777 17.3939 13.6ZM18.6059 20.6H11.8767C11.3796 20.6 10.9767 21.0029 10.9767 21.5C10.9767 21.997 11.3796 22.4 11.8767 22.4H18.6058C18.9561 23.223 19.7722 23.8 20.7231 23.8C21.674 23.8 22.4901 23.223 22.8403 22.4H24.1231C24.6202 22.4 25.0231 21.997 25.0231 21.5C25.0231 21.0029 24.6202 20.6 24.1231 20.6H22.8403C22.4901 19.777 21.674 19.2 20.7231 19.2C19.7722 19.2 18.9561 19.777 18.6059 20.6Z" fill="white"/>
<defs>
<linearGradient id="paint0_linear_10998_23397" x1="18" y1="0" x2="5.5" y2="33" gradientUnits="userSpaceOnUse">
<stop stop-color="#91A9FE"/>
<stop offset="1" stop-color="#BA84FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -32,9 +32,9 @@ import { useDeepCompareEffect } from 'ahooks';
import VariablePickerPlugin from './plugins/VariablePickerPlugin';
export default function Editor({
h = 200,
minH = 200,
maxH = 400,
maxLength,
showResize = true,
showOpenModal = true,
onOpenModal,
variables,
@@ -46,9 +46,9 @@ export default function Editor({
isFlow,
bg = 'white'
}: {
h?: number;
minH?: number;
maxH?: number;
maxLength?: number;
showResize?: boolean;
showOpenModal?: boolean;
onOpenModal?: () => void;
variables: EditorVariablePickerType[];
@@ -62,7 +62,6 @@ export default function Editor({
}) {
const [key, setKey] = useState(getNanoid(6));
const [_, startSts] = useTransition();
const [height, setHeight] = useState(h);
const [focus, setFocus] = useState(false);
const initialConfig = {
@@ -74,25 +73,6 @@ export default function Editor({
}
};
const initialY = useRef(0);
const handleMouseDown = useCallback((e: React.MouseEvent) => {
initialY.current = e.clientY;
const handleMouseMove = (e: MouseEvent) => {
const deltaY = e.clientY - initialY.current;
setHeight((prevHeight) => (prevHeight + deltaY < h * 0.5 ? h * 0.5 : prevHeight + deltaY));
initialY.current = e.clientY;
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}, []);
useDeepCompareEffect(() => {
if (focus) return;
setKey(getNanoid(6));
@@ -103,7 +83,6 @@ export default function Editor({
className="nowheel"
position={'relative'}
width={'full'}
h={`${height}px`}
cursor={'text'}
color={'myGray.700'}
bg={focus ? 'white' : bg}
@@ -114,6 +93,10 @@ export default function Editor({
contentEditable={
<ContentEditable
className={isFlow ? styles.contentEditable_isFlow : styles.contentEditable}
style={{
minHeight: `${minH}px`,
maxHeight: `${maxH}px`
}}
/>
}
placeholder={
@@ -158,19 +141,6 @@ export default function Editor({
<VariablePickerPlugin variables={variableLabels.length > 0 ? [] : variables} />
<OnBlurPlugin onBlur={onBlur} />
</LexicalComposer>
{showResize && (
<Box
position={'absolute'}
right={'0'}
bottom={'-1'}
zIndex={9}
cursor={'ns-resize'}
px={'2px'}
onMouseDown={handleMouseDown}
>
<MyIcon name={'common/editor/resizer'} width={'14px'} height={'14px'} />
</Box>
)}
{showOpenModal && (
<Box
zIndex={10}

View File

@@ -10,13 +10,13 @@ import { useCallback } from 'react';
const PromptEditor = ({
showOpenModal = true,
showResize = true,
variables = [],
variableLabels = [],
value,
onChange,
onBlur,
h,
minH,
maxH,
maxLength,
placeholder,
title,
@@ -24,13 +24,13 @@ const PromptEditor = ({
bg = 'white'
}: {
showOpenModal?: boolean;
showResize?: boolean;
variables?: EditorVariablePickerType[];
variableLabels?: EditorVariableLabelPickerType[];
value?: string;
onChange?: (text: string) => void;
onBlur?: (text: string) => void;
h?: number;
minH?: number;
maxH?: number;
maxLength?: number;
placeholder?: string;
title?: string;
@@ -58,12 +58,12 @@ const PromptEditor = ({
return (
<>
<Editor
showResize={showResize}
showOpenModal={showOpenModal}
onOpenModal={onOpen}
variables={variables}
variableLabels={variableLabels}
h={h}
minH={minH}
maxH={maxH}
maxLength={maxLength}
value={value}
onChange={onChangeInput}
@@ -75,9 +75,9 @@ const PromptEditor = ({
<MyModal isOpen={isOpen} onClose={onClose} iconSrc="modal/edit" title={title} w={'full'}>
<ModalBody>
<Editor
h={400}
minH={400}
maxH={400}
maxLength={maxLength}
showResize
showOpenModal={false}
variables={variables}
variableLabels={variableLabels}

View File

@@ -67,7 +67,6 @@
"logs_message_total": "Total Messages",
"logs_title": "Title",
"mark_count": "Number of Marked Answers",
"module.Confirm Sync": "Will update to the latest template configuration. Fields not in the template will be deleted (including all custom fields). It is recommended to copy a node first, then update the original node version.",
"module.Custom Title Tip": "This title will be displayed during the conversation.",
"module.No Modules": "No Plugins Found",
"module.type": "\"{{type}}\" type\n{{description}}",

View File

@@ -28,6 +28,7 @@
"UnKnow": "Unknown",
"Warning": "Warning",
"add_new": "Add New",
"add_new_param": "Add new param",
"back": "Back",
"chose_condition": "Choose Condition",
"chosen": "Chosen",
@@ -340,7 +341,8 @@
"core.app.share.Not share link": "No Share Link Created",
"core.app.share.Role check": "Identity Verification",
"core.app.tip.Add a intro to app": "Give the app an introduction",
"core.app.tip.chatNodeSystemPromptTip": "Fixed guide words for the model. By adjusting this content, you can guide the model's chat direction. This content will be fixed at the beginning of the context. You can use / to insert variables.\nIf a Dataset is associated, you can also guide the model when to call the Dataset search by appropriate description. For example:\nYou are an assistant for the movie 'Interstellar'. When users ask about content related to 'Interstellar', please search the Dataset and answer based on the search results.",
"core.app.tip.chatNodeSystemPromptTip": "Enter a prompt here",
"core.app.tip.systemPromptTip": "Fixed guide words for the model. By adjusting this content, you can guide the model's chat direction. This content will be fixed at the beginning of the context. You can use / to insert variables.\nIf a Dataset is associated, you can also guide the model when to call the Dataset search by appropriate description. For example:\nYou are an assistant for the movie 'Interstellar'. When users ask about content related to 'Interstellar', please search the Dataset and answer based on the search results.",
"core.app.tip.variableTip": "Before the conversation starts, you can ask the user to fill in some content as specific variables for this round of conversation. This module is located after the opening guide.\nVariables can be injected into other modules' string type inputs in the form of {{variable key}}, such as prompts, delimiters, etc.",
"core.app.tip.welcomeTextTip": "Before each conversation starts, send an initial content. Supports standard Markdown syntax. Additional tags that can be used:\n[Quick Key]: Users can directly send the question by clicking",
"core.app.tool_label.doc": "Documentation",
@@ -1109,7 +1111,6 @@
"tag_list": "Tag List",
"team_tag": "Team Tag",
"textarea_variable_picker_tip": "Enter \"/\" to select a variable",
"tool_field": "Tool Field Parameter Configuration",
"undefined_var": "Referenced an undefined variable, add it automatically?",
"unit.character": "Character",
"unit.minute": "Minute",

View File

@@ -1,13 +1,16 @@
{
"Chinese_ip_tip": "It is detected that you are a mainland Chinese IP, click to jump to visit the mainland China version.",
"Login": "Login",
"forget_password": "Find password",
"login_failed": "Login failed",
"login_success": "Login successful",
"no_remind": "Don't remind again",
"password_condition": "Password maximum 60 characters",
"policy_tip": "By useing, you agree to our",
"privacy": "Privacy policy",
"redirect": "Jump",
"register": "Register",
"root_password_placeholder": "The root user password is the value of the environment variable DEFAULT_ROOT_PSW",
"terms": "Terms",
"use_root_login": "Log in as root user"
}
}

View File

@@ -1,6 +1,7 @@
{
"Array_element": "Array element",
"Code": "Code",
"Confirm_sync_node": "It will be updated to the latest node configuration and fields that do not exist in the template will be deleted (including all custom fields).\n\nIf the fields are complex, it is recommended that you copy a node first and then update the original node to facilitate parameter copying.",
"Quote_prompt_setting": "Quote prompt",
"add_new_input": "Add New Input",
"add_new_output": "New output",
@@ -23,9 +24,11 @@
"contains": "Contains",
"content_to_retrieve": "Content to Retrieve",
"content_to_search": "Content to Search",
"context_menu.add_comment": "Add comment",
"create_link_error": "Error creating link",
"custom_feedback": "Custom Feedback",
"custom_input": "Custom Input",
"custom_tool_input": "Custom tool input",
"dataset_quote_role": "Role",
"dataset_quote_role_system_option_desc": "Historical records should be consistent first (recommended)",
"dataset_quote_role_tip": "When set to System, the knowledge base reference content will be placed in the system message, which can ensure the continuity of the history record, but the constraint effect may not be good.\n\nWhen set to User, the knowledge base reference content will be placed in the user message, and the {{question}} variable location needs to be specified. \nIt will have a certain impact on the consistency of historical records, but usually the constraint effect is better.",
@@ -36,6 +39,7 @@
"edit_input": "Edit Input",
"edit_output": "Edit output",
"end_with": "Ends With",
"enter_comment": "Enter comment",
"error_info_returns_empty_on_success": "Error information of code execution, returns empty on success",
"execute_a_simple_script_code_usually_for_complex_data_processing": "Execute a simple script code, usually for complex data processing.",
"execute_different_branches_based_on_conditions": "Execute different branches based on conditions.",
@@ -77,6 +81,7 @@
"intro_text_concatenation": "Can process and output fixed or incoming text. Non-string type data will be converted to string type.",
"intro_text_content_extraction": "Can extract specified data from text, such as SQL statements, search keywords, code, etc.",
"intro_tool_call_termination": "This module needs to be configured for tool calls. When this module is executed, the current tool call will be forcibly terminated, and AI will no longer answer questions based on the tool call results.",
"intro_tool_params_config": " This module works with tool calls. It creates required tool parameters. Tool calls automatically generate parameter content and pass it to corresponding function blocks.",
"is_empty": "Is Empty",
"is_equal_to": "Is Equal To",
"is_not_empty": "Is Not Empty",
@@ -155,7 +160,17 @@
"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_call_termination": "Tool Call Termination",
"tool_field": " Tool Field Parameter Configuration",
"tool_input": "Tool Input",
"tool_params.enum_placeholder": "apple \npeach \nwatermelon",
"tool_params.enum_values": "Enum values",
"tool_params.enum_values_tip": "List the possible values for this field, one per line",
"tool_params.params_description": "Description",
"tool_params.params_description_placeholder": "Name/Age/SQL statement..",
"tool_params.params_name": "Name",
"tool_params.params_name_placeholder": "name/age/sql",
"tool_params.tool_params_result": "Tool params result",
"tool_params_config": "Tool params config",
"trigger_after_application_completion": "Will be triggered after the application is fully completed",
"update_link_error": "Error updating link",
"update_specified_node_output_or_global_variable": "Can update the output value of a specified node or update global variables",

View File

@@ -67,7 +67,6 @@
"logs_message_total": "消息总数",
"logs_title": "标题",
"mark_count": "标注答案数量",
"module.Confirm Sync": "将会更新至最新的模板配置,不存在模板中的字段将会被删除(包括所有自定义字段),建议您先复制一份节点,再更新原来节点的版本。",
"module.Custom Title Tip": "该标题名字会展示在对话过程中",
"module.No Modules": "没找到插件",
"module.type": "\"{{type}}\"类型\n{{description}}",

View File

@@ -28,6 +28,7 @@
"UnKnow": "未知",
"Warning": "提示",
"add_new": "新增",
"add_new_param": "新增参数",
"back": "返回",
"chose_condition": "选择条件",
"chosen": "已选",
@@ -339,7 +340,8 @@
"core.app.share.Not share link": "没有创建分享链接",
"core.app.share.Role check": "身份校验",
"core.app.tip.Add a intro to app": "快来给应用一个介绍~",
"core.app.tip.chatNodeSystemPromptTip": "模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可通过输入 / 插入选择变量\n如果关联了知识库你还可以通过适当的描述来引导模型何时去调用知识库搜索。例如\n你是电影《星际穿越》的助手当用户询问与《星际穿越》相关的内容时请搜索知识库并结合搜索结果进行回答。",
"core.app.tip.chatNodeSystemPromptTip": "在此输入提示词",
"core.app.tip.systemPromptTip": "模型固定的引导词,通过调整该内容,可以引导模型聊天方向。该内容会被固定在上下文的开头。可通过输入 / 插入选择变量\n如果关联了知识库你还可以通过适当的描述来引导模型何时去调用知识库搜索。例如\n你是电影《星际穿越》的助手当用户询问与《星际穿越》相关的内容时请搜索知识库并结合搜索结果进行回答。",
"core.app.tip.variableTip": "可以在对话开始前,要求用户填写一些内容作为本轮对话的特定变量。该模块位于开场引导之后。\n变量可以通过 {{变量key}} 的形式注入到其他模块 string 类型的输入中,例如:提示词、限定词等",
"core.app.tip.welcomeTextTip": "每次对话开始前,发送一个初始内容。支持标准 Markdown 语法,可使用的额外标记:\n[快捷按键]:用户点击后可以直接发送该问题",
"core.app.tool_label.doc": "使用文档",
@@ -731,8 +733,8 @@
"core.module.template.empty_plugin": "空白插件",
"core.module.template.empty_workflow": "空白工作流",
"core.module.template.http body placeholder": "与 Apifox 相同的语法",
"core.module.template.self_output": "插件输出",
"core.module.template.self_input": "插件输入",
"core.module.template.self_output": "插件输出",
"core.module.template.system_config": "系统配置",
"core.module.template.system_config_info": "可以配置应用的系统参数",
"core.module.template.work_start": "流程开始",
@@ -1108,7 +1110,6 @@
"tag_list": "标签列表",
"team_tag": "团队标签",
"textarea_variable_picker_tip": "输入\"/\"可选择变量",
"tool_field": "工具字段参数配置",
"undefined_var": "引用了未定义的变量,是否自动添加?",
"unit.character": "字符",
"unit.minute": "分钟",

View File

@@ -9,5 +9,8 @@
"register": "注册账号",
"root_password_placeholder": "root 用户密码为环境变量 DEFAULT_ROOT_PSW 的值",
"terms": "服务协议",
"use_root_login": "使用 root 用户登录"
}
"use_root_login": "使用 root 用户登录",
"redirect": "跳转",
"no_remind": "不再提醒",
"Chinese_ip_tip": "检测到您是中国大陆 IP点击跳转访问中国大陆版。"
}

View File

@@ -1,6 +1,7 @@
{
"Array_element": "数组元素",
"Code": "代码",
"Confirm_sync_node": "将会更新至最新的节点配置,不存在模板中的字段将会被删除(包括所有自定义字段)。\n如果字段较为复杂建议您先复制一份节点再更新原来的节点便于参数复制。",
"Quote_prompt_setting": "引用提示词配置",
"add_new_input": "新增输入",
"add_new_output": "新增输出",
@@ -23,9 +24,12 @@
"contains": "包含",
"content_to_retrieve": "需要检索的内容",
"content_to_search": "需要检索的内容",
"contextMenu.addComment": "添加注释",
"context_menu.add_comment": "添加注释",
"create_link_error": "创建链接异常",
"custom_feedback": "自定义反馈",
"custom_input": "自定义输入",
"custom_tool_input": "自定义工具参数",
"dataset_quote_role": "角色",
"dataset_quote_role_system_option_desc": "历史记录连贯优先(推荐)",
"dataset_quote_role_tip": "设置为 System 时,将会把知识库引用内容放置到 system 消息中,可以确保历史记录的连贯性,但约束效果可能不佳,需要多调试。\n设置为 User 时,将会把知识库引用内容放置到 user 消息中,并且需要指定 {{question}} 变量位置。会对历史记录连贯性有一定影响,但通常约束效果更优。",
@@ -36,6 +40,7 @@
"edit_input": "编辑输入",
"edit_output": "编辑输出",
"end_with": "结束为",
"enter_comment": "输入注释",
"error_info_returns_empty_on_success": "代码运行错误信息,成功时返回空",
"execute_a_simple_script_code_usually_for_complex_data_processing": "执行一段简单的脚本代码,通常用于进行复杂的数据处理。",
"execute_different_branches_based_on_conditions": "根据一定的条件,执行不同的分支。",
@@ -77,6 +82,7 @@
"intro_text_concatenation": "可对固定或传入的文本进行加工后输出,非字符串类型数据最终会转成字符串类型。",
"intro_text_content_extraction": "可从文本中提取指定的数据例如sql语句、搜索关键词、代码等",
"intro_tool_call_termination": "该模块需配置工具调用使用。当该模块被执行时本次工具调用将会强制结束并且不再调用AI针对工具调用结果回答问题。",
"intro_tool_params_config": "该模块需要配合工具调用使用。可以创建所需的工具参数,工具调用将自动生成参数内容并传给对应的功能块。",
"is_empty": "为空",
"is_equal_to": "等于",
"is_not_empty": "不为空",
@@ -94,7 +100,7 @@
"length_not_equal_to": "长度不等于",
"less_than": "小于",
"less_than_or_equal_to": "小于等于",
"loop": "循环运行(测试)",
"loop": "循环运行",
"loop_body": "循环体",
"loop_end": "循环体结束",
"loop_input_array": "数组",
@@ -155,7 +161,17 @@
"text_to_extract": "需要提取的文本",
"these_variables_will_be_input_parameters_for_code_execution": "这些变量会作为代码的运行的输入参数",
"tool_call_termination": "工具调用终止",
"tool_field": "工具参数配置",
"tool_input": "工具参数",
"tool_params.enum_placeholder": "apple \npeach \nwatermelon",
"tool_params.enum_values": "枚举值(可选)",
"tool_params.enum_values_tip": "列举出该字段可能的值,每行一个",
"tool_params.params_description": "参数描述",
"tool_params.params_description_placeholder": "姓名/年龄/SQL 语句...",
"tool_params.params_name": "参数名",
"tool_params.params_name_placeholder": "name/age/sql",
"tool_params.tool_params_result": "参数配置结果",
"tool_params_config": "工具参数配置",
"trigger_after_application_completion": "将在应用完全结束后触发",
"update_link_error": "更新链接异常",
"update_specified_node_output_or_global_variable": "可以更新指定节点的输出值或更新全局变量",

View File

@@ -30,8 +30,8 @@ MILVUS_TOKEN=133964348b00b4b4e4b51bef680a61350950385c8c64a3ec16b1ab92d3c67dcc4e0
SANDBOX_URL=http://localhost:3001
# 商业版地址
PRO_URL=
# 首页路径
HOME_URL=/
# 页面的地址,用于自动补全相对路径资源的 domain
# FE_DOMAIN=http://localhost:3000
# 日志等级: debug, info, warn, error
LOG_LEVEL=debug

View File

@@ -169,7 +169,7 @@
"max": 3000,
"valueType": "WorkflowIOValueTypeEnum.string",
"label": "core.ai.Prompt",
"description": "core.app.tip.chatNodeSystemPromptTip",
"description": "core.app.tip.systemPromptTip",
"placeholder": "core.app.tip.chatNodeSystemPromptTip",
"value": ""
},

View File

@@ -166,7 +166,7 @@
"max": 3000,
"valueType": "string",
"label": "core.ai.Prompt",
"description": "core.app.tip.chatNodeSystemPromptTip",
"description": "core.app.tip.systemPromptTip",
"placeholder": "core.app.tip.chatNodeSystemPromptTip",
"value": "# Role: 资深英汉翻译专家\n\n## Background:\n你是一位经验丰富的英汉翻译专家,精通英汉互译,尤其擅长将英文文章译成流畅易懂的现代汉语。你曾多次带领团队完成大型翻译项目,译文广受好评。\n\n## Attention:\n- 翻译过程中要始终坚持\"信、达、雅\"的原则,但\"达\"尤为重要\n- 译文要符合现代汉语的表达习惯,通俗易懂,连贯流畅 \n- 避免使用过于文绉绉的表达和晦涩难懂的典故引用\n\n## Profile: \n- Author: 米开朗基杨 \n- Version: 0.2\n- Language: 中文\n- Description: 你是一位资深英汉翻译专家,精通英汉互译。你擅长将英文文章译成地道流畅的现代汉语,表达准确易懂,符合当代中文语言习惯。\n\n## Constraints:\n- 必须严格遵循四轮翻译流程:直译、意译、校审、定稿 \n- 译文要忠实原文,准确无误,不能遗漏或曲解原意\n- 译文应以现代白话文为主,避免过多使用文言文和古典诗词\n- 每一轮翻译前后必须添加【思考】和【翻译】标记\n- 最终译文使用Markdown的代码块呈现\n\n## Goals:\n- 通过四轮翻译流程,将英文原文译成高质量的现代汉语译文 \n- 译文要准确传达原文意思,语言表达力求浅显易懂,朗朗上口\n- 适度使用一些熟语俗语、流行网络用语等,增强译文的亲和力\n- 在直译的基础上,提供至少2个不同风格的意译版本供选择\n\n## Skills:\n- 精通英汉双语,具有扎实的语言功底和丰富的翻译经验\n- 擅长将英语表达习惯转换为地道自然的现代汉语\n- 对当代中文语言的发展变化有敏锐洞察,善于把握语言流行趋势\n\n## Workflow:\n1. 第一轮直译:逐字逐句忠实原文,不遗漏任何信息\n2. 第二轮意译:在直译的基础上用通俗流畅的现代汉语意译原文,至少提供2个不同风格的版本\n3. 第三轮校审:仔细审视译文,消除偏差和欠缺,使译文更加地道易懂 \n4. 第四轮定稿:择优选取,反复修改润色,最终定稿出一个简洁畅达、符合大众阅读习惯的译文\n\n## OutputFormat: \n- 每一轮翻译前用【思考】说明该轮要点\n- 每一轮翻译后用【翻译】呈现译文\n- 在\\`\\`\\`代码块中展示最终定稿译文\n\n## Suggestions:\n- 直译时力求忠实原文,但不要过于拘泥逐字逐句\n- 意译时在准确表达原意的基础上,用最朴实无华的现代汉语来表达 \n- 校审环节重点关注译文是否符合当代汉语表达习惯,是否通俗易懂\n- 定稿时适度采用一些熟语谚语、网络流行语等,使译文更接地气\n- 善于利用中文的灵活性,用不同的表述方式展现同一内容,提高译文的可读性\n\n## Initialization\n作为一名资深英汉翻译专家,你必须严格遵循翻译流程的各项要求。首先请向用户问好,介绍你将带领团队完成翻译任务,力求将英文原文译成通俗易懂的现代汉语。然后简要说明四轮翻译流程,请用户提供英文原文,开始进行翻译工作。"
},

View File

@@ -187,7 +187,7 @@
"max": 3000,
"valueType": "WorkflowIOValueTypeEnum.string",
"label": "core.ai.Prompt",
"description": "core.app.tip.chatNodeSystemPromptTip",
"description": "core.app.tip.systemPromptTip",
"placeholder": "core.app.tip.chatNodeSystemPromptTip",
"value": "请直接将我的问题翻译成{{language}},不需要回答问题。"
},

View File

@@ -148,7 +148,7 @@
"max": 3000,
"valueType": "string",
"label": "core.ai.Prompt",
"description": "core.app.tip.chatNodeSystemPromptTip",
"description": "core.app.tip.systemPromptTip",
"placeholder": "core.app.tip.chatNodeSystemPromptTip",
"value": ""
},

View File

@@ -169,7 +169,7 @@
"max": 3000,
"valueType": "WorkflowIOValueTypeEnum.string",
"label": "core.ai.Prompt",
"description": "core.app.tip.chatNodeSystemPromptTip",
"description": "core.app.tip.systemPromptTip",
"placeholder": "core.app.tip.chatNodeSystemPromptTip",
"value": ""
},

View File

@@ -174,7 +174,7 @@
"max": 3000,
"valueType": "string",
"label": "core.ai.Prompt",
"description": "core.app.tip.chatNodeSystemPromptTip",
"description": "core.app.tip.systemPromptTip",
"placeholder": "core.app.tip.chatNodeSystemPromptTip",
"value": "# Role: 资深字幕翻译专家\n\n## Background:\n你是一位经验丰富的{{source_lang}}和{{target_lang}}字幕翻译专家,精通{{source_lang}}和{{target_lang}}互译,尤其擅长将{{source_lang}}字幕译成流畅易懂的{{target_lang}}字幕。你曾多次带领团队完成大型商业电影的字幕翻译项目,所翻译的字幕广受好评。\n\n## Attention:\n- 翻译过程中要始终坚持\"信、达、雅\"的原则,但\"达\"尤为重要\n- 翻译的字幕要符合{{target_lang}}的表达习惯,通俗易懂,连贯流畅\n- 避免使用过于文绉绉的表达和晦涩难懂的典故引用 \n- 诗词歌词等内容需按原文换行和节奏分行,不破坏原排列格式 \n- 翻译对象是字幕,请进入整段文本的语境中对需要翻译的文本段进行翻译\n- <T>是标识每一帧字幕的标签,请严格按照<T>对文本的分割逐帧翻译\n\n## Constraints:\n- 必须严格遵循四轮翻译流程:直译、意译、反思、提升\n- 译文要忠实原文,准确无误,不能遗漏或曲解原意\n- 最终译文使用Markdown的代码块呈现,但是不用输出markdown这个单词\n- <T>是标识每一帧字幕的标签,请严格按照<T>对文本的分割逐帧翻译,每一帧字幕末尾不要加\\n 回车标识,且第一帧字幕开头不需要加<T>标识\n\n## Goals:\n- 通过四轮翻译流程,将{{source_lang}}字幕译成高质量的{{target_lang}}字幕\n- 翻译的字幕要准确传达原字幕意思,语言表达力求浅显易懂,朗朗上口 \n\n## Workflow:\n1. 第一轮直译:严格按照<T>逐句翻译,不遗漏任何信息\n2. 第二轮意译:在直译的基础上用通俗流畅的{{target_lang}}意译原文,逐句翻译,保留<T>标识标签\n3. 第三轮反思:仔细审视译文,分点列出一份建设性的批评和有用的建议清单以改进翻译,对每一句话提出建议,从以下四个角度展开\n (i) 准确性(纠正添加、误译、遗漏或未翻译的文本错误),\n (ii) 流畅性(应用{{target_lang}}的语法、拼写和标点规则,并确保没有不必要的重复),\n (iii) 风格(确保翻译反映源文本的风格并考虑其文化背景),\n (iv) 术语(确保术语使用一致且反映源文本所在领域,注意确保使用{{target_lang}}中的等效习语)\n4. 第四轮提升:严格遵循第三轮提出的建议对翻译修改,定稿出一个简洁畅达、符合大众观影习惯的字幕译文,保留<T>标识标签\n\n## OutputFormat:\n- 每一轮前用【思考】说明该轮要点\n- 第一轮和第二轮翻译后用【翻译】呈现译文\n- 第三轮输出建议清单,分点列出,在每一点前用*xxx*标识这条建议对应的要点,如*风格*;建议前用【思考】说明该轮要点,建议后用【建议】呈现建议\n- 第四轮在\\`\\`\\`代码块中展示最终{{target_lang}}字幕文件内容,如\\`\\`\\`xxx\\`\\`\\`\n\n## Suggestions:\n- 直译时力求忠实原文,但注意控制每帧字幕的字数,必要时进行精简压缩\n- 意译时在准确表达原意的基础上,用最朴实无华的{{target_lang}}来表达\n- 反思环节重点关注译文是否符合{{target_lang}}表达习惯,是否通俗易懂,是否准确流畅,是否术语一致\n- 提升环节采用反思环节的建议对意译环节的翻译进行修改,适度采用一些口语化的表达、网络流行语等,增强字幕的亲和力\n- 注意<T>是很重要的标识标签,请确保标签能在正确位置输出"
},

View File

@@ -100,7 +100,7 @@
"max": 3000,
"valueType": "string",
"label": "core.ai.Prompt",
"description": "core.app.tip.chatNodeSystemPromptTip",
"description": "core.app.tip.systemPromptTip",
"placeholder": "core.app.tip.chatNodeSystemPromptTip",
"value": ""
},

View File

@@ -0,0 +1,37 @@
import { langMap } from '@/web/common/utils/i18n';
import { Avatar, Box, Flex } from '@chakra-ui/react';
import MySelect from '@fastgpt/web/components/common/MySelect';
import { useI18nLng } from '@fastgpt/web/hooks/useI18n';
import { useTranslation } from 'next-i18next';
import { useMemo } from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
const I18nLngSelector = () => {
const { i18n } = useTranslation();
const { onChangeLng } = useI18nLng();
const list = useMemo(() => {
return Object.entries(langMap).map(([key, lang]) => ({
label: (
<Flex alignItems={'center'}>
<MyIcon borderRadius={'0'} mr={2} name={lang.avatar as any} w={'14px'} h={'9px'} />
<Box>{lang.label}</Box>
</Flex>
),
value: key
}));
}, []);
return (
<MySelect
value={i18n.language}
list={list}
onchange={(val: any) => {
const lang = val;
onChangeLng(lang);
}}
/>
);
};
export default I18nLngSelector;

View File

@@ -318,7 +318,8 @@ const DatasetParamsModal = ({
</Flex>
<Box mt={1}>
<PromptEditor
h={200}
minH={150}
maxH={300}
showOpenModal={false}
placeholder={t('common:core.module.QueryExtension.placeholder')}
value={cfbBgDesc}

View File

@@ -355,6 +355,12 @@ export const WholeResponseContent = ({
{/* form input */}
<Row label={t('workflow:form_input_result')} value={activeModule?.formInputResult} />
{/* tool params */}
<Row
label={t('workflow:tool_params.tool_params_result')}
value={activeModule?.toolParamsResult}
/>
</Box>
) : null;
};

View File

@@ -7,18 +7,13 @@ import { UserType } from '@fastgpt/global/support/user/type';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { useForm } from 'react-hook-form';
import { UserUpdateParams } from '@/types/user';
import { langMap } from '@/web/common/utils/i18n';
import { useRouter } from 'next/router';
import { useI18nLng } from '@fastgpt/web/hooks/useI18n';
import MySelect from '@fastgpt/web/components/common/MySelect';
import TimezoneSelect from '@fastgpt/web/components/common/MySelect/TimezoneSelect';
import I18nLngSelector from '@/components/Select/I18nLngSelector';
const Individuation = () => {
const { t, i18n } = useTranslation();
const { t } = useTranslation();
const { userInfo, updateUserInfo } = useUserStore();
const { toast } = useToast();
const { onChangeLng } = useI18nLng();
const { reset } = useForm<UserUpdateParams>({
defaultValues: userInfo as UserType
@@ -49,17 +44,7 @@ const Individuation = () => {
<Flex alignItems={'center'} w={['85%', '350px']}>
<Box flex={'0 0 80px'}>{t('common:user.Language')}:&nbsp;</Box>
<Box flex={'1 0 0'}>
<MySelect
value={i18n.language}
list={Object.entries(langMap).map(([key, lang]) => ({
label: lang.label,
value: key
}))}
onchange={(val: any) => {
const lang = val;
onChangeLng(lang);
}}
/>
<I18nLngSelector />
</Box>
</Flex>
<Flex mt={6} alignItems={'center'} w={['85%', '350px']}>

View File

@@ -2,7 +2,7 @@ import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/nex
import { NextAPI } from '@/service/middleware/entry';
import { authApp } from '@fastgpt/service/support/permission/app/auth';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
import { getAppLatestVersion } from '@fastgpt/service/core/app/version/controller';
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
import { StoreEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node';

View File

@@ -21,7 +21,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse<any>): Promise<
versionName
} = req.body as PostPublishAppProps;
const { app, tmbId } = await authApp({ appId, req, per: WritePermissionVal, authToken: true });
const { tmbId } = await authApp({ appId, req, per: WritePermissionVal, authToken: true });
const { nodes: formatNodes } = beforeUpdateAppFormat({ nodes });

View File

@@ -6,7 +6,7 @@ import { getChatModelNameListByModules } from '@/service/core/app/workflow';
import type { InitChatProps, InitChatResponse } from '@/global/core/chat/api.d';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
import { getAppLatestVersion } from '@fastgpt/service/core/app/version/controller';
import { NextAPI } from '@/service/middleware/entry';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
@@ -15,7 +15,7 @@ async function handler(
req: NextApiRequest,
res: NextApiResponse
): Promise<InitChatResponse | void> {
let { appId, chatId, loadCustomFeedbacks } = req.query as InitChatProps;
let { appId, chatId } = req.query as InitChatProps;
if (!appId) {
return jsonRes(res, {

View File

@@ -9,7 +9,7 @@ import { MongoApp } from '@fastgpt/service/core/app/schema';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
import { getAppLatestVersion } from '@fastgpt/service/core/app/version/controller';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { NextAPI } from '@/service/middleware/entry';

View File

@@ -9,7 +9,7 @@ import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
import { authTeamSpaceToken } from '@/service/support/permission/auth/team';
import { MongoTeam } from '@fastgpt/service/support/user/team/teamSchema';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
import { getAppLatestVersion } from '@fastgpt/service/core/app/version/controller';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import { NextAPI } from '@/service/middleware/entry';

View File

@@ -46,7 +46,7 @@ import { AIChatItemType, UserChatItemType } from '@fastgpt/global/core/chat/type
import { DispatchNodeResponseKeyEnum } from '@fastgpt/global/core/workflow/runtime/constants';
import { NextAPI } from '@/service/middleware/entry';
import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
import { getAppLatestVersion } from '@fastgpt/service/core/app/version/controller';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import {

View File

@@ -168,13 +168,14 @@ const EditForm = ({
<Box mt={4}>
<HStack {...LabelStyles} w={'100%'}>
<Box>{t('common:core.ai.Prompt')}</Box>
<QuestionTip label={t('common:core.app.tip.chatNodeSystemPromptTip')} />
<QuestionTip label={t('common:core.app.tip.systemPromptTip')} />
<Box flex={1} />
<VariableTip color={'myGray.500'} />
</HStack>
<Box mt={1}>
<PromptEditor
minH={150}
value={appForm.aiSettings.systemPrompt}
bg={'myGray.50'}
onChange={(text) => {
@@ -190,7 +191,7 @@ const EditForm = ({
}}
variableLabels={formatVariables}
variables={formatVariables}
placeholder={t('common:core.app.tip.chatNodeSystemPromptTip')}
placeholder={t('common:core.app.tip.systemPromptTip')}
title={t('common:core.ai.Prompt')}
/>
</Box>

View File

@@ -72,7 +72,8 @@ const SaveButton = ({
toast({
status: 'success',
title: t('app:saved_success'),
position: 'top-right'
position: 'top-right',
isClosable: true
});
onClose();
setIsSave(false);

View File

@@ -106,8 +106,12 @@ const NodeTemplatesModal = ({ isOpen, onClose }: ModuleTemplateListProps) => {
if (item.flowNodeType === FlowNodeTypeEnum.lafModule && !feConfigs.lafEnv) {
return false;
}
// tool stop
if (!hasToolNode && item.flowNodeType === FlowNodeTypeEnum.stopTool) {
// tool stop or tool params
if (
!hasToolNode &&
(item.flowNodeType === FlowNodeTypeEnum.stopTool ||
item.flowNodeType === FlowNodeTypeEnum.toolParams)
) {
return false;
}
return true;

View File

@@ -0,0 +1,83 @@
import { Box, Flex } from '@chakra-ui/react';
import React from 'react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'react-i18next';
import { nodeTemplate2FlowNode } from '@/web/core/workflow/utils';
import { CommentNode } from '@fastgpt/global/core/workflow/template/system/comment';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
import { useReactFlow } from 'reactflow';
type ContextMenuProps = {
top: number;
left: number;
};
const ContextMenu = ({ top, left }: ContextMenuProps) => {
const { t } = useTranslation();
const setNodes = useContextSelector(WorkflowContext, (ctx) => ctx.setNodes);
const setMenu = useContextSelector(WorkflowContext, (ctx) => ctx.setMenu);
const { screenToFlowPosition } = useReactFlow();
const newNode = nodeTemplate2FlowNode({
template: CommentNode,
position: screenToFlowPosition({ x: left, y: top }),
t
});
return (
<Box position="relative">
<Box
position="absolute"
top={`${top - 6}px`}
left={`${left + 10}px`}
width={0}
height={0}
borderLeft="6px solid transparent"
borderRight="6px solid transparent"
borderBottom="6px solid white"
zIndex={2}
filter="drop-shadow(0px -1px 2px rgba(0, 0, 0, 0.1))"
/>
<Flex
position={'absolute'}
top={top}
left={left}
bg={'white'}
w={'120px'}
height={9}
p={1}
rounded={'md'}
boxShadow={'0px 2px 4px 0px #A1A7B340'}
className="context-menu"
alignItems={'center'}
color={'myGray.600'}
cursor={'pointer'}
_hover={{
color: 'primary.500'
}}
onClick={() => {
setMenu(null);
setNodes((state) => {
const newState = state
.map((node) => ({
...node,
selected: false
}))
// @ts-ignore
.concat(newNode);
return newState;
});
}}
zIndex={1}
>
<MyIcon name="comment" w={'16px'} h={'16px'} ml={1} />
<Box fontSize={'12px'} fontWeight={'500'} ml={1.5}>
{t('workflow:context_menu.add_comment')}
</Box>
</Flex>
</Box>
);
};
export default React.memo(ContextMenu);

View File

@@ -282,7 +282,8 @@ export const useWorkflow = () => {
setEdges,
onChangeNode,
onEdgesChange,
setHoverEdgeId
setHoverEdgeId,
setMenu
} = useContextSelector(WorkflowContext, (v) => v);
const { getIntersectingNodes } = useReactFlow();
@@ -599,7 +600,7 @@ export const useWorkflow = () => {
connect
});
},
[onConnect, t, toast, nodes]
[onConnect, t, toast]
);
/* edge */
@@ -613,6 +614,24 @@ export const useWorkflow = () => {
setHoverEdgeId(undefined);
}, [setHoverEdgeId]);
// context menu
const onPaneContextMenu = useCallback(
(e: any) => {
// Prevent native context menu from showing
e.preventDefault();
setMenu({
top: e.clientY - 64,
left: e.clientX - 12
});
},
[setMenu]
);
const onPaneClick = useCallback(() => {
setMenu(null);
}, [setMenu]);
return {
handleNodesChange,
handleEdgeChange,
@@ -624,7 +643,9 @@ export const useWorkflow = () => {
onEdgeMouseLeave,
helperLineHorizontal,
helperLineVertical,
onNodeDragStop
onNodeDragStop,
onPaneContextMenu,
onPaneClick
};
};

View File

@@ -17,6 +17,7 @@ import { WorkflowContext } from '../context';
import { useWorkflow } from './hooks/useWorkflow';
import HelperLines from './components/HelperLines';
import FlowController from './components/FlowController';
import ContextMenu from './components/ContextMenu';
export const minZoom = 0.1;
export const maxZoom = 1.5;
@@ -48,6 +49,7 @@ const nodeTypes: Record<FlowNodeTypeEnum, any> = {
[FlowNodeTypeEnum.stopTool]: (data: NodeProps<FlowNodeItemType>) => (
<NodeSimple {...data} minW={'100px'} maxW={'300px'} />
),
[FlowNodeTypeEnum.toolParams]: dynamic(() => import('./nodes/NodeToolParams')),
[FlowNodeTypeEnum.lafModule]: dynamic(() => import('./nodes/NodeLaf')),
[FlowNodeTypeEnum.ifElseNode]: dynamic(() => import('./nodes/NodeIfElse')),
[FlowNodeTypeEnum.variableUpdate]: dynamic(() => import('./nodes/NodeVariableUpdate')),
@@ -56,14 +58,15 @@ const nodeTypes: Record<FlowNodeTypeEnum, any> = {
[FlowNodeTypeEnum.loop]: dynamic(() => import('./nodes/Loop/NodeLoop')),
[FlowNodeTypeEnum.loopStart]: dynamic(() => import('./nodes/Loop/NodeLoopStart')),
[FlowNodeTypeEnum.loopEnd]: dynamic(() => import('./nodes/Loop/NodeLoopEnd')),
[FlowNodeTypeEnum.formInput]: dynamic(() => import('./nodes/NodeFormInput'))
[FlowNodeTypeEnum.formInput]: dynamic(() => import('./nodes/NodeFormInput')),
[FlowNodeTypeEnum.comment]: dynamic(() => import('./nodes/NodeComment'))
};
const edgeTypes = {
[EDGE_TYPE]: ButtonEdge
};
const Workflow = () => {
const { nodes, edges, reactFlowWrapper, workflowControlMode } = useContextSelector(
const { nodes, edges, menu, reactFlowWrapper, workflowControlMode } = useContextSelector(
WorkflowContext,
(v) => v
);
@@ -78,7 +81,9 @@ const Workflow = () => {
onEdgeMouseLeave,
helperLineHorizontal,
helperLineVertical,
onNodeDragStop
onNodeDragStop,
onPaneContextMenu,
onPaneClick
} = useWorkflow();
const {
@@ -120,6 +125,7 @@ const Workflow = () => {
<NodeTemplatesModal isOpen={isOpenTemplate} onClose={onCloseTemplate} />
</>
{menu && <ContextMenu {...menu} />}
<ReactFlow
ref={reactFlowWrapper}
fitView
@@ -141,6 +147,8 @@ const Workflow = () => {
onEdgeMouseEnter={onEdgeMouseEnter}
onEdgeMouseLeave={onEdgeMouseLeave}
panOnScrollSpeed={2}
onPaneContextMenu={onPaneContextMenu}
onPaneClick={onPaneClick}
{...(workflowControlMode === 'select'
? {
selectionMode: SelectionMode.Full,

View File

@@ -0,0 +1,128 @@
import React, { useCallback, useMemo, useRef, useState } from 'react';
import NodeCard from './render/NodeCard';
import { NodeProps } from 'reactflow';
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import { Box, Textarea } from '@chakra-ui/react';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useTranslation } from 'react-i18next';
const NodeComment = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { nodeId, inputs } = data;
const { commentText, commentSize } = useMemo(
() => ({
commentText: inputs.find((item) => item.key === NodeInputKeyEnum.commentText),
commentSize: inputs.find((item) => item.key === NodeInputKeyEnum.commentSize)
}),
[inputs]
);
const onChangeNode = useContextSelector(WorkflowContext, (ctx) => ctx.onChangeNode);
const { t } = useTranslation();
const [size, setSize] = useState<{
width: number;
height: number;
}>(commentSize?.value);
const initialY = useRef(0);
const initialX = useRef(0);
const handleMouseDown = useCallback(
(e: React.MouseEvent) => {
initialY.current = e.clientY;
initialX.current = e.clientX;
const handleMouseMove = (e: MouseEvent) => {
const deltaY = e.clientY - initialY.current;
const deltaX = e.clientX - initialX.current;
setSize((prevSize) => ({
width: prevSize.width + deltaX < 240 ? 240 : prevSize.width + deltaX,
height: prevSize.height + deltaY < 140 ? 140 : prevSize.height + deltaY
}));
initialY.current = e.clientY;
initialX.current = e.clientX;
commentSize &&
onChangeNode({
nodeId: nodeId,
type: 'updateInput',
key: NodeInputKeyEnum.commentSize,
value: {
...commentSize,
value: {
width: size.width + deltaX,
height: size.height + deltaY
}
}
});
};
const handleMouseUp = () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
},
[commentSize, nodeId, onChangeNode, size.height, size.width]
);
return (
<NodeCard
selected={false}
{...data}
minW={`${size.width}px`}
minH={`${size.height}px`}
menuForbid={{
debug: true
}}
border={'none'}
rounded={'none'}
bg={'#D8E9FF'}
boxShadow={
'0px 4px 10px 0px rgba(19, 51, 107, 0.10), 0px 0px 1px 0px rgba(19, 51, 107, 0.10)'
}
>
<Box w={'full'} h={'full'} position={'relative'}>
<Box
position={'absolute'}
right={'0'}
bottom={'-2'}
zIndex={9}
cursor={'nwse-resize'}
px={'2px'}
className="nodrag"
onMouseDown={handleMouseDown}
>
<MyIcon name={'common/editor/resizer'} width={'14px'} height={'14px'} />
</Box>
<Textarea
value={commentText?.value}
border={'none'}
rounded={'none'}
minH={`${size.height}px`}
minW={`${size.width}px`}
resize={'none'}
placeholder={t('workflow:enter_comment')}
onChange={(e) => {
commentText &&
onChangeNode({
nodeId: nodeId,
type: 'updateInput',
key: NodeInputKeyEnum.commentText,
value: {
...commentText,
value: e.target.value
}
});
}}
/>
</Box>
</NodeCard>
);
};
export default React.memo(NodeComment);

View File

@@ -701,7 +701,7 @@ const RenderBody = ({
}}
showOpenModal={false}
variableLabels={variables}
h={200}
minH={200}
/>
)}
</Box>

View File

@@ -0,0 +1,182 @@
import { fnValueTypeSelect } from '@/web/core/workflow/constants/dataType';
import { Box, Button, Flex, Input, ModalBody, ModalFooter, Textarea } from '@chakra-ui/react';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
import MyModal from '@fastgpt/web/components/common/MyModal';
import MySelect from '@fastgpt/web/components/common/MySelect';
import React, { useCallback, useMemo } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { defaultEditFormData } from '../render/RenderToolInput/EditFieldModal';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
import FormLabel from '@fastgpt/web/components/common/MyBox/FormLabel';
import QuestionTip from '@fastgpt/web/components/common/MyTooltip/QuestionTip';
import { WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
const ToolParamsEditModal = ({
defaultValue = defaultEditFormData,
nodeId,
onClose
}: {
defaultValue: FlowNodeInputItemType;
nodeId: string;
onClose: () => void;
}) => {
const { t } = useTranslation();
const { toast } = useToast();
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const { register, setValue, handleSubmit, watch } = useForm<FlowNodeInputItemType>({
defaultValues: defaultValue
});
const valueType = watch('valueType');
const { runAsync: onClickSubmit } = useRequest2(
async (e: FlowNodeInputItemType) => {
e.key = e.key.trim();
const inputConfig: FlowNodeInputItemType = {
...e,
description: e.toolDescription,
label: e.key
};
if (defaultValue.key) {
// edit
onChangeNode({
nodeId,
type: 'replaceInput',
key: defaultValue.key,
value: inputConfig
});
onChangeNode({
nodeId,
type: 'replaceOutput',
key: defaultValue.key,
value: {
...e,
id: e.key,
label: e.key,
type: FlowNodeOutputTypeEnum.static
}
});
} else {
// create
onChangeNode({
nodeId,
type: 'addInput',
value: {
...e,
label: e.key
}
});
onChangeNode({
nodeId,
type: 'addOutput',
value: {
...e,
id: e.key,
label: e.key,
type: FlowNodeOutputTypeEnum.static
}
});
}
onClose();
},
{
onSuccess: () => {
onClose();
}
}
);
const onClickSubmitError = useCallback(
(e: Object) => {
for (const item of Object.values(e)) {
if (item.message) {
toast({
status: 'warning',
title: item.message
});
break;
}
}
},
[toast]
);
const showEnumInput = useMemo(() => {
return !(
valueType === WorkflowIOValueTypeEnum.boolean ||
valueType === WorkflowIOValueTypeEnum.arrayBoolean
);
}, [valueType]);
return (
<MyModal isOpen iconSrc="modal/edit" title={t('workflow:tool_field')} onClose={onClose}>
<ModalBody>
<Flex alignItems={'center'} mb={5}>
<FormLabel flex={'0 0 80px'}>{t('common:core.module.Data Type')}</FormLabel>
<Box flex={'1 0 0'}>
<MySelect
list={fnValueTypeSelect}
value={valueType}
onchange={(e: any) => {
setValue('valueType', e);
}}
/>
</Box>
</Flex>
<Flex alignItems={'center'} mb={5}>
<FormLabel flex={'0 0 80px'}>{t('workflow:tool_params.params_name')}</FormLabel>
<Input
bg={'myGray.50'}
{...register('key', {
required: true,
pattern: {
value: /^[a-zA-Z]+[0-9]*$/,
message: t('common:info.felid_message')
}
})}
placeholder={t('workflow:tool_params.params_name_placeholder')}
/>
</Flex>
<Flex alignItems={'center'} mb={showEnumInput ? 5 : 0}>
<FormLabel flex={'0 0 80px'}>{t('workflow:tool_params.params_description')}</FormLabel>
<Input
bg={'myGray.50'}
{...register('toolDescription', {
required: true
})}
placeholder={t('workflow:tool_params.params_description_placeholder')}
/>
</Flex>
{showEnumInput && (
<Box>
<Flex alignItems={'center'} mb={2}>
<FormLabel>{t('workflow:tool_params.enum_values')}</FormLabel>
<QuestionTip label={t('workflow:tool_params.enum_values_tip')} />
</Flex>
<Textarea
bg={'myGray.50'}
{...register('enum')}
placeholder={t('workflow:tool_params.enum_placeholder')}
/>
</Box>
)}
</ModalBody>
<ModalFooter>
<Button variant={'whiteBase'} mr={2} onClick={onClose}>
{t('common:common.Close')}
</Button>
<Button onClick={handleSubmit((data) => onClickSubmit(data), onClickSubmitError)}>
{t('common:common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default React.memo(ToolParamsEditModal);

View File

@@ -0,0 +1,117 @@
import { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node';
import { NodeProps } from 'reactflow';
import NodeCard from '../render/NodeCard';
import React, { useMemo, useState } from 'react';
import Container from '../../components/Container';
import {
Box,
Button,
Flex,
FormLabel,
Table,
TableContainer,
Tbody,
Td,
Th,
Thead,
Tr
} from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import { SmallAddIcon } from '@chakra-ui/icons';
import { FlowNodeInputItemType } from '@fastgpt/global/core/workflow/type/io';
import { defaultEditFormData } from '../render/RenderToolInput/EditFieldModal';
import ToolParamsEditModal from './ToolParamsEditModal';
import MyIcon from '@fastgpt/web/components/common/Icon';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context';
const NodeToolParams = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
const [editField, setEditField] = useState<FlowNodeInputItemType>();
const { nodeId, inputs } = data;
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const Render = useMemo(() => {
return (
<NodeCard selected={selected} {...data}>
<Container>
<Flex alignItems={'center'} justifyContent={'space-between'} mb={1.5}>
<FormLabel>{t('workflow:custom_tool_input')}</FormLabel>
<Button
variant={'whiteBase'}
leftIcon={<SmallAddIcon />}
iconSpacing={1}
size={'sm'}
onClick={() => setEditField(defaultEditFormData)}
>
{t('common:add_new_param')}
</Button>
{!!editField && (
<ToolParamsEditModal
defaultValue={editField}
nodeId={nodeId}
onClose={() => setEditField(undefined)}
/>
)}
</Flex>
<Box borderRadius={'md'} overflow={'hidden'} border={'base'}>
<TableContainer>
<Table bg={'white'}>
<Thead>
<Tr>
<Th>{t('workflow:tool_params.params_name')}</Th>
<Th>{t('workflow:tool_params.params_description')}</Th>
<Th>{t('common:common.Operation')}</Th>
</Tr>
</Thead>
<Tbody>
{inputs.map((item, index) => (
<Tr
key={index}
position={'relative'}
whiteSpace={'pre-wrap'}
wordBreak={'break-all'}
>
<Td>{item.key}</Td>
<Td>{item.toolDescription}</Td>
<Td whiteSpace={'nowrap'}>
<MyIcon
mr={3}
name={'common/settingLight'}
w={'16px'}
cursor={'pointer'}
onClick={() => setEditField(item)}
/>
<MyIcon
name={'delete'}
w={'16px'}
cursor={'pointer'}
onClick={() => {
onChangeNode({
nodeId,
type: 'delInput',
key: item.key
});
onChangeNode({
nodeId,
type: 'delOutput',
key: item.key
});
}}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
</Container>
</NodeCard>
);
}, [selected, data, t, editField, inputs, onChangeNode, nodeId]);
return Render;
};
export default React.memo(NodeToolParams);

View File

@@ -12,8 +12,7 @@ import {
NumberInput,
NumberInputField,
NumberInputStepper,
Switch,
Textarea
Switch
} from '@chakra-ui/react';
import { TUpdateListItem } from '@fastgpt/global/core/workflow/template/system/variableUpdate/type';
import { NodeInputKeyEnum, WorkflowIOValueTypeEnum } from '@fastgpt/global/core/workflow/constants';
@@ -25,7 +24,6 @@ import {
} from '@fastgpt/global/core/workflow/node/constant';
import Container from '../components/Container';
import MyIcon from '@fastgpt/web/components/common/Icon';
import JsonEditor from '@fastgpt/web/components/common/Textarea/JsonEditor';
import { SmallAddIcon } from '@chakra-ui/icons';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { ReferenceValueProps } from '@fastgpt/global/core/workflow/type/io';
@@ -105,8 +103,9 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
(item) => item.renderType === updateItem.renderType
);
const nodeIds = nodeList.map((node) => node.nodeId);
const handleUpdate = (newValue: ReferenceValueProps | string) => {
if (isReferenceValue(newValue)) {
if (isReferenceValue(newValue, nodeIds)) {
onUpdateList(
updateList.map((update, i) =>
i === index ? { ...update, value: newValue as ReferenceValueProps } : update
@@ -219,7 +218,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
onChange={handleUpdate}
showOpenModal={false}
variableLabels={variables}
h={100}
minH={100}
/>
</Box>
);
@@ -251,7 +250,7 @@ const NodeVariableUpdate = ({ data, selected }: NodeProps<FlowNodeItemType>) =>
onChange={handleUpdate}
showOpenModal={false}
variableLabels={variables}
h={100}
minH={100}
/>
</Box>
);

View File

@@ -32,14 +32,18 @@ export const ConnectionSourceHandle = ({
);
/*
If the node is folded, must show the handle
If the node is folded and has outputs, must show the handle
hide handle when:
- not folded
- not node
- not sourceHandle
- already connected
*/
if (!isFoldNode && (!node || !node?.sourceHandle?.right || rightTargetConnected)) return null;
if (
!(isFoldNode && node?.outputs.length) &&
(!node || !node?.sourceHandle?.right || rightTargetConnected)
)
return null;
return (
<SourceHandle

View File

@@ -1,5 +1,5 @@
import React, { useCallback, useMemo } from 'react';
import { Box, Button, Card, Flex, Image } from '@chakra-ui/react';
import { Box, Button, Card, Flex, FlexProps, Image } from '@chakra-ui/react';
import MyIcon from '@fastgpt/web/components/common/Icon';
import Avatar from '@fastgpt/web/components/common/Avatar';
import type { FlowNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
@@ -39,7 +39,7 @@ type Props = FlowNodeItemType & {
copy?: boolean;
delete?: boolean;
};
};
} & Omit<FlexProps, 'children'>;
const NodeCard = (props: Props) => {
const { t } = useTranslation();
@@ -62,7 +62,8 @@ const NodeCard = (props: Props) => {
isTool = false,
isError = false,
debugResult,
isFolded
isFolded,
...customStyle
} = props;
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
@@ -92,10 +93,6 @@ const NodeCard = (props: Props) => {
return { node, parentNode };
}, [nodeList, nodeId]);
const { openConfirm: onOpenConfirmSync, ConfirmModal: ConfirmSyncModal } = useConfirm({
content: t('app:module.Confirm Sync')
});
const { data: nodeTemplate, runAsync: getNodeLatestTemplate } = useRequest2(
async () => {
if (
@@ -125,6 +122,10 @@ const NodeCard = (props: Props) => {
manual: false
}
);
const { openConfirm: onOpenConfirmSync, ConfirmModal: ConfirmSyncModal } = useConfirm({
content: t('workflow:Confirm_sync_node')
});
const hasNewVersion = nodeTemplate && nodeTemplate.version !== node?.version;
const { runAsync: onClickSyncVersion } = useRequest2(
@@ -156,121 +157,136 @@ const NodeCard = (props: Props) => {
/* Node header */
const Header = useMemo(() => {
const showHeader = node?.flowNodeType !== FlowNodeTypeEnum.comment;
return (
<Box position={'relative'}>
{/* debug */}
<Box px={4} py={3}>
{/* tool target handle */}
<ToolTargetHandle show={showToolHandle} nodeId={nodeId} />
{showHeader && (
<Box px={4} py={3}>
{/* tool target handle */}
<ToolTargetHandle show={showToolHandle} nodeId={nodeId} />
{/* avatar and name */}
<Flex alignItems={'center'}>
{node?.flowNodeType !== FlowNodeTypeEnum.stopTool && (
<Box
mr={2}
cursor={'pointer'}
rounded={'sm'}
_hover={{ bg: 'myGray.200' }}
onClick={() => {
onChangeNode({
nodeId,
type: 'attr',
key: 'isFolded',
value: !isFolded
});
}}
>
<MyIcon
name={isFolded ? 'core/chat/chevronDown' : 'core/chat/chevronRight'}
w={'24px'}
h={'24px'}
color={'myGray.500'}
/>
</Box>
)}
<Avatar src={avatar} borderRadius={'sm'} objectFit={'contain'} w={'30px'} h={'30px'} />
<Box ml={3} fontSize={'md'} fontWeight={'medium'}>
{t(name as any)}
</Box>
<MyIcon
className="controller-rename"
display={'none'}
name={'edit'}
w={'14px'}
cursor={'pointer'}
ml={1}
color={'myGray.500'}
_hover={{ color: 'primary.600' }}
onClick={() => {
onOpenCustomTitleModal({
defaultVal: name,
onSuccess: (e) => {
if (!e) {
return toast({
title: t('app:modules.Title is required'),
status: 'warning'
});
}
{/* avatar and name */}
<Flex alignItems={'center'}>
{node?.flowNodeType !== FlowNodeTypeEnum.stopTool && (
<Box
mr={2}
cursor={'pointer'}
rounded={'sm'}
_hover={{ bg: 'myGray.200' }}
onClick={() => {
onChangeNode({
nodeId,
type: 'attr',
key: 'name',
value: e
key: 'isFolded',
value: !isFolded
});
}
});
}}
/>
<Box flex={1} />
{hasNewVersion && (
<MyTooltip label={t('app:app.modules.click to update')}>
<Button
bg={'yellow.50'}
color={'yellow.600'}
variant={'ghost'}
h={8}
px={2}
rounded={'6px'}
fontSize={'xs'}
fontWeight={'medium'}
cursor={'pointer'}
_hover={{ bg: 'yellow.100' }}
onClick={onOpenConfirmSync(onClickSyncVersion)}
}}
>
<Box>{t('app:app.modules.has new version')}</Box>
<QuestionOutlineIcon ml={1} />
</Button>
</MyTooltip>
)}
{!!nodeTemplate?.diagram && !hasNewVersion && (
<MyTooltip
label={
<Image src={nodeTemplate?.diagram} w={'100%'} minH={['auto', '200px']} alt={''} />
}
>
<Box
fontSize={'sm'}
color={'primary.700'}
p={1}
rounded={'sm'}
cursor={'default'}
_hover={{ bg: 'rgba(17, 24, 36, 0.05)' }}
>
{t('common:core.module.Diagram')}
<MyIcon
name={!isFolded ? 'core/chat/chevronDown' : 'core/chat/chevronRight'}
w={'24px'}
h={'24px'}
color={'myGray.500'}
/>
</Box>
</MyTooltip>
)}
</Flex>
<MenuRender nodeId={nodeId} menuForbid={menuForbid} nodeList={nodeList} />
<NodeIntro nodeId={nodeId} intro={intro} />
</Box>
)}
<Avatar
src={avatar}
borderRadius={'sm'}
objectFit={'contain'}
w={'30px'}
h={'30px'}
/>
<Box ml={3} fontSize={'md'} fontWeight={'medium'}>
{t(name as any)}
</Box>
<MyIcon
className="controller-rename"
display={'none'}
name={'edit'}
w={'14px'}
cursor={'pointer'}
ml={1}
color={'myGray.500'}
_hover={{ color: 'primary.600' }}
onClick={() => {
onOpenCustomTitleModal({
defaultVal: name,
onSuccess: (e) => {
if (!e) {
return toast({
title: t('app:modules.Title is required'),
status: 'warning'
});
}
onChangeNode({
nodeId,
type: 'attr',
key: 'name',
value: e
});
}
});
}}
/>
<Box flex={1} />
{hasNewVersion && (
<MyTooltip label={t('app:app.modules.click to update')}>
<Button
bg={'yellow.50'}
color={'yellow.600'}
variant={'ghost'}
h={8}
px={2}
rounded={'6px'}
fontSize={'xs'}
fontWeight={'medium'}
cursor={'pointer'}
_hover={{ bg: 'yellow.100' }}
onClick={onOpenConfirmSync(onClickSyncVersion)}
>
<Box>{t('app:app.modules.has new version')}</Box>
<QuestionOutlineIcon ml={1} />
</Button>
</MyTooltip>
)}
{!!nodeTemplate?.diagram && !hasNewVersion && (
<MyTooltip
label={
<Image
src={nodeTemplate?.diagram}
w={'100%'}
minH={['auto', '200px']}
alt={''}
/>
}
>
<Box
fontSize={'sm'}
color={'primary.700'}
p={1}
rounded={'sm'}
cursor={'default'}
_hover={{ bg: 'rgba(17, 24, 36, 0.05)' }}
>
{t('common:core.module.Diagram')}
</Box>
</MyTooltip>
)}
</Flex>
<NodeIntro nodeId={nodeId} intro={intro} />
</Box>
)}
<MenuRender nodeId={nodeId} menuForbid={menuForbid} nodeList={nodeList} />
<ConfirmSyncModal />
</Box>
);
}, [
node?.flowNodeType,
showToolHandle,
nodeId,
node?.flowNodeType,
isFolded,
avatar,
t,
@@ -279,14 +295,15 @@ const NodeCard = (props: Props) => {
onOpenConfirmSync,
onClickSyncVersion,
nodeTemplate?.diagram,
intro,
menuForbid,
nodeList,
intro,
ConfirmSyncModal,
onChangeNode,
onOpenCustomTitleModal,
toast
]);
const RenderHandle = useMemo(() => {
return (
<>
@@ -335,6 +352,7 @@ const NodeCard = (props: Props) => {
: {
borderColor: selected ? 'primary.600' : 'borderColor.base'
})}
{...customStyle}
>
<NodeDebugResponse nodeId={nodeId} debugResult={debugResult} />
{Header}
@@ -342,6 +360,7 @@ const NodeCard = (props: Props) => {
{RenderHandle}
{RenderToolHandle}
<ConfirmSyncModal />
<EditTitleModal maxLength={20} />
</Flex>
);
@@ -384,7 +403,12 @@ const MenuRender = React.memo(function MenuRender({
pluginId: node.data.pluginId,
version: node.data.version
};
return state.concat(
return [
...state.map((item) => ({
...item,
selected: false
})),
storeNode2FlowNode({
item: {
flowNodeType: template.flowNodeType,
@@ -403,7 +427,7 @@ const MenuRender = React.memo(function MenuRender({
parentNodeId: undefined,
t
})
);
];
});
},
[computedNewNodeName, setNodes, t]

View File

@@ -259,7 +259,7 @@ const SettingQuotePrompt = (props: RenderInputProps) => {
<PromptEditor
variables={quoteTemplateVariables}
h={160}
minH={160}
title={t('common:core.app.Quote templates')}
placeholder={t('workflow:quote_content_placeholder')}
value={aiChatQuoteTemplate}
@@ -281,7 +281,7 @@ const SettingQuotePrompt = (props: RenderInputProps) => {
<PromptEditor
variables={quotePromptVariables}
title={t('common:core.app.Quote prompt')}
h={300}
minH={300}
placeholder={t('workflow:quote_prompt_tip', {
default: quotePromptTemplates[0].value
})}

View File

@@ -49,7 +49,8 @@ const TextareaRender = ({ inputs = [], item, nodeId }: RenderInputProps) => {
variables={variables}
title={t(item.label as any)}
maxLength={item.maxLength}
h={150}
minH={100}
maxH={300}
placeholder={t((item.placeholder as any) || '')}
value={item.value}
onChange={onChange}

View File

@@ -85,7 +85,7 @@ const EditFieldModal = ({
);
return (
<MyModal isOpen iconSrc="modal/edit" title={t('common:tool_field')} onClose={onClose}>
<MyModal isOpen iconSrc="modal/edit" title={t('workflow:tool_field')} onClose={onClose}>
<ModalBody>
<Flex alignItems={'center'} mb={5}>
<Box flex={'0 0 80px'}>{t('common:common.Require Input')}</Box>

View File

@@ -174,6 +174,11 @@ type WorkflowContextType = {
//
workflowControlMode?: 'drag' | 'select';
setWorkflowControlMode: (value?: SetState<'drag' | 'select'> | undefined) => void;
menu: {
top: number;
left: number;
} | null;
setMenu: (value: React.SetStateAction<{ top: number; left: number } | null>) => void;
};
type DebugDataType = {
@@ -313,6 +318,10 @@ export const WorkflowContext = createContext<WorkflowContextType>({
},
onSwitchCloudVersion: function (appVersion: AppVersionSchemaType): boolean {
throw new Error('Function not implemented.');
},
menu: null,
setMenu: function (value: React.SetStateAction<{ top: number; left: number } | null>): void {
throw new Error('Function not implemented.');
}
});
@@ -973,6 +982,8 @@ const WorkflowContextProvider = ({
};
}, [edges, nodes]);
const [menu, setMenu] = useState<{ top: number; left: number } | null>(null);
const value = {
appId,
reactFlowWrapper,
@@ -1032,7 +1043,10 @@ const WorkflowContextProvider = ({
setShowHistoryModal,
// chat test
setWorkflowTestData
setWorkflowTestData,
menu,
setMenu
};
return (

View File

@@ -34,6 +34,7 @@ import { useChat } from '@/components/core/chat/ChatContainer/useChat';
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { InitChatResponse } from '@/global/core/chat/api';
import { AppErrEnum } from '@fastgpt/global/common/error/code/app';
const CustomPluginRunBox = dynamic(() => import('./components/CustomPluginRunBox'));
@@ -111,8 +112,14 @@ const Chat = ({
// reset all chat tore
if (e?.code === 501) {
router.replace('/app/list');
} else if (chatId) {
onChangeChatId();
} else {
router.replace({
query: {
...router.query,
appId: myApps[0]?._id,
chatId: ''
}
});
}
},
onFinally() {

View File

@@ -210,9 +210,8 @@ const OutLink = ({
}
},
onError(e: any) {
console.log(e);
if (chatId) {
onChangeChatId();
onChangeChatId('');
}
},
onFinally() {

View File

@@ -11,14 +11,10 @@ import ChatHistorySlider from './components/ChatHistorySlider';
import ChatHeader from './components/ChatHeader';
import { serviceSideProps } from '@/web/common/utils/i18n';
import { useTranslation } from 'next-i18next';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 12);
import ChatBox from '@/components/core/chat/ChatContainer/ChatBox';
import type { StartChatFnProps } from '@/components/core/chat/ChatContainer/type';
import { streamFetch } from '@/web/common/api/fetch';
import { getChatTitleFromChatMessage } from '@fastgpt/global/core/chat/utils';
import { ChatStatusEnum } from '@fastgpt/global/core/chat/constants';
import { getErrText } from '@fastgpt/global/common/error/utils';
import SliderApps from './components/SliderApps';
import { GPTMessages2Chats } from '@fastgpt/global/core/chat/adapt';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
@@ -164,12 +160,9 @@ const Chat = ({ myApps }: { myApps: AppListItemType[] }) => {
manual: false,
refreshDeps: [teamId, teamToken, appId, chatId],
onError(e: any) {
toast({
title: getErrText(e, t('common:core.chat.Failed to initialize chat')),
status: 'error'
});
console.log(e);
if (chatId) {
onChangeChatId();
onChangeChatId('');
}
},
onFinally() {

View File

@@ -9,6 +9,8 @@ import { useRouter } from 'next/router';
import { Dispatch, useRef } from 'react';
import { useTranslation } from 'next-i18next';
import Divider from '@/pages/app/detail/components/WorkflowComponents/Flow/components/Divider';
import I18nLngSelector from '@/components/Select/I18nLngSelector';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 8);
interface Props {
@@ -24,6 +26,7 @@ const FormLayout = ({ children, setPageType, pageType }: Props) => {
const { lastRoute = '/app/list' } = router.query as { lastRoute: string };
const state = useRef(nanoid());
const redirectUri = `${location.origin}/login/provider`;
const { isPc } = useSystem();
const oAuthList = [
...(feConfigs?.oauth?.wechat && pageType !== LoginPageTypeEnum.wechat
@@ -72,22 +75,25 @@ const FormLayout = ({ children, setPageType, pageType }: Props) => {
return (
<Flex flexDirection={'column'} h={'100%'}>
<Flex alignItems={'center'}>
<Flex
w={['48px', '56px']}
h={['48px', '56px']}
bg={'myGray.25'}
borderRadius={'xl'}
borderWidth={'1.5px'}
borderColor={'borderColor.base'}
alignItems={'center'}
justifyContent={'center'}
>
<Image src={LOGO_ICON} w={['24px', '28px']} alt={'icon'} />
<Flex alignItems={'center'} justify={'space-between'}>
<Flex>
<Flex
w={['48px', '56px']}
h={['48px', '56px']}
bg={'myGray.25'}
borderRadius={'xl'}
borderWidth={'1.5px'}
borderColor={'borderColor.base'}
alignItems={'center'}
justifyContent={'center'}
>
<Image src={LOGO_ICON} w={['24px', '28px']} alt={'icon'} />
</Flex>
<Box ml={3} fontSize={['2xl', '3xl']} fontWeight={'bold'}>
{feConfigs?.systemTitle}
</Box>
</Flex>
<Box ml={3} fontSize={['2xl', '3xl']} fontWeight={'bold'}>
{feConfigs?.systemTitle}
</Box>
{!isPc && <I18nLngSelector />}
</Flex>
{children}
{show_oauth && (

View File

@@ -1,5 +1,15 @@
import React, { useState, useCallback, useEffect } from 'react';
import { Box, Center, Flex, useDisclosure } from '@chakra-ui/react';
import {
Box,
Button,
Center,
Drawer,
DrawerCloseButton,
DrawerContent,
DrawerOverlay,
Flex,
useDisclosure
} from '@chakra-ui/react';
import { LoginPageTypeEnum } from '@/web/support/user/login/constants';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import type { ResLogin } from '@/global/support/api/userRes.d';
@@ -12,22 +22,38 @@ import { serviceSideProps } from '@/web/common/utils/i18n';
import { clearToken, setToken } from '@/web/support/user/auth';
import Script from 'next/script';
import Loading from '@fastgpt/web/components/common/MyLoading';
import { useMount } from 'ahooks';
import { t } from 'i18next';
import { useLocalStorageState, useMount } from 'ahooks';
import { useTranslation } from 'next-i18next';
import I18nLngSelector from '@/components/Select/I18nLngSelector';
import { useSystem } from '@fastgpt/web/hooks/useSystem';
import { GET } from '@/web/common/api/request';
const RegisterForm = dynamic(() => import('./components/RegisterForm'));
const ForgetPasswordForm = dynamic(() => import('./components/ForgetPasswordForm'));
const WechatForm = dynamic(() => import('./components/LoginForm/WechatForm'));
const CommunityModal = dynamic(() => import('@/components/CommunityModal'));
const Login = () => {
const ipDetectURL = 'https://qifu-api.baidubce.com/ip/local/geo/v1/district';
const Login = ({ ChineseRedirectUrl }: { ChineseRedirectUrl: string }) => {
const router = useRouter();
const { t } = useTranslation();
const { lastRoute = '' } = router.query as { lastRoute: string };
const { feConfigs } = useSystemStore();
const [pageType, setPageType] = useState<`${LoginPageTypeEnum}`>();
const { setUserInfo } = useUserStore();
const { setLastChatId, setLastChatAppId } = useChatStore();
const { isOpen, onOpen, onClose } = useDisclosure();
const { isPc } = useSystem();
const {
isOpen: isOpenRedirect,
onOpen: onOpenRedirect,
onClose: onCloseRedirect
} = useDisclosure();
const [showRedirect, setShowRedirect] = useLocalStorageState<boolean>('showRedirect', {
defaultValue: true
});
const loginSuccess = useCallback(
(res: ResLogin) => {
@@ -64,9 +90,27 @@ const Login = () => {
);
}, [feConfigs.oauth]);
const checkIpInChina = useCallback(async () => {
try {
const res = await GET<any>(ipDetectURL);
const country = res?.country;
if (
country &&
country === '中国' &&
res.prov !== '中国香港' &&
res.prov !== '中国澳门' &&
res.prov !== '中国台湾'
) {
onOpenRedirect();
}
} catch (error) {
console.log(error);
}
}, [onOpenRedirect]);
useMount(() => {
clearToken();
router.prefetch('/app/list');
ChineseRedirectUrl && showRedirect && checkIpInChina();
});
return (
@@ -76,6 +120,7 @@ const Login = () => {
src={`https://www.recaptcha.net/recaptcha/api.js?render=${feConfigs.googleClientVerKey}`}
></Script>
)}
<Flex
alignItems={'center'}
justifyContent={'center'}
@@ -85,6 +130,11 @@ const Login = () => {
h={'100%'}
px={[0, '10vw']}
>
{isPc && (
<Box position={'absolute'} top={'24px'} right={'50px'}>
<I18nLngSelector />
</Box>
)}
<Flex
flexDirection={'column'}
w={['100%', 'auto']}
@@ -123,13 +173,67 @@ const Login = () => {
{isOpen && <CommunityModal onClose={onClose} />}
</Flex>
{showRedirect && (
<RedirectDrawer
isOpen={isOpenRedirect}
onClose={onCloseRedirect}
onRedirect={() => router.push(ChineseRedirectUrl)}
disableDrawer={() => setShowRedirect(false)}
/>
)}
</>
);
};
function RedirectDrawer({
isOpen,
onClose,
disableDrawer,
onRedirect
}: {
isOpen: boolean;
onClose: () => void;
disableDrawer: () => void;
onRedirect: () => void;
}) {
const { t } = useTranslation();
return (
<Drawer placement="bottom" size={'xs'} isOpen={isOpen} onClose={onClose}>
<DrawerOverlay backgroundColor={'rgba(0,0,0,0.2)'} />
<DrawerContent py={'1.75rem'} px={'3rem'}>
<DrawerCloseButton size={'sm'} />
<Flex align={'center'} justify={'space-between'}>
<Box>
<Box color={'myGray.900'} fontWeight={'500'} fontSize={'1rem'}>
{t('login:Chinese_ip_tip')}
</Box>
<Box
color={'primary.700'}
fontWeight={'500'}
fontSize={'1rem'}
textDecorationLine={'underline'}
cursor={'pointer'}
onClick={disableDrawer}
>
{t('login:no_remind')}
</Box>
</Box>
<Button ml={'0.75rem'} onClick={onRedirect}>
{t('login:redirect')}
</Button>
</Flex>
</DrawerContent>
</Drawer>
);
}
export async function getServerSideProps(context: any) {
return {
props: { ...(await serviceSideProps(context, ['app', 'user', 'login'])) }
props: {
ChineseRedirectUrl: process.env.CHINESE_IP_REDIRECT_URL,
...(await serviceSideProps(context, ['app', 'user', 'login']))
}
};
}

View File

@@ -2,10 +2,12 @@ import { NodeInputKeyEnum } from '@fastgpt/global/core/workflow/constants';
import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
export const getChatModelNameListByModules = (nodes: StoreNodeItemType[]): string[] => {
return nodes
const modelList = nodes
.map((item) => {
const model = item.inputs.find((input) => input.key === NodeInputKeyEnum.aiModel)?.value;
return global.llmModels.find((item) => item.model === model)?.name || '';
})
.filter(Boolean);
return Array.from(new Set(modelList));
};

View File

@@ -7,12 +7,14 @@ export enum LangEnum {
}
export const langMap = {
[LangEnum.en]: {
label: 'English',
icon: 'common/language/en'
label: 'English(US)',
icon: 'common/language/en',
avatar: 'common/language/America'
},
[LangEnum.zh]: {
label: '简体中文',
icon: 'common/language/zh'
icon: 'common/language/zh',
avatar: 'common/language/China'
}
};

View File

@@ -192,7 +192,7 @@ export const emptyTemplates: Record<
max: 3000,
valueType: WorkflowIOValueTypeEnum.string,
label: 'core.ai.Prompt',
description: 'core.app.tip.chatNodeSystemPromptTip',
description: 'core.app.tip.systemPromptTip',
placeholder: 'core.app.tip.chatNodeSystemPromptTip',
value: ''
},

View File

@@ -7,6 +7,7 @@ import {
import { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node.d';
import {
chatHistoryValueDesc,
defaultNodeVersion,
FlowNodeInputTypeEnum,
FlowNodeTypeEnum
} from '@fastgpt/global/core/workflow/node/constant';
@@ -140,7 +141,7 @@ export function form2AppWorkflow(
max: 3000,
valueType: WorkflowIOValueTypeEnum.string,
label: 'core.ai.Prompt',
description: 'core.app.tip.chatNodeSystemPromptTip',
description: 'core.app.tip.systemPromptTip',
placeholder: 'core.app.tip.chatNodeSystemPromptTip',
value: formData.aiSettings.systemPrompt
},
@@ -195,7 +196,7 @@ export function form2AppWorkflow(
x: 918.5901682164496,
y: -227.11542247619582
},
version: '481',
version: DatasetSearchModule.version,
inputs: [
{
key: 'datasets',
@@ -377,7 +378,8 @@ export function form2AppWorkflow(
x: 500 + 500 * (i + 1),
y: 545
},
version: tool.version,
// 这里不需要固定版本,给一个不存在的版本,每次都会用最新版
version: defaultNodeVersion,
inputs: tool.inputs.map((input) => {
// Special key value
if (input.key === NodeInputKeyEnum.forbidStream) {
@@ -461,7 +463,7 @@ export function form2AppWorkflow(
max: 3000,
valueType: WorkflowIOValueTypeEnum.string,
label: 'core.ai.Prompt',
description: 'core.app.tip.chatNodeSystemPromptTip',
description: 'core.app.tip.systemPromptTip',
placeholder: 'core.app.tip.chatNodeSystemPromptTip',
value: formData.aiSettings.systemPrompt
},

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