Skip to content

Commit

Permalink
perf: http body;perf: create by json;perf: create by curl (#3570)
Browse files Browse the repository at this point in the history
* perf: http body

* feat: create app by json (#3557)

* feat: create app by json

* fix build

* perf: create by json;perf: create by curl

* fix: ts

---------

Co-authored-by: heheer <[email protected]>
  • Loading branch information
c121914yu and newfish-cmyk authored Jan 12, 2025
1 parent f1f0ae2 commit d0d1a2c
Show file tree
Hide file tree
Showing 34 changed files with 1,276 additions and 516 deletions.
2 changes: 1 addition & 1 deletion docSite/content/zh-cn/docs/agreement/open-source.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ toc: true
weight: 1210
---

FastGPT 项目在 Apache License 2.0 许可下开源,同时包含以下附加条件
FastGPT 项目在 Apache License 2.0 许可下开源,但包含以下附加条件

+ FastGPT 允许被用于商业化,例如作为其他应用的“后端即服务”使用,或者作为应用开发平台提供给企业。然而,当满足以下条件时,必须联系作者获得商业许可:

Expand Down
25 changes: 14 additions & 11 deletions docSite/content/zh-cn/docs/development/upgrading/4818.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,17 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4818' \

## 完整更新内容

1. 新增 - 支持部门架构权限模式。
2. 新增 - 支持配置自定跨域安全策略,默认全开。
3. 优化 - 分享链接随机生成用户头像。
4. 优化 - 图片上传安全校验。并增加头像图片唯一存储,确保不会累计存储。
5. 优化 - Mongo 全文索引表分离。
6. 优化 - 知识库检索查询语句合并,同时减少查库数量。
7. 优化 - 文件编码检测,减少 CSV 文件乱码概率。
8. 优化 - 异步读取文件内容,减少进程阻塞。
9. 优化 - 文件阅读,HTML 直接下载,不允许在线阅读。
10. 修复 - HTML 文件上传,base64 图片无法自动转图片链接。
11. 修复 - 插件计费错误。
1. 新增 - 支持通过 JSON 配置直接创建应用。
2. 新增 - 支持通过 CURL 脚本快速创建 HTTP 插件。
3. 新增 - 商业版支持部门架构权限模式。
4. 新增 - 支持配置自定跨域安全策略,默认全开。
5. 优化 - HTTP Body 增加特殊处理,解决字符串变量带换行时无法解析问题。
6. 优化 - 分享链接随机生成用户头像。
7. 优化 - 图片上传安全校验。并增加头像图片唯一存储,确保不会累计存储。
8. 优化 - Mongo 全文索引表分离。
9. 优化 - 知识库检索查询语句合并,同时减少查库数量。
10. 优化 - 文件编码检测,减少 CSV 文件乱码概率。
11. 优化 - 异步读取文件内容,减少进程阻塞。
12. 优化 - 文件阅读,HTML 直接下载,不允许在线阅读。
13. 修复 - HTML 文件上传,base64 图片无法自动转图片链接。
14. 修复 - 插件计费错误。
2 changes: 1 addition & 1 deletion docSite/content/zh-cn/docs/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ FastGPT 对外的 API 接口对齐了 OpenAI 官方接口,可以直接接入

1. **项目开源**

FastGPT 遵循附加条件 Apache License 2.0 开源协议,你可以 [Fork](https://github.com/labring/FastGPT/fork) 之后进行二次开发和发布。FastGPT 社区版将保留核心功能,商业版仅在社区版基础上使用 API 的形式进行扩展,不影响学习使用。
FastGPT 遵循**附加条件 Apache License 2.0 开源协议**,你可以 [Fork](https://github.com/labring/FastGPT/fork) 之后进行二次开发和发布。FastGPT 社区版将保留核心功能,商业版仅在社区版基础上使用 API 的形式进行扩展,不影响学习使用。

2. **独特的 QA 结构**

Expand Down
2 changes: 1 addition & 1 deletion packages/global/common/error/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export const getErrText = (err: any, def = ''): any => {
typeof err === 'string'
? err
: err?.response?.data?.message || err?.response?.message || err?.message || def;
msg && console.log('error =>', msg);
// msg && console.log('error =>', msg);
return replaceSensitiveText(msg);
};
38 changes: 38 additions & 0 deletions packages/global/common/string/http.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import parse from '@bany/curl-to-json';

type RequestMethod = 'get' | 'post' | 'put' | 'delete' | 'patch';
const methodMap: { [K in RequestMethod]: string } = {
get: 'GET',
post: 'POST',
put: 'PUT',
delete: 'DELETE',
patch: 'PATCH'
};

export const parseCurl = (curlContent: string) => {
const parsed = parse(curlContent);

if (!parsed.url) {
throw new Error('url not found');
}

const newParams = Object.keys(parsed.params || {}).map((key) => ({
key,
value: parsed.params?.[key],
type: 'string'
}));
const newHeaders = Object.keys(parsed.header || {}).map((key) => ({
key,
value: parsed.header?.[key],
type: 'string'
}));
const newBody = JSON.stringify(parsed.data, null, 2);

return {
url: parsed.url,
method: methodMap[parsed.method?.toLowerCase() as RequestMethod] || 'GET',
params: newParams,
headers: newHeaders,
body: newBody
};
};
19 changes: 19 additions & 0 deletions packages/global/core/app/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import type { FlowNodeInputItemType } from '../workflow/type/io.d';
import { getAppChatConfig } from '../workflow/utils';
import { StoreNodeItemType } from '../workflow/type/node';
import { DatasetSearchModeEnum } from '../dataset/constants';
import { WorkflowTemplateBasicType } from '../workflow/type';
import { AppTypeEnum } from './constants';

export const getDefaultAppForm = (): AppSimpleEditFormType => {
return {
Expand Down Expand Up @@ -127,3 +129,20 @@ export const appWorkflow2Form = ({

return defaultAppForm;
};

export const getAppType = (config?: WorkflowTemplateBasicType | AppSimpleEditFormType) => {
if (!config) return '';

if ('aiSettings' in config) {
return AppTypeEnum.simple;
}

if (!('nodes' in config)) return '';
if (config.nodes.some((node) => node.flowNodeType === 'workflowStart')) {
return AppTypeEnum.workflow;
}
if (config.nodes.some((node) => node.flowNodeType === 'pluginInput')) {
return AppTypeEnum.plugin;
}
return '';
};
3 changes: 2 additions & 1 deletion packages/global/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"openai": "4.61.0",
"openapi-types": "^12.1.3",
"json5": "^2.2.3",
"timezones-list": "^3.0.2"
"timezones-list": "^3.0.2",
"@bany/curl-to-json": "^1.2.8"
},
"devDependencies": {
"@types/js-yaml": "^4.0.9",
Expand Down
17 changes: 11 additions & 6 deletions packages/service/core/workflow/dispatch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,16 +487,16 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
if (input.key === dynamicInput?.key) return;

// Skip some special key
if (input.key === NodeInputKeyEnum.childrenNodeIdList) {
if (
[NodeInputKeyEnum.childrenNodeIdList, NodeInputKeyEnum.httpJsonBody].includes(
input.key as any
)
) {
params[input.key] = input.value;

return;
}

// replace {{xx}} variables
// let value = replaceVariable(input.value, variables);

// replace {{$xx.xx$}} variables
// replace {{$xx.xx$}} and {{xx}} variables
let value = replaceEditorVariable({
text: input.value,
nodes: runtimeNodes,
Expand Down Expand Up @@ -606,6 +606,11 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
};
}

// Error
if (dispatchRes?.responseData?.error) {
addLog.warn('workflow error', dispatchRes.responseData.error);
}

return {
node,
runStatus: 'run',
Expand Down
158 changes: 107 additions & 51 deletions packages/service/core/workflow/dispatch/tools/http468.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { ModuleDispatchProps } from '@fastgpt/global/core/workflow/runtime/
import {
NodeInputKeyEnum,
NodeOutputKeyEnum,
VARIABLE_NODE_ID,
WorkflowIOValueTypeEnum
} from '@fastgpt/global/core/workflow/constants';
import {
Expand All @@ -16,7 +17,9 @@ import { DispatchNodeResultType } from '@fastgpt/global/core/workflow/runtime/ty
import { getErrText } from '@fastgpt/global/common/error/utils';
import {
textAdaptGptResponse,
replaceEditorVariable
replaceEditorVariable,
formatVariableValByType,
getReferenceVariableValue
} from '@fastgpt/global/core/workflow/runtime/utils';
import { ContentTypes } from '@fastgpt/global/core/workflow/constants';
import { uploadFileFromBase64Img } from '../../../../common/file/gridfs/controller';
Expand Down Expand Up @@ -104,15 +107,73 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
[NodeInputKeyEnum.addInputParam]: concatVariables,
...concatVariables
};

// General data for variable substitution(Exclude: json body)
const replaceStringVariables = (text: string) => {
return replaceVariable(
replaceEditorVariable({
text,
nodes: runtimeNodes,
variables: allVariables
}),
allVariables
);
return replaceEditorVariable({
text,
nodes: runtimeNodes,
variables: allVariables
});
};
/* 特殊处理 JSON 的字符串,减少解码错误
1. 找不到的值,替换成 null
2. 有换行字符串
*/
const replaceJsonBodyString = (text: string) => {
const valToStr = (val: any) => {
if (val === undefined) return 'null';
if (val === null) return 'null';

if (typeof val === 'object') return JSON.stringify(val);

if (typeof val === 'string') {
const str = JSON.stringify(val);
return str.startsWith('"') && str.endsWith('"') ? str.slice(1, -1) : str;
}

return String(val);
};

// 1. Replace {{key}} variables
const regex1 = /{{([^}]+)}}/g;
const matches1 = text.match(regex1) || [];
const uniqueKeys1 = [...new Set(matches1.map((match) => match.slice(2, -2)))];
for (const key of uniqueKeys1) {
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), () => valToStr(variables[key]));
}

// 2. Replace {{key.key}} variables
const regex2 = /\{\{\$([^.]+)\.([^$]+)\$\}\}/g;
const matches2 = [...text.matchAll(regex2)];
if (matches2.length === 0) return text;
matches2.forEach((match) => {
const nodeId = match[1];
const id = match[2];

const variableVal = (() => {
if (nodeId === VARIABLE_NODE_ID) {
return variables[id];
}
// Find upstream node input/output
const node = runtimeNodes.find((node) => node.nodeId === nodeId);
if (!node) return;

const output = node.outputs.find((output) => output.id === id);
if (output) return formatVariableValByType(output.value, output.valueType);

const input = node.inputs.find((input) => input.key === id);
if (input)
return getReferenceVariableValue({ value: input.value, nodes: runtimeNodes, variables });
})();

const formatVal = valToStr(variableVal);

const regex = new RegExp(`\\{\\{\\$(${nodeId}\\.${id})\\$\\}\\}`, 'g');
text = text.replace(regex, () => formatVal);
});

return text.replace(/(".*?")\s*:\s*undefined\b/g, '$1: null');
};

httpReqUrl = replaceStringVariables(httpReqUrl);
Expand Down Expand Up @@ -176,15 +237,10 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
}
if (!httpJsonBody) return {};
if (httpContentType === ContentTypes.json) {
httpJsonBody = replaceStringVariables(httpJsonBody);

const replaceJsonBody = httpJsonBody.replace(/(".*?")\s*:\s*undefined\b/g, '$1: null');

// Json body, parse and return
const jsonParse = json5.parse(replaceJsonBody);
const removeSignJson = removeUndefinedSign(jsonParse);
return removeSignJson;
return json5.parse(replaceJsonBodyString(httpJsonBody));
}

// Raw text, xml
httpJsonBody = replaceStringVariables(httpJsonBody);
return httpJsonBody.replaceAll(UNDEFINED_SIGN, 'null');
} catch (error) {
Expand Down Expand Up @@ -335,40 +391,40 @@ async function fetchData({
};
}

function replaceVariable(text: string, obj: Record<string, any>) {
for (const [key, value] of Object.entries(obj)) {
if (value === undefined) {
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), UNDEFINED_SIGN);
} else {
const replacement = JSON.stringify(value);
const unquotedReplacement =
replacement.startsWith('"') && replacement.endsWith('"')
? replacement.slice(1, -1)
: replacement;
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), () => unquotedReplacement);
}
}
return text || '';
}
function removeUndefinedSign(obj: Record<string, any>) {
for (const key in obj) {
if (obj[key] === UNDEFINED_SIGN) {
obj[key] = undefined;
} else if (Array.isArray(obj[key])) {
obj[key] = obj[key].map((item: any) => {
if (item === UNDEFINED_SIGN) {
return undefined;
} else if (typeof item === 'object') {
removeUndefinedSign(item);
}
return item;
});
} else if (typeof obj[key] === 'object') {
removeUndefinedSign(obj[key]);
}
}
return obj;
}
// function replaceVariable(text: string, obj: Record<string, any>) {
// for (const [key, value] of Object.entries(obj)) {
// if (value === undefined) {
// text = text.replace(new RegExp(`{{(${key})}}`, 'g'), UNDEFINED_SIGN);
// } else {
// const replacement = JSON.stringify(value);
// const unquotedReplacement =
// replacement.startsWith('"') && replacement.endsWith('"')
// ? replacement.slice(1, -1)
// : replacement;
// text = text.replace(new RegExp(`{{(${key})}}`, 'g'), () => unquotedReplacement);
// }
// }
// return text || '';
// }
// function removeUndefinedSign(obj: Record<string, any>) {
// for (const key in obj) {
// if (obj[key] === UNDEFINED_SIGN) {
// obj[key] = undefined;
// } else if (Array.isArray(obj[key])) {
// obj[key] = obj[key].map((item: any) => {
// if (item === UNDEFINED_SIGN) {
// return undefined;
// } else if (typeof item === 'object') {
// removeUndefinedSign(item);
// }
// return item;
// });
// } else if (typeof obj[key] === 'object') {
// removeUndefinedSign(obj[key]);
// }
// }
// return obj;
// }

// Replace some special response from system plugin
async function replaceSystemPluginResponse({
Expand Down
1 change: 1 addition & 0 deletions packages/web/components/common/Icon/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export const iconPaths = {
'core/app/ttsFill': () => import('./icons/core/app/ttsFill.svg'),
'core/app/type/httpPlugin': () => import('./icons/core/app/type/httpPlugin.svg'),
'core/app/type/httpPluginFill': () => import('./icons/core/app/type/httpPluginFill.svg'),
'core/app/type/jsonImport': () => import('./icons/core/app/type/jsonImport.svg'),
'core/app/type/plugin': () => import('./icons/core/app/type/plugin.svg'),
'core/app/type/pluginFill': () => import('./icons/core/app/type/pluginFill.svg'),
'core/app/type/pluginLight': () => import('./icons/core/app/type/pluginLight.svg'),
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit d0d1a2c

Please sign in to comment.