diff --git a/docSite/content/zh-cn/docs/agreement/open-source.md b/docSite/content/zh-cn/docs/agreement/open-source.md index 8bd372353..eaa493ec6 100644 --- a/docSite/content/zh-cn/docs/agreement/open-source.md +++ b/docSite/content/zh-cn/docs/agreement/open-source.md @@ -7,7 +7,7 @@ toc: true weight: 1210 --- -FastGPT 项目在 Apache License 2.0 许可下开源,同时包含以下附加条件: +FastGPT 项目在 Apache License 2.0 许可下开源,但包含以下附加条件: + FastGPT 允许被用于商业化,例如作为其他应用的“后端即服务”使用,或者作为应用开发平台提供给企业。然而,当满足以下条件时,必须联系作者获得商业许可: diff --git a/docSite/content/zh-cn/docs/development/upgrading/4818.md b/docSite/content/zh-cn/docs/development/upgrading/4818.md index 0f102c2d6..7b8289489 100644 --- a/docSite/content/zh-cn/docs/development/upgrading/4818.md +++ b/docSite/content/zh-cn/docs/development/upgrading/4818.md @@ -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. 修复 - 插件计费错误。 diff --git a/docSite/content/zh-cn/docs/intro.md b/docSite/content/zh-cn/docs/intro.md index 5f060c939..90a808125 100644 --- a/docSite/content/zh-cn/docs/intro.md +++ b/docSite/content/zh-cn/docs/intro.md @@ -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 结构** diff --git a/packages/global/common/error/utils.ts b/packages/global/common/error/utils.ts index 54a47ed01..88a9ea9da 100644 --- a/packages/global/common/error/utils.ts +++ b/packages/global/common/error/utils.ts @@ -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); }; diff --git a/packages/global/common/string/http.ts b/packages/global/common/string/http.ts new file mode 100644 index 000000000..1ec7666b6 --- /dev/null +++ b/packages/global/common/string/http.ts @@ -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 + }; +}; diff --git a/packages/global/core/app/utils.ts b/packages/global/core/app/utils.ts index c574cac5f..1c793cfea 100644 --- a/packages/global/core/app/utils.ts +++ b/packages/global/core/app/utils.ts @@ -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 { @@ -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 ''; +}; diff --git a/packages/global/package.json b/packages/global/package.json index fbd0ad4b5..b00061a0b 100644 --- a/packages/global/package.json +++ b/packages/global/package.json @@ -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", diff --git a/packages/service/core/workflow/dispatch/index.ts b/packages/service/core/workflow/dispatch/index.ts index c482c38ae..d13ef910a 100644 --- a/packages/service/core/workflow/dispatch/index.ts +++ b/packages/service/core/workflow/dispatch/index.ts @@ -487,16 +487,16 @@ export async function dispatchWorkFlow(data: Props): Promise { - 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); @@ -176,15 +237,10 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise) { - 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) { - 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) { +// 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) { +// 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({ diff --git a/packages/web/components/common/Icon/constants.ts b/packages/web/components/common/Icon/constants.ts index 15c87b98d..d8e4edc89 100644 --- a/packages/web/components/common/Icon/constants.ts +++ b/packages/web/components/common/Icon/constants.ts @@ -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'), diff --git a/packages/web/components/common/Icon/icons/core/app/type/jsonImport.svg b/packages/web/components/common/Icon/icons/core/app/type/jsonImport.svg new file mode 100644 index 000000000..8ec2dc584 --- /dev/null +++ b/packages/web/components/common/Icon/icons/core/app/type/jsonImport.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/web/components/common/MyModal/index.tsx b/packages/web/components/common/MyModal/index.tsx index 051d4813f..d49337a5c 100644 --- a/packages/web/components/common/MyModal/index.tsx +++ b/packages/web/components/common/MyModal/index.tsx @@ -75,6 +75,7 @@ const MyModal = ({ py={'10px'} fontSize={'md'} fontWeight={'bold'} + minH={['46px', '53px']} > {iconSrc && ( <> diff --git a/packages/web/i18n/en/app.json b/packages/web/i18n/en/app.json index d206af376..de29637fe 100644 --- a/packages/web/i18n/en/app.json +++ b/packages/web/i18n/en/app.json @@ -31,6 +31,8 @@ "copy_one_app": "Create Duplicate", "core.app.QG.Switch": "Enable guess what you want to ask", "core.dataset.import.Custom prompt": "Custom Prompt", + "create_by_curl": "By CURL", + "create_by_template": "By template", "create_copy_success": "Duplicate Created Successfully", "create_empty_app": "Create Default App", "create_empty_plugin": "Create Default Plugin", @@ -69,6 +71,7 @@ "interval.6_hours": "Every 6 Hours", "interval.per_hour": "Every Hour", "intro": "A comprehensive model application orchestration system that offers out-of-the-box data processing and model invocation capabilities. It allows for rapid Dataset construction and workflow orchestration through Flow visualization, enabling complex Dataset scenarios!", + "invalid_json_format": "JSON format error", "llm_not_support_vision": "This model does not support image recognition", "llm_use_vision": "Vision", "llm_use_vision_tip": "After clicking on the model selection, you can see whether the model supports image recognition and the ability to control whether to start image recognition. \nAfter starting image recognition, the model will read the image content in the file link, and if the user question is less than 500 words, it will automatically parse the image in the user question.", @@ -90,10 +93,11 @@ "move_app": "Move Application", "node_not_intro": "This node is not introduced", "not_json_file": "Please select a JSON file", + "oaste_curl_string": "Enter CURL code", "open_auto_execute": "Enable automatic execution", "open_vision_function_tip": "Models with icon switches have image recognition capabilities. \nAfter being turned on, the model will parse the pictures in the file link and automatically parse the pictures in the user's question (user question ≤ 500 words).", "or_drag_JSON": "or drag in JSON file", - "paste_config": "Paste Configuration", + "paste_config_or_drag": "Paste config or drag JSON file here", "permission.des.manage": "Based on write permissions, you can configure publishing channels, view conversation logs, and assign permissions to the application.", "permission.des.read": "Use the app to have conversations", "permission.des.write": "Can view and edit apps", @@ -150,9 +154,12 @@ "type.Create workflow bot": "Create Workflow", "type.Create workflow tip": "Build complex multi-turn dialogue AI applications through low-code methods, recommended for advanced users.", "type.Http plugin": "HTTP Plugin", + "type.Import from json": "Import JSON", + "type.Import from json tip": "Create applications directly through JSON configuration files", "type.Plugin": "Plugin", "type.Simple bot": "Simple App", "type.Workflow bot": "Workflow", + "type_not_recognized": "App type not recognized", "upload_file_max_amount": "Maximum File Quantity", "upload_file_max_amount_tip": "Maximum number of files uploaded in a single round of conversation", "variable.select type_desc": "You can define a global variable that does not need to be filled in by the user.\n\nThe value of this variable can come from the API interface, the Query of the shared link, or assigned through the [Variable Update] module.", diff --git a/packages/web/i18n/en/common.json b/packages/web/i18n/en/common.json index 08c7a7448..1feb36b2a 100644 --- a/packages/web/i18n/en/common.json +++ b/packages/web/i18n/en/common.json @@ -305,7 +305,6 @@ "core.app.Random": "Divergent", "core.app.Search team tags": "Search Tags", "core.app.Select TTS": "Select Voice Playback Mode", - "core.app.Select app from template": "Template", "core.app.Select quote template": "Select Quote Prompt Template", "core.app.Set a name for your app": "Set a Name for Your App", "core.app.Setting ai property": "Click to Configure AI Model Properties", diff --git a/packages/web/i18n/zh-CN/app.json b/packages/web/i18n/zh-CN/app.json index 4124839cb..a87fb0234 100644 --- a/packages/web/i18n/zh-CN/app.json +++ b/packages/web/i18n/zh-CN/app.json @@ -31,6 +31,8 @@ "copy_one_app": "创建副本", "core.app.QG.Switch": "启用猜你想问", "core.dataset.import.Custom prompt": "自定义提示词", + "create_by_curl": "从 CURL 创建", + "create_by_template": "从模板创建", "create_copy_success": "创建副本成功", "create_empty_app": "创建空白应用", "create_empty_plugin": "创建空白插件", @@ -69,6 +71,7 @@ "interval.6_hours": "每6小时", "interval.per_hour": "每小时", "intro": "是一个大模型应用编排系统,提供开箱即用的数据处理、模型调用等能力,可以快速的构建知识库并通过 Flow 可视化进行工作流编排,实现复杂的知识库场景!", + "invalid_json_format": "JSON 格式错误", "llm_not_support_vision": "该模型不支持图片识别", "llm_use_vision": "图片识别", "llm_use_vision_tip": "点击模型选择后,可以看到模型是否支持图片识别以及控制是否启动图片识别的能力。启动图片识别后,模型会读取文件链接里图片内容,并且如果用户问题少于 500 字,会自动解析用户问题中的图片。", @@ -90,10 +93,11 @@ "move_app": "移动应用", "node_not_intro": "这个节点没有介绍", "not_json_file": "请选择JSON文件", + "oaste_curl_string": "输入 CURL 代码", "open_auto_execute": "启用自动执行", "open_vision_function_tip": "有图示开关的模型即拥有图片识别能力。若开启,模型会解析文件链接里的图片,并自动解析用户问题中的图片(用户问题≤500字时生效)。", "or_drag_JSON": "或拖入JSON文件", - "paste_config": "粘贴配置", + "paste_config_or_drag": "粘贴配置或拖入 JSON 文件", "permission.des.manage": "写权限基础上,可配置发布渠道、查看对话日志、分配该应用权限", "permission.des.read": "可使用该应用进行对话", "permission.des.write": "可查看和编辑应用", @@ -150,9 +154,12 @@ "type.Create workflow bot": "创建工作流", "type.Create workflow tip": "通过低代码的方式,构建逻辑复杂的多轮对话 AI 应用,推荐高级玩家使用", "type.Http plugin": "HTTP 插件", + "type.Import from json": "导入 JSON 配置", + "type.Import from json tip": "通过 JSON 配置文件,直接创建应用", "type.Plugin": "插件", "type.Simple bot": "简易应用", "type.Workflow bot": "工作流", + "type_not_recognized": "未识别到应用类型", "upload_file_max_amount": "最大文件数量", "upload_file_max_amount_tip": "单轮对话中最大上传文件数量", "variable.select type_desc": "可以为工作流定义全局变量,常用临时缓存。赋值的方式包括:\n1. 从对话页面的 query 参数获取。\n2. 通过 API 的 variables 对象传递。\n3. 通过【变量更新】节点进行赋值。", diff --git a/packages/web/i18n/zh-CN/common.json b/packages/web/i18n/zh-CN/common.json index f4c97061f..bbbfcb4ed 100644 --- a/packages/web/i18n/zh-CN/common.json +++ b/packages/web/i18n/zh-CN/common.json @@ -308,7 +308,6 @@ "core.app.Random": "发散", "core.app.Search team tags": "搜索标签", "core.app.Select TTS": "选择语音播放模式", - "core.app.Select app from template": "从模板中选择", "core.app.Select quote template": "选择引用提示模板", "core.app.Set a name for your app": "给应用设置一个名称", "core.app.Setting ai property": "点击配置 AI 模型相关属性", diff --git a/packages/web/i18n/zh-Hant/app.json b/packages/web/i18n/zh-Hant/app.json index fa0bbd8c0..5e0e28bcd 100644 --- a/packages/web/i18n/zh-Hant/app.json +++ b/packages/web/i18n/zh-Hant/app.json @@ -31,6 +31,8 @@ "copy_one_app": "建立副本", "core.app.QG.Switch": "啟用猜你想問", "core.dataset.import.Custom prompt": "自訂提示詞", + "create_by_curl": "從 CURL 創建", + "create_by_template": "從模板創建", "create_copy_success": "建立副本成功", "create_empty_app": "建立空白應用程式", "create_empty_plugin": "建立空白外掛", @@ -69,6 +71,7 @@ "interval.6_hours": "每 6 小時", "interval.per_hour": "每小時", "intro": "FastGPT 是一個基於大型語言模型的知識庫平臺,提供開箱即用的資料處理、向量檢索和視覺化 AI 工作流程編排等功能,讓您可以輕鬆開發和部署複雜的問答系統,而無需繁瑣的設定或配置。", + "invalid_json_format": "JSON 格式錯誤", "llm_not_support_vision": "這個模型不支援圖片辨識", "llm_use_vision": "圖片辨識", "llm_use_vision_tip": "點選模型選擇後,可以看到模型是否支援圖片辨識以及控制是否啟用圖片辨識的功能。啟用圖片辨識後,模型會讀取檔案連結中的圖片內容,並且如果使用者問題少於 500 字,會自動解析使用者問題中的圖片。", @@ -90,10 +93,11 @@ "move_app": "移動應用程式", "node_not_intro": "這個節點沒有介紹", "not_json_file": "請選擇 JSON 檔案", + "oaste_curl_string": "輸入 CURL 代碼", "open_auto_execute": "啟用自動執行", "open_vision_function_tip": "有圖示開關的模型即擁有圖片辨識功能。若開啟,模型會解析檔案連結中的圖片,並自動解析使用者問題中的圖片(使用者問題 ≤ 500 字時生效)。", "or_drag_JSON": "或拖曳 JSON 檔案", - "paste_config": "貼上設定", + "paste_config_or_drag": "貼上配置或拖入 JSON 文件", "permission.des.manage": "在寫入權限基礎上,可以設定發布通道、檢視對話紀錄、分配這個應用程式的權限", "permission.des.read": "可以使用這個應用程式進行對話", "permission.des.write": "可以檢視和編輯應用程式", @@ -150,9 +154,12 @@ "type.Create workflow bot": "建立工作流程", "type.Create workflow tip": "透過低程式碼的方式,建立邏輯複雜的多輪對話 AI 應用程式,建議進階使用者使用", "type.Http plugin": "HTTP 外掛", + "type.Import from json": "導入 JSON 配置", + "type.Import from json tip": "透過 JSON 設定文件,直接建立應用", "type.Plugin": "外掛", "type.Simple bot": "簡易應用程式", "type.Workflow bot": "工作流程", + "type_not_recognized": "未識別到應用程式類型", "upload_file_max_amount": "最大檔案數量", "upload_file_max_amount_tip": "單輪對話中最大上傳檔案數量", "variable.select type_desc": "可以為工作流程定義全域變數,常用於暫存。賦值的方式包括:\n1. 從對話頁面的 query 參數取得。\n2. 透過 API 的 variables 物件傳遞。\n3. 透過【變數更新】節點進行賦值。", diff --git a/packages/web/i18n/zh-Hant/common.json b/packages/web/i18n/zh-Hant/common.json index 1604a0ffd..2d3cfc13a 100644 --- a/packages/web/i18n/zh-Hant/common.json +++ b/packages/web/i18n/zh-Hant/common.json @@ -305,7 +305,6 @@ "core.app.Random": "發散", "core.app.Search team tags": "搜尋標籤", "core.app.Select TTS": "選擇語音播放模式", - "core.app.Select app from template": "從範本中選擇", "core.app.Select quote template": "選擇引用提示範本", "core.app.Set a name for your app": "為您的應用程式命名", "core.app.Setting ai property": "點選設定 AI 模型相關屬性", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e9216fe5d..c9a4b8c37 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: '@apidevtools/swagger-parser': specifier: ^10.1.0 version: 10.1.0(openapi-types@12.1.3) + '@bany/curl-to-json': + specifier: ^1.2.8 + version: 1.2.8 axios: specifier: ^1.5.1 version: 1.7.2 @@ -139,9 +142,6 @@ importers: '@node-rs/jieba': specifier: 1.10.0 version: 1.10.0 - '@wecom/jssdk': - specifier: ^2.2.5 - version: 2.2.5 '@xmldom/xmldom': specifier: ^0.8.10 version: 0.8.10 @@ -408,9 +408,6 @@ importers: projects/app: dependencies: - '@bany/curl-to-json': - specifier: ^1.2.8 - version: 1.2.8 '@chakra-ui/anatomy': specifier: 2.2.1 version: 2.2.1 @@ -584,7 +581,7 @@ importers: version: 1.77.8 ts-jest: specifier: ^29.1.0 - version: 29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0))(typescript@5.5.3) + version: 29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)))(typescript@5.5.3) use-context-selector: specifier: ^1.4.4 version: 1.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(scheduler@0.23.2) @@ -724,7 +721,7 @@ importers: version: 6.3.4 ts-jest: specifier: ^29.1.0 - version: 29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0))(typescript@5.5.3) + version: 29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)))(typescript@5.5.3) ts-loader: specifier: ^9.4.3 version: 9.5.1(typescript@5.5.3)(webpack@5.92.1) @@ -2025,7 +2022,7 @@ packages: '@emotion/use-insertion-effect-with-fallbacks@1.0.1': resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==} peerDependencies: - react: 18.3.1 + react: '>=16.8.0' '@emotion/utils@1.2.1': resolution: {integrity: sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==} @@ -2648,8 +2645,8 @@ packages: resolution: {integrity: sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==} peerDependencies: monaco-editor: '>= 0.25.0 < 1' - react: 18.3.1 - react-dom: 18.3.1 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 '@mongodb-js/saslprep@1.1.9': resolution: {integrity: sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==} @@ -3000,8 +2997,8 @@ packages: '@reactflow/node-resizer@2.2.14': resolution: {integrity: sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==} peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 + react: '>=17' + react-dom: '>=17' '@reactflow/node-toolbar@1.3.14': resolution: {integrity: sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==} @@ -3718,9 +3715,6 @@ packages: '@webassemblyjs/wast-printer@1.12.1': resolution: {integrity: sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==} - '@wecom/jssdk@2.2.5': - resolution: {integrity: sha512-qOBAsfqaiYM8jZHWYs/atHSpJhsLdZVNaxHQdmEQ7ZWul/GZMt4P5VY8Nf7GII7GhG8z/k+r37Dto6qtAaRqow==} - '@xmldom/xmldom@0.8.10': resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} engines: {node: '>=10.0.0'} @@ -7143,8 +7137,8 @@ packages: peerDependencies: '@opentelemetry/api': ^1.1.0 '@playwright/test': ^1.41.2 - react: 18.3.1 - react-dom: 18.3.1 + react: ^18.2.0 + react-dom: ^18.2.0 sass: ^1.3.0 peerDependenciesMeta: '@opentelemetry/api': @@ -7799,8 +7793,8 @@ packages: react-photo-view@1.2.6: resolution: {integrity: sha512-Fq17yxkMIv0oFp7HOJr39HgCZRP6A9K5T5rixJ4flSUYT2OO3V8vNxEExjhIKgIrfmTu+mDnHYEsI9RRWi1JHw==} peerDependencies: - react: 18.3.1 - react-dom: 18.3.1 + react: '>=16.8.0' + react-dom: '>=16.8.0' react-redux@7.2.9: resolution: {integrity: sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==} @@ -7818,8 +7812,8 @@ packages: resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} engines: {node: '>=10'} peerDependencies: - '@types/react': 18.3.1 - react: 18.3.1 + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 peerDependenciesMeta: '@types/react': optional: true @@ -7838,8 +7832,8 @@ packages: resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} engines: {node: '>=10'} peerDependencies: - '@types/react': 18.3.1 - react: 18.3.1 + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 peerDependenciesMeta: '@types/react': optional: true @@ -8868,8 +8862,8 @@ packages: resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} engines: {node: '>=10'} peerDependencies: - '@types/react': 18.3.1 - react: 18.3.1 + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 peerDependenciesMeta: '@types/react': optional: true @@ -8919,8 +8913,8 @@ packages: resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} engines: {node: '>=10'} peerDependencies: - '@types/react': 18.3.1 - react: 18.3.1 + '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 peerDependenciesMeta: '@types/react': optional: true @@ -12925,8 +12919,6 @@ snapshots: '@webassemblyjs/ast': 1.12.1 '@xtuc/long': 4.2.2 - '@wecom/jssdk@2.2.5': {} - '@xmldom/xmldom@0.8.10': {} '@xtuc/ieee754@1.2.0': {} @@ -14530,7 +14522,7 @@ snapshots: eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.56.0) eslint-plugin-react: 7.34.4(eslint@8.56.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.56.0) @@ -14553,12 +14545,8 @@ snapshots: debug: 4.3.5 enhanced-resolve: 5.17.0 eslint: 8.56.0 -<<<<<<< HEAD eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0) -======= - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) ->>>>>>> 29ab002e3 (feat: support wecom sso (#3518)) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0) fast-glob: 3.3.2 get-tsconfig: 4.7.5 is-core-module: 2.14.0 @@ -14569,7 +14557,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0): dependencies: debug: 3.2.7 optionalDependencies: @@ -14580,7 +14568,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -14590,7 +14578,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.56.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.56.0))(eslint@8.56.0))(eslint@8.56.0) hasown: 2.0.2 is-core-module: 2.14.0 is-glob: 4.0.3 @@ -19004,7 +18992,7 @@ snapshots: ts-dedent@2.2.0: {} - ts-jest@29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0))(typescript@5.5.3): + ts-jest@29.2.2(@babel/core@7.24.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.9))(jest@29.7.0(@types/node@20.14.11)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@20.14.11)(typescript@5.5.3)))(typescript@5.5.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 diff --git a/projects/app/package.json b/projects/app/package.json index 6029415bf..c735c9f6a 100644 --- a/projects/app/package.json +++ b/projects/app/package.json @@ -10,7 +10,6 @@ "test": "jest" }, "dependencies": { - "@bany/curl-to-json": "^1.2.8", "@chakra-ui/anatomy": "2.2.1", "@chakra-ui/icons": "2.1.1", "@chakra-ui/next-js": "2.1.5", diff --git a/projects/app/src/pageComponents/app/ImportAppConfigEditor.tsx b/projects/app/src/pageComponents/app/ImportAppConfigEditor.tsx new file mode 100644 index 000000000..c7c914ad9 --- /dev/null +++ b/projects/app/src/pageComponents/app/ImportAppConfigEditor.tsx @@ -0,0 +1,136 @@ +import React, { DragEvent, useCallback, useState } from 'react'; +import { Box, Button, Flex, Textarea } from '@chakra-ui/react'; +import { useTranslation } from 'next-i18next'; +import { useToast } from '@fastgpt/web/hooks/useToast'; +import MyIcon from '@fastgpt/web/components/common/Icon'; +import { useSelectFile } from '@/web/common/file/hooks/useSelectFile'; + +type Props = { + value: string; + onChange: (value: string) => void; + rows?: number; +}; + +const ImportAppConfigEditor = ({ value, onChange, rows = 16 }: Props) => { + const { t } = useTranslation(); + const { toast } = useToast(); + const [isDragging, setIsDragging] = useState(false); + + const { File, onOpen } = useSelectFile({ + fileType: 'json', + multiple: false + }); + + const handleDragEnter = useCallback((e: DragEvent) => { + e.preventDefault(); + setIsDragging(true); + }, []); + + const handleDragLeave = useCallback((e: DragEvent) => { + e.preventDefault(); + setIsDragging(false); + }, []); + + const readJSONFile = useCallback( + (file: File) => { + const reader = new FileReader(); + reader.onload = (e) => { + if (!file.name.endsWith('.json')) { + toast({ + title: t('app:not_json_file'), + status: 'error' + }); + return; + } + if (e.target) { + const res = JSON.parse(e.target.result as string); + onChange(JSON.stringify(res, null, 2)); + } + }; + reader.readAsText(file); + }, + [onChange, t, toast] + ); + + const onSelectFile = useCallback( + async (e: File[]) => { + const file = e[0]; + readJSONFile(file); + }, + [readJSONFile] + ); + + const handleDrop = useCallback( + async (e: DragEvent) => { + e.preventDefault(); + const file = e.dataTransfer.files[0]; + console.log(file); + readJSONFile(file); + setIsDragging(false); + }, + [readJSONFile] + ); + + return ( + <> + + {isDragging ? ( + e.preventDefault()} + onDrop={handleDrop} + onDragLeave={handleDragLeave} + > + + + + {t('app:file_recover')} + + + + ) : ( + + + + {t('common:common.json_config')} + + + + e.preventDefault()} + onDrop={handleDrop} + onDragLeave={handleDragLeave} + > +