v4.5.2 (#439)
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app",
|
||||
"version": "4.5.1",
|
||||
"version": "4.5.2",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
@@ -31,7 +31,7 @@
|
||||
"formidable": "^2.1.1",
|
||||
"framer-motion": "^9.0.6",
|
||||
"hyperdown": "^2.4.29",
|
||||
"i18next": "^23.2.11",
|
||||
"i18next": "^22.5.1",
|
||||
"immer": "^9.0.19",
|
||||
"js-cookie": "^3.0.5",
|
||||
"js-tiktoken": "^1.0.7",
|
||||
@@ -44,7 +44,7 @@
|
||||
"multer": "1.4.5-lts.1",
|
||||
"nanoid": "^4.0.1",
|
||||
"next": "13.5.2",
|
||||
"next-i18next": "^14.0.0",
|
||||
"next-i18next": "^13.3.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"papaparse": "^5.4.1",
|
||||
"pg": "^8.10.0",
|
||||
@@ -53,7 +53,7 @@
|
||||
"react-day-picker": "^8.7.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "^7.43.1",
|
||||
"react-i18next": "^13.0.2",
|
||||
"react-i18next": "^12.3.1",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"reactflow": "^11.7.4",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
### Fast GPT V4.5.1
|
||||
### Fast GPT V4.5.2
|
||||
|
||||
1. 新增 - 知识库目录结构,更方便进行分类
|
||||
2. 新增 - 升级 PgVector 插件,引入 HNSW 索引,极大加快的知识库搜索速度。
|
||||
3. 新增 - AI对话模块,增加【返回AI内容】选项,可控制 AI 的内容不直接返回浏览器。
|
||||
4. 优化 - TextSplitter,采用递归拆解法。
|
||||
5. [使用文档](https://doc.fastgpt.run/docs/intro/)
|
||||
6. [点击查看高级编排介绍文档](https://doc.fastgpt.run/docs/workflow)
|
||||
7. [点击查看商业版](https://doc.fastgpt.run/docs/commercial/)
|
||||
1. 新增 - 模块插件,允许自行组装插件进行模块复用。
|
||||
2. 优化 - 知识库引用提示。
|
||||
3. [知识库结构详解](https://doc.fastgpt.in/docs/use-cases/datasetengine/)
|
||||
4. [知识库提示词详解](https://doc.fastgpt.in/docs/use-cases/ai_settings/#引用模板--引用提示词)
|
||||
5. [使用文档](https://doc.fastgpt.in/docs/intro/)
|
||||
6. [点击查看高级编排介绍文档](https://doc.fastgpt.in/docs/workflow)
|
||||
7. [点击查看商业版](https://doc.fastgpt.in/docs/commercial/)
|
||||
|
||||
11
projects/app/public/imgs/module/ai.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<?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="1698306337334"
|
||||
class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2550"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128">
|
||||
<path
|
||||
d="M362.496 661.504c0 23.552 18.944 42.496 42.496 42.496s42.496-18.944 42.496-42.496c0-23.552-18.944-42.496-42.496-42.496s-42.496 18.944-42.496 42.496zM576 661.504c0 23.552 18.944 42.496 42.496 42.496 23.552 0 42.496-18.944 42.496-42.496 0-23.552-18.944-42.496-42.496-42.496-23.552-0.512-42.496 18.944-42.496 42.496z"
|
||||
p-id="2551" fill="#1296db"></path>
|
||||
<path
|
||||
d="M874.496 618.496v-21.504c2.048-95.744-41.984-186.368-118.272-244.224L860.16 190.464c34.304 8.192 68.608-13.312 76.8-48.128 8.192-34.304-13.312-68.608-48.128-76.8s-68.608 13.312-76.8 48.128c-4.608 18.944 0 38.4 11.776 53.76l-102.912 162.304c-64-35.328-136.192-53.248-209.408-52.224-73.216-1.536-145.408 16.384-209.408 52.224L199.68 167.424c21.504-27.648 16.896-68.096-11.264-89.6-27.648-21.504-68.096-16.896-89.6 11.264-22.016 27.648-16.896 67.584 11.264 89.6 15.36 11.776 34.816 16.384 53.76 11.776L267.776 353.28c-76.288 57.856-120.32 148.48-118.272 244.224v21.504H42.496v256h106.496v85.504h725.504v-85.504h106.496v-256h-106.496zM149.504 832H85.504v-170.496h64v170.496z m512-42.496H362.496c-70.656 0-128-57.344-128-128s57.344-128 128-128h298.496c70.656 0 128 57.344 128 128 0.512 70.656-56.832 128-127.488 128z m276.992 42.496h-64v-170.496h64v170.496z"
|
||||
p-id="2552" fill="#3370ff"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
BIN
projects/app/public/imgs/module/custom.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
projects/app/public/imgs/module/input.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
projects/app/public/imgs/module/output.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
1
projects/app/public/imgs/module/plugin.svg
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
@@ -27,6 +27,7 @@
|
||||
"Connection is invalid": "Connecting is invalid",
|
||||
"Connection type is different": "Connection type is different",
|
||||
"Copy Module Config": "Copy config",
|
||||
"Dataset Quote Template": "Dataset Mode",
|
||||
"Export Config Successful": "The configuration has been copied. Please check for important data",
|
||||
"Export Configs": "Export Configs",
|
||||
"Feedback Count": "User Feedback",
|
||||
@@ -46,7 +47,20 @@
|
||||
"Paste Config": "Paste Config",
|
||||
"Variable Key Repeat Tip": "Variable Key Repeat",
|
||||
"module": {
|
||||
"Custom Title Tip": "The title name is displayed during the conversation"
|
||||
"Combine Modules": "Combine Modules",
|
||||
"Custom Title Tip": "The title name is displayed during the conversation",
|
||||
"My Modules": "My Custom Modules",
|
||||
"No Modules": "No module",
|
||||
"System Module": "System Module",
|
||||
"type": "{{type}}\n{{example}}",
|
||||
"valueType": {
|
||||
"any": "any",
|
||||
"boolean": "boolean",
|
||||
"chatHistory": "Chat History",
|
||||
"datasetQuote": "Dataset Quote",
|
||||
"number": "number",
|
||||
"string": "string"
|
||||
}
|
||||
},
|
||||
"modules": {
|
||||
"Title is required": "Title is required"
|
||||
@@ -72,6 +86,7 @@
|
||||
"Mark Description Title": "Mark Description",
|
||||
"New Chat": "New Chat",
|
||||
"Question Guide Tips": "I guess what you're asking is",
|
||||
"Quote": "Quote",
|
||||
"Read Mark Description": "Read mark description",
|
||||
"Read User Feedback": "Read user feedback",
|
||||
"Select Mark Kb": "Select Dataset",
|
||||
@@ -104,24 +119,32 @@
|
||||
"module similarity": "Similarity",
|
||||
"module temperature": "Temperature",
|
||||
"module time": "Running Time",
|
||||
"module tokens": "Tokens"
|
||||
"module tokens": "Tokens",
|
||||
"plugin output": "Plugin Output"
|
||||
},
|
||||
"retry": "Retry"
|
||||
},
|
||||
"common": {
|
||||
"Add": "Add",
|
||||
"Back": "Back",
|
||||
"Beta": "Beta",
|
||||
"Choose": "Choose",
|
||||
"Close": "Clow",
|
||||
"Collect": "Collect",
|
||||
"Confirm Create": "Create",
|
||||
"Confirm Move": "Move here",
|
||||
"Confirm Update": "Update",
|
||||
"Copy": "Copy",
|
||||
"Copy Successful": "Copy Successful",
|
||||
"Course": "",
|
||||
"Create Failed": "Create Failed",
|
||||
"Create Success": "Create Success",
|
||||
"Create Virtual File Failed": "Create Virtual File Failed",
|
||||
"Custom Title": "Custom Title",
|
||||
"Delete": "Delete",
|
||||
"Delete Failed": "Delete Failed",
|
||||
"Delete Success": "Delete Successful",
|
||||
"Delete Tip": "Delete Confirm",
|
||||
"Delete Warning": "Warning",
|
||||
"Edit": "Edit",
|
||||
"Expired Time": "Expired",
|
||||
@@ -130,10 +153,13 @@
|
||||
"Filed is repeated": "",
|
||||
"Input": "Input",
|
||||
"Last Step": "Last",
|
||||
"Loading": "Loading",
|
||||
"Max credit": "Credit",
|
||||
"Max credit tips": "What is the maximum amount of money that can be consumed by the link? If the link is exceeded, it will be banned. -1 indicates no limit.",
|
||||
"Name": "Name",
|
||||
"Name Can": "Name Can't Be Empty",
|
||||
"Name is empty": "Name is empty",
|
||||
"New Create": "Create",
|
||||
"Next Step": "Next",
|
||||
"Output": "Output",
|
||||
"Params": "Params",
|
||||
@@ -143,11 +169,17 @@
|
||||
"Rename Success": "Rename Success",
|
||||
"Request Error": "Request Error",
|
||||
"Search": "Search",
|
||||
"Select File Failed": "Select File Failed",
|
||||
"Select One Folder": "Select a folder",
|
||||
"Set Avatar": "Set Avatar",
|
||||
"Set Name": "Make a nice name",
|
||||
"Status": "Status",
|
||||
"Test": "Test",
|
||||
"Time": "Time",
|
||||
"Unknow": "Unknow",
|
||||
"Unknow Source": "UnKnow Source",
|
||||
"Update Failed": "Update Failed",
|
||||
"Update Success": "Update Success",
|
||||
"Update Successful": "Update Successful",
|
||||
"Update Time": "Update Time",
|
||||
"error": {
|
||||
@@ -304,11 +336,26 @@
|
||||
"desc": "AI knowledge base question and answer platform based on LLM large model",
|
||||
"slogan": "Let the AI know more about you"
|
||||
},
|
||||
"module": {
|
||||
"Confirm Delete Module": "Confirm to delete the custom module?",
|
||||
"Confirm Sync Plugin": "Confirm the latest sync plugin information? The plug-in connection and input content will be cleared, please confirm!",
|
||||
"Create Your Module": "Create You Module",
|
||||
"Intro": "Module Intro",
|
||||
"Load Module Failed": "Load Module Failed",
|
||||
"Plugin input is not value": "User-defined input parameters cannot be null",
|
||||
"Plugin input is required": "The plug setting must contain an input module",
|
||||
"Plugin input must connect": "Custom input modules must all be connected",
|
||||
"Preview Plugin": "Preview Plugin",
|
||||
"Save Config": "Save",
|
||||
"Update Your Module": "Update Module"
|
||||
},
|
||||
"navbar": {
|
||||
"Account": "Account",
|
||||
"Apps": "Apps",
|
||||
"Chat": "Chat",
|
||||
"Datasets": "DataSets",
|
||||
"Module": "Module",
|
||||
"Plugin": "Plugin",
|
||||
"Store": "Store",
|
||||
"Tools": "Tools"
|
||||
},
|
||||
@@ -338,12 +385,26 @@
|
||||
"token auth Tips": "Identity verification server address. If this value is set, the server will be specified to send a request for identity verification before each session",
|
||||
"token auth use cases": "Review the authentication instructions"
|
||||
},
|
||||
"plugin": {
|
||||
"Confirm Delete": "Confirm to delete the plugin?",
|
||||
"Create Your Plugin": "Create Plugin",
|
||||
"Get Plugin Module Detail Failed": "Get plugin detail failed",
|
||||
"Intro": "Plugin Intro",
|
||||
"Load Plugin Failed": "Load Plugin Failed",
|
||||
"My Plugins": "My Plugins",
|
||||
"No Intro": "This plugin is not introduced",
|
||||
"Plugin Module": "Plugin",
|
||||
"Set Name": "Plugin Name",
|
||||
"Synchronous version": "Sync Version",
|
||||
"To Edit Plugin": "To Edit",
|
||||
"Update Your Plugin": "Update Plugin"
|
||||
},
|
||||
"system": {
|
||||
"Help Document": "Document"
|
||||
},
|
||||
"template": {
|
||||
"Quote Content Tip": "This configuration takes effect only when reference content is passed in (knowledge base search). You can customize the structure of the reference content to better fit different scenarios. You can use {{q}}, {{a}}, {{source}} as \"search content\", \"expected content\", and \"source\", they are all optional, and here are the default values: \n{instruction:\"{{q}}\",output:\"{{a}}\"}",
|
||||
"Quote Prompt Tip": "This configuration takes effect only when reference content is passed in (knowledge base search). \n You can insert references with {{quote}}, here are the default values: \n\"\"\"{{quote}}\"\"\" The three quotes are the knowledge base I gave you, they have the highest priority. instruction is a relevant introduction and output is an expected answer or supplement."
|
||||
"Quote Content Tip": "This configuration takes effect only when reference content is passed in (knowledge base search).\nYou can customize the structure of the reference content to better suit different scenarios. Some variables can be used for template configuration:\n{{q}} - retrieve content, {{a}} - expected content, {{source}} - source, {{sourceId}} - source file name, {{index}} - the first n references, {{with}} - the reference points (0-1), they are optional, Here are the default values:\n{{default}}",
|
||||
"Quote Prompt Tip": "This configuration takes effect only when the knowledge base is searched.\nYou can use {{quote}} to insert the reference content template and {{question}} to insert the question. Here are the default values:\n{{default}}"
|
||||
},
|
||||
"user": {
|
||||
"Account": "Account",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"Connection is invalid": "连接无效",
|
||||
"Connection type is different": "连接的类型不一致",
|
||||
"Copy Module Config": "复制配置",
|
||||
"Dataset Quote Template": "知识库问答模式",
|
||||
"Export Config Successful": "已复制配置,请注意检查是否有重要数据",
|
||||
"Export Configs": "导出配置",
|
||||
"Feedback Count": "用户反馈",
|
||||
@@ -46,7 +47,20 @@
|
||||
"Paste Config": "粘贴配置",
|
||||
"Variable Key Repeat Tip": "变量 key 重复",
|
||||
"module": {
|
||||
"Custom Title Tip": "该标题名字会展示在对话过程中"
|
||||
"Combine Modules": "组合模块",
|
||||
"Custom Title Tip": "该标题名字会展示在对话过程中",
|
||||
"My Modules": "",
|
||||
"No Modules": "还没有模块~",
|
||||
"System Module": "系统模块",
|
||||
"type": "\"{{type}}\"类型\n{{example}}",
|
||||
"valueType": {
|
||||
"any": "任意",
|
||||
"boolean": "布尔",
|
||||
"chatHistory": "聊天记录",
|
||||
"datasetQuote": "引用内容",
|
||||
"number": "数字",
|
||||
"string": "字符串"
|
||||
}
|
||||
},
|
||||
"modules": {
|
||||
"Title is required": "模块名不能为空"
|
||||
@@ -72,6 +86,7 @@
|
||||
"Mark Description Title": "标注功能介绍",
|
||||
"New Chat": "新对话",
|
||||
"Question Guide Tips": "猜你想问",
|
||||
"Quote": "引用",
|
||||
"Read Mark Description": "查看标注功能介绍",
|
||||
"Read User Feedback": "查看用户反馈",
|
||||
"Select Mark Kb": "选择知识库",
|
||||
@@ -104,24 +119,32 @@
|
||||
"module similarity": "相似度",
|
||||
"module temperature": "温度",
|
||||
"module time": "运行时长",
|
||||
"module tokens": "Tokens"
|
||||
"module tokens": "Tokens",
|
||||
"plugin output": "插件输出值"
|
||||
},
|
||||
"retry": "重新生成"
|
||||
},
|
||||
"common": {
|
||||
"Add": "添加",
|
||||
"Back": "返回",
|
||||
"Beta": "实验版",
|
||||
"Choose": "选择",
|
||||
"Close": "关闭",
|
||||
"Collect": "收藏",
|
||||
"Confirm Create": "确认创建",
|
||||
"Confirm Move": "移动到这",
|
||||
"Confirm Update": "确认更新",
|
||||
"Copy": "复制",
|
||||
"Copy Successful": "复制成功",
|
||||
"Course": "",
|
||||
"Create Failed": "创建异常",
|
||||
"Create Success": "创建成功",
|
||||
"Create Virtual File Failed": "创建虚拟文件失败",
|
||||
"Custom Title": "自定义标题",
|
||||
"Delete": "删除",
|
||||
"Delete Failed": "删除失败",
|
||||
"Delete Success": "删除成功",
|
||||
"Delete Tip": "删除提示",
|
||||
"Delete Warning": "删除警告",
|
||||
"Edit": "编辑",
|
||||
"Expired Time": "过期时间",
|
||||
@@ -130,10 +153,13 @@
|
||||
"Filed is repeated": "字段重复了",
|
||||
"Input": "输入",
|
||||
"Last Step": "上一步",
|
||||
"Loading": "加载中",
|
||||
"Max credit": "最大金额",
|
||||
"Max credit tips": "该链接最大可消耗多少金额,超出后链接将被禁止使用。-1 代表无限制。",
|
||||
"Name": "名称",
|
||||
"Name Can": "名称不能为空",
|
||||
"Name is empty": "名称不能为空",
|
||||
"New Create": "新建",
|
||||
"Next Step": "下一步",
|
||||
"Output": "输出",
|
||||
"Params": "参数",
|
||||
@@ -143,11 +169,17 @@
|
||||
"Rename Success": "重命名成功",
|
||||
"Request Error": "请求异常",
|
||||
"Search": "搜索",
|
||||
"Select File Failed": "选择文件异常",
|
||||
"Select One Folder": "选择一个目录",
|
||||
"Set Avatar": "点击设置头像",
|
||||
"Set Name": "取个响亮的名字",
|
||||
"Status": "状态",
|
||||
"Test": "测试",
|
||||
"Time": "时间",
|
||||
"Unknow": "未知",
|
||||
"Unknow Source": "未知来源",
|
||||
"Update Failed": "更新异常",
|
||||
"Update Success": "更新成功",
|
||||
"Update Successful": "更新成功",
|
||||
"Update Time": "更新时间",
|
||||
"error": {
|
||||
@@ -232,7 +264,7 @@
|
||||
"data": {
|
||||
"Delete Tip": "确认删除该条数据?",
|
||||
"File import": "文件导入",
|
||||
"Input Data": "导入数据",
|
||||
"Input Data": "导入新数据",
|
||||
"Input Success Tip": "导入数据成功",
|
||||
"Update Data": "更新数据",
|
||||
"Update Success Tip": "更新数据成功"
|
||||
@@ -304,11 +336,26 @@
|
||||
"desc": "基于 LLM 大模型的 AI 知识库问答平台",
|
||||
"slogan": "让 AI 更懂你的知识"
|
||||
},
|
||||
"module": {
|
||||
"Confirm Delete Module": "确认删除该自定义模块?",
|
||||
"Confirm Sync Plugin": "确认同步插件最新信息?插件的连线和输入的内容将会被清空,请确认!",
|
||||
"Create Your Module": "创建自定义模块",
|
||||
"Intro": "模块介绍",
|
||||
"Load Module Failed": "加载模块失败",
|
||||
"Plugin input is not value": "自定义输入的参数不能为空",
|
||||
"Plugin input is required": "插件编排必须包含一个输入模块",
|
||||
"Plugin input must connect": "自定义输入模块必须全部连接",
|
||||
"Preview Plugin": "预览插件",
|
||||
"Save Config": "保存配置",
|
||||
"Update Your Module": "更新模块信息"
|
||||
},
|
||||
"navbar": {
|
||||
"Account": "账号",
|
||||
"Apps": "应用",
|
||||
"Chat": "聊天",
|
||||
"Datasets": "知识库",
|
||||
"Module": "模块",
|
||||
"Plugin": "插件",
|
||||
"Store": "应用市场",
|
||||
"Tools": "工具"
|
||||
},
|
||||
@@ -338,12 +385,26 @@
|
||||
"token auth Tips": "身份校验服务器地址,如填写该值,每次对话前都会想指定服务器发送一个请求,进行身份校验",
|
||||
"token auth use cases": "查看身份验证使用说明"
|
||||
},
|
||||
"plugin": {
|
||||
"Confirm Delete": "确认删除该插件?",
|
||||
"Create Your Plugin": "创建你的插件",
|
||||
"Get Plugin Module Detail Failed": "获取插件信息异常",
|
||||
"Intro": "插件介绍",
|
||||
"Load Plugin Failed": "加载插件异常",
|
||||
"My Plugins": "我的插件",
|
||||
"No Intro": "这个插件没有介绍~",
|
||||
"Plugin Module": "插件模块",
|
||||
"Set Name": "给插件取个名字",
|
||||
"Synchronous version": "同步版本",
|
||||
"To Edit Plugin": "去编辑",
|
||||
"Update Your Plugin": "更新插件"
|
||||
},
|
||||
"system": {
|
||||
"Help Document": "帮助文档"
|
||||
},
|
||||
"template": {
|
||||
"Quote Content Tip": "该配置只有传入引用内容(知识库搜索)时生效。\n可以自定义引用内容的结构,以更好的适配不同场景。可以使用一些变量来进行模板配置:\n{{q}} - 检索内容, {{a}} - 预期内容, {{source}} - 来源,{{file_id}} - 来源文件名,{{index}} - 第n个引用,他们都是可选的,下面是默认值:\n{{default}}",
|
||||
"Quote Prompt Tip": "该配置只有传入引用内容(知识库搜索)时生效。\n可以用 {{quote}} 来插入引用内容,使用 {{question}} 来插入问题。下面是默认值:\n{{default}}"
|
||||
"Quote Content Tip": "该配置只有传入引用内容(知识库搜索)时生效。\n可以自定义引用内容的结构,以更好的适配不同场景。可以使用一些变量来进行模板配置:\n{{q}} - 检索内容, {{a}} - 预期内容, {{source}} - 来源,{{sourceId}} - 来源文件名,{{index}} - 第n个引用,{{score}} - 该引用的得分(0-1),他们都是可选的,下面是默认值:\n{{default}}",
|
||||
"Quote Prompt Tip": "该配置只在知识库搜索时生效。\n可以用 {{quote}} 来插入引用内容模板,使用 {{question}} 来插入问题。下面是默认值:\n{{default}}"
|
||||
},
|
||||
"user": {
|
||||
"Account": "账号",
|
||||
|
||||
@@ -77,13 +77,7 @@ const QuoteModal = ({
|
||||
</>
|
||||
}
|
||||
>
|
||||
<ModalBody
|
||||
pt={0}
|
||||
whiteSpace={'pre-wrap'}
|
||||
textAlign={'justify'}
|
||||
wordBreak={'break-all'}
|
||||
fontSize={'sm'}
|
||||
>
|
||||
<ModalBody pt={0} whiteSpace={'pre-wrap'} textAlign={'justify'} wordBreak={'break-all'}>
|
||||
{rawSearch.map((item, i) => (
|
||||
<Box
|
||||
key={i}
|
||||
@@ -95,11 +89,18 @@ const QuoteModal = ({
|
||||
position={'relative'}
|
||||
overflow={'hidden'}
|
||||
_hover={{ '& .hover-data': { display: 'flex' } }}
|
||||
bg={i % 2 === 0 ? 'white' : 'myWhite.500'}
|
||||
>
|
||||
{!isShare && (
|
||||
<Flex alignItems={'flex-end'} mb={3} color={'myGray.500'}>
|
||||
<RawSourceText sourceName={item.sourceName} sourceId={item.sourceId} />
|
||||
<Box flex={1} />
|
||||
<Flex alignItems={'flex-end'} mb={3} fontSize={'sm'}>
|
||||
<RawSourceText
|
||||
fontWeight={'bold'}
|
||||
color={'black'}
|
||||
sourceName={item.sourceName}
|
||||
sourceId={item.sourceId}
|
||||
addr={!isShare}
|
||||
/>
|
||||
<Box flex={1} />
|
||||
{!isShare && (
|
||||
<Link
|
||||
as={NextLink}
|
||||
className="hover-data"
|
||||
@@ -111,13 +112,13 @@ const QuoteModal = ({
|
||||
{t('core.dataset.Go Dataset')}
|
||||
<MyIcon name={'rightArrowLight'} w={'10px'} />
|
||||
</Link>
|
||||
</Flex>
|
||||
)}
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Box color={'black'}>{item.q}</Box>
|
||||
<Box color={'black'}>{item.a}</Box>
|
||||
<Box color={'myGray.600'}>{item.a}</Box>
|
||||
{!isShare && (
|
||||
<Flex alignItems={'center'} mt={3} gap={4} color={'myGray.500'}>
|
||||
<Flex alignItems={'center'} fontSize={'sm'} mt={3} gap={4} color={'myGray.500'}>
|
||||
{isPc && (
|
||||
<MyTooltip label={t('core.dataset.data.id')}>
|
||||
<Flex border={theme.borders.base} px={3} borderRadius={'md'}>
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { ChatHistoryItemResType, ChatItemType } from '@/types/chat';
|
||||
import { Flex, BoxProps, useDisclosure } from '@chakra-ui/react';
|
||||
import { Flex, BoxProps, useDisclosure, Image, useTheme } from '@chakra-ui/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import type { SearchDataResponseItemType } from '@fastgpt/global/core/dataset/type';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Tag from '../Tag';
|
||||
import MyTooltip from '../MyTooltip';
|
||||
import { FlowModuleTypeEnum } from '@/constants/flow';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { getSourceNameIcon } from '@fastgpt/global/core/dataset/utils';
|
||||
import ChatBoxDivider from '@/components/core/chat/Divider';
|
||||
|
||||
const QuoteModal = dynamic(() => import('./QuoteModal'), { ssr: false });
|
||||
const ContextModal = dynamic(() => import('./ContextModal'), { ssr: false });
|
||||
const WholeResponseModal = dynamic(() => import('./WholeResponseModal'), { ssr: false });
|
||||
|
||||
const ResponseTags = ({ responseData = [] }: { responseData?: ChatHistoryItemResType[] }) => {
|
||||
const theme = useTheme();
|
||||
const { isPc } = useSystemStore();
|
||||
const { t } = useTranslation();
|
||||
const [quoteModalData, setQuoteModalData] = useState<SearchDataResponseItemType[]>();
|
||||
@@ -27,18 +30,36 @@ const ResponseTags = ({ responseData = [] }: { responseData?: ChatHistoryItemRes
|
||||
const {
|
||||
chatAccount,
|
||||
quoteList = [],
|
||||
sourceList = [],
|
||||
historyPreview = [],
|
||||
runningTime = 0
|
||||
} = useMemo(() => {
|
||||
const chatData = responseData.find((item) => item.moduleType === FlowModuleTypeEnum.chatNode);
|
||||
const chatData = responseData.find((item) => item.moduleType === FlowNodeTypeEnum.chatNode);
|
||||
const quoteList = responseData
|
||||
.filter((item) => item.moduleType === FlowNodeTypeEnum.chatNode)
|
||||
.map((item) => item.quoteList)
|
||||
.flat()
|
||||
.filter((item) => item) as SearchDataResponseItemType[];
|
||||
const sourceList = quoteList.reduce(
|
||||
(acc: Record<string, SearchDataResponseItemType[]>, cur) => {
|
||||
if (!acc[cur.sourceName]) {
|
||||
acc[cur.sourceName] = [cur];
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
return {
|
||||
chatAccount: responseData.filter((item) => item.moduleType === FlowModuleTypeEnum.chatNode)
|
||||
chatAccount: responseData.filter((item) => item.moduleType === FlowNodeTypeEnum.chatNode)
|
||||
.length,
|
||||
quoteList: responseData
|
||||
.filter((item) => item.moduleType === FlowModuleTypeEnum.chatNode)
|
||||
.map((item) => item.quoteList)
|
||||
quoteList,
|
||||
sourceList: Object.values(sourceList)
|
||||
.flat()
|
||||
.filter((item) => item) as SearchDataResponseItemType[],
|
||||
.map((item) => ({
|
||||
sourceName: item.sourceName,
|
||||
icon: getSourceNameIcon({ sourceId: item.sourceId, sourceName: item.sourceName })
|
||||
})),
|
||||
historyPreview: chatData?.historyPreview,
|
||||
runningTime: +responseData.reduce((sum, item) => sum + (item.runningTime || 0), 0).toFixed(2)
|
||||
};
|
||||
@@ -50,64 +71,93 @@ const ResponseTags = ({ responseData = [] }: { responseData?: ChatHistoryItemRes
|
||||
};
|
||||
|
||||
return responseData.length === 0 ? null : (
|
||||
<Flex alignItems={'center'} mt={2} flexWrap={'wrap'}>
|
||||
{quoteList.length > 0 && (
|
||||
<MyTooltip label="查看引用">
|
||||
<Tag
|
||||
colorSchema="blue"
|
||||
cursor={'pointer'}
|
||||
{...TagStyles}
|
||||
onClick={() => setQuoteModalData(quoteList)}
|
||||
>
|
||||
{quoteList.length}条引用
|
||||
</Tag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
{chatAccount === 1 && (
|
||||
<>
|
||||
{sourceList.length > 0 && (
|
||||
<>
|
||||
{historyPreview.length > 0 && (
|
||||
<MyTooltip label={'点击查看完整对话记录'}>
|
||||
<Tag
|
||||
colorSchema="green"
|
||||
<ChatBoxDivider icon="core/chat/quoteFill" text={t('chat.Quote')} />
|
||||
<Flex alignItems={'center'} flexWrap={'wrap'} gap={2}>
|
||||
{sourceList.map((item) => (
|
||||
<Flex
|
||||
key={item.sourceName}
|
||||
alignItems={'center'}
|
||||
flexWrap={'wrap'}
|
||||
fontSize={'sm'}
|
||||
cursor={'pointer'}
|
||||
{...TagStyles}
|
||||
onClick={() => setContextModalData(historyPreview)}
|
||||
border={theme.borders.sm}
|
||||
py={1}
|
||||
px={2}
|
||||
borderRadius={'md'}
|
||||
_hover={{
|
||||
bg: 'myBlue.100'
|
||||
}}
|
||||
onClick={() => setQuoteModalData(quoteList)}
|
||||
>
|
||||
{historyPreview.length}条上下文
|
||||
</Tag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
<Image src={item.icon} alt={''} mr={1} w={'12px'} />
|
||||
{item.sourceName}
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
{chatAccount > 1 && (
|
||||
<Tag colorSchema="blue" {...TagStyles}>
|
||||
多组 AI 对话
|
||||
</Tag>
|
||||
)}
|
||||
<Flex alignItems={'center'} mt={2} flexWrap={'wrap'}>
|
||||
{quoteList.length > 0 && (
|
||||
<MyTooltip label="查看引用">
|
||||
<Tag
|
||||
colorSchema="blue"
|
||||
cursor={'pointer'}
|
||||
{...TagStyles}
|
||||
onClick={() => setQuoteModalData(quoteList)}
|
||||
>
|
||||
{quoteList.length}条引用
|
||||
</Tag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
{chatAccount === 1 && (
|
||||
<>
|
||||
{historyPreview.length > 0 && (
|
||||
<MyTooltip label={'点击查看完整对话记录'}>
|
||||
<Tag
|
||||
colorSchema="green"
|
||||
cursor={'pointer'}
|
||||
{...TagStyles}
|
||||
onClick={() => setContextModalData(historyPreview)}
|
||||
>
|
||||
{historyPreview.length}条上下文
|
||||
</Tag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{chatAccount > 1 && (
|
||||
<Tag colorSchema="blue" {...TagStyles}>
|
||||
多组 AI 对话
|
||||
</Tag>
|
||||
)}
|
||||
|
||||
{isPc && runningTime > 0 && (
|
||||
<MyTooltip label={'模块运行时间和'}>
|
||||
<Tag colorSchema="purple" cursor={'default'} {...TagStyles}>
|
||||
{runningTime}s
|
||||
{isPc && runningTime > 0 && (
|
||||
<MyTooltip label={'模块运行时间和'}>
|
||||
<Tag colorSchema="purple" cursor={'default'} {...TagStyles}>
|
||||
{runningTime}s
|
||||
</Tag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
<MyTooltip label={'点击查看完整响应'}>
|
||||
<Tag colorSchema="gray" cursor={'pointer'} {...TagStyles} onClick={onOpenWholeModal}>
|
||||
{t('chat.Complete Response')}
|
||||
</Tag>
|
||||
</MyTooltip>
|
||||
)}
|
||||
<MyTooltip label={'点击查看完整响应'}>
|
||||
<Tag colorSchema="gray" cursor={'pointer'} {...TagStyles} onClick={onOpenWholeModal}>
|
||||
{t('chat.Complete Response')}
|
||||
</Tag>
|
||||
</MyTooltip>
|
||||
|
||||
{!!quoteModalData && (
|
||||
<QuoteModal rawSearch={quoteModalData} onClose={() => setQuoteModalData(undefined)} />
|
||||
)}
|
||||
{!!contextModalData && (
|
||||
<ContextModal context={contextModalData} onClose={() => setContextModalData(undefined)} />
|
||||
)}
|
||||
{isOpenWholeModal && (
|
||||
<WholeResponseModal response={responseData} onClose={onCloseWholeModal} />
|
||||
)}
|
||||
</Flex>
|
||||
{!!quoteModalData && (
|
||||
<QuoteModal rawSearch={quoteModalData} onClose={() => setQuoteModalData(undefined)} />
|
||||
)}
|
||||
{!!contextModalData && (
|
||||
<ContextModal context={contextModalData} onClose={() => setContextModalData(undefined)} />
|
||||
)}
|
||||
{isOpenWholeModal && (
|
||||
<WholeResponseModal response={responseData} onClose={onCloseWholeModal} />
|
||||
)}
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ function Row({ label, value }: { label: string; value?: string | number | React.
|
||||
) : null;
|
||||
}
|
||||
|
||||
const ResponseModal = ({
|
||||
const WholeResponseModal = ({
|
||||
response,
|
||||
onClose
|
||||
}: {
|
||||
@@ -50,6 +50,7 @@ const ResponseModal = ({
|
||||
<Image
|
||||
mr={2}
|
||||
src={
|
||||
item.moduleLogo ||
|
||||
ModuleTemplatesFlat.find((template) => item.moduleType === template.flowType)?.logo
|
||||
}
|
||||
alt={''}
|
||||
@@ -192,10 +193,22 @@ const ResponseModal = ({
|
||||
}
|
||||
})()}
|
||||
/>
|
||||
|
||||
{/* plugin */}
|
||||
<Row
|
||||
label={t('chat.response.plugin output')}
|
||||
value={(() => {
|
||||
try {
|
||||
return JSON.stringify(activeModule?.pluginOutput, null, 2);
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
})()}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResponseModal;
|
||||
export default WholeResponseModal;
|
||||
|
||||
@@ -35,7 +35,7 @@ import { feConfigs } from '@/web/common/system/staticData';
|
||||
import { eventBus } from '@/web/common/utils/eventbus';
|
||||
import { adaptChat2GptMessages } from '@/utils/common/adapt/message';
|
||||
import { useMarkdown } from '@/web/common/hooks/useMarkdown';
|
||||
import { AppModuleItemType } from '@/types/app';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { VariableInputEnum } from '@/constants/app';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { MessageItemType } from '@/types/core/chat/type';
|
||||
@@ -54,6 +54,7 @@ import Avatar from '@/components/Avatar';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import MySelect from '@/components/Select';
|
||||
import MyTooltip from '../MyTooltip';
|
||||
import ChatBoxDivider from '@/components/core/chat/Divider';
|
||||
import dynamic from 'next/dynamic';
|
||||
const ResponseTags = dynamic(() => import('./ResponseTags'));
|
||||
const FeedbackModal = dynamic(() => import('./FeedbackModal'));
|
||||
@@ -99,7 +100,7 @@ type Props = {
|
||||
showEmptyIntro?: boolean;
|
||||
appAvatar?: string;
|
||||
userAvatar?: string;
|
||||
userGuideModule?: AppModuleItemType;
|
||||
userGuideModule?: ModuleItemType;
|
||||
active?: boolean;
|
||||
onUpdateVariable?: (e: Record<string, any>) => void;
|
||||
onStartChat?: (e: StartChatFnProps) => Promise<{
|
||||
@@ -488,7 +489,7 @@ const ChatBox = (
|
||||
|
||||
return {
|
||||
bg: colorMap[chatContent.status] || colorMap.loading,
|
||||
name: t(chatContent.moduleName || 'Running')
|
||||
name: t(chatContent.moduleName || 'common.Loading')
|
||||
};
|
||||
}, [chatHistory, isChatting, t]);
|
||||
/* style end */
|
||||
@@ -496,6 +497,7 @@ const ChatBox = (
|
||||
// page change and abort request
|
||||
useEffect(() => {
|
||||
isNewChatReplace.current = false;
|
||||
setQuestionGuide([]);
|
||||
return () => {
|
||||
chatController.current?.abort('leave');
|
||||
if (!isNewChatReplace.current) {
|
||||
@@ -750,38 +752,28 @@ const ChatBox = (
|
||||
{index === chatHistory.length - 1 &&
|
||||
!isChatting &&
|
||||
questionGuides.length > 0 && (
|
||||
<Flex
|
||||
mt={2}
|
||||
borderTop={theme.borders.sm}
|
||||
alignItems={'center'}
|
||||
flexWrap={'wrap'}
|
||||
>
|
||||
<Box
|
||||
color={'myGray.500'}
|
||||
mt={2}
|
||||
mr={2}
|
||||
fontSize={'sm'}
|
||||
fontStyle={'italic'}
|
||||
>
|
||||
{t('chat.Question Guide Tips')}
|
||||
</Box>
|
||||
{questionGuides.map((item) => (
|
||||
<Button
|
||||
mt={2}
|
||||
key={item}
|
||||
mr="2"
|
||||
borderRadius={'md'}
|
||||
variant={'outline'}
|
||||
colorScheme={'gray'}
|
||||
size={'xs'}
|
||||
onClick={() => {
|
||||
resetInputVal(item);
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</Button>
|
||||
))}
|
||||
</Flex>
|
||||
<Box mt={2}>
|
||||
<ChatBoxDivider
|
||||
icon="core/chat/QGFill"
|
||||
text={t('chat.Question Guide Tips')}
|
||||
/>
|
||||
<Flex alignItems={'center'} flexWrap={'wrap'} gap={2}>
|
||||
{questionGuides.map((item) => (
|
||||
<Button
|
||||
key={item}
|
||||
borderRadius={'md'}
|
||||
variant={'outline'}
|
||||
colorScheme={'gray'}
|
||||
size={'xs'}
|
||||
onClick={() => {
|
||||
resetInputVal(item);
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</Button>
|
||||
))}
|
||||
</Flex>
|
||||
</Box>
|
||||
)}
|
||||
{/* admin mark content */}
|
||||
{showMarkIcon && item.adminFeedback && (
|
||||
|
||||
@@ -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="1698493025597" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4931" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M845.4 481.9c24.1 0 47.1 4.6 68.9 13.9 21.8 9.3 40.9 22.1 57.2 38.3 16.3 16.3 29.2 35.2 38.5 56.9 9.3 21.7 14 44.5 14 68.5 0 24.8-4.7 47.8-14 69.1-9.3 21.3-22.2 40.1-38.5 56.3-16.3 16.3-35.4 29-57.2 38.3-21.8 9.3-44.8 13.9-68.9 13.9-22.6 0-43.8-4.1-63.6-12.2-19.9-8.1-37.6-18.8-53.1-31.9v145.2c0 20.9-7.4 38.7-22.2 53.4-14.8 14.7-32.7 22.1-53.7 22.1H77.1c-21 0-39.1-7.4-54.3-22.1C7.6 977 0 959.2 0 938.3V809.4c3.9-16.3 10.9-28.3 21-36 10.1-7.7 24.9-5.8 44.4 5.8 9.3 5.4 16.3 8.5 21 9.3 20.2 10.1 40.9 15.1 61.9 15.1s40.7-3.9 59-11.6c18.3-7.7 34.2-18.4 47.9-31.9 13.6-13.5 24.3-29.2 32.1-47 7.8-17.8 11.7-37.2 11.7-58.1s-3.9-40.4-11.7-58.6-18.5-34.1-32.1-47.6c-13.6-13.5-29.6-24.2-47.9-31.9-18.3-7.7-37.9-11.6-59-11.6-20.2 0-40.1 4.3-59.5 12.8-4.7 1.5-8.6 3.1-11.7 4.6-9.3 4.6-18.5 8.9-27.4 12.8s-16.9 5.4-23.9 4.6c-7-0.8-12.8-4.6-17.5-11.6-4.7-7-7.4-18.6-8.2-34.8V371.6c0-20.9 7.6-38.9 22.8-54C37.9 302.5 56 295 77.1 295h182.1c-12.5-15.5-22.4-32.7-29.8-51.7-7.4-19-11.1-39.3-11.1-61 0-25.5 4.9-49.4 14.6-71.4 9.7-22.1 22.8-41.2 39.1-57.5 16.3-16.3 35.6-29.2 57.8-38.9C352 4.8 375.6 0 400.5 0c24.9 0 48.5 4.8 70.6 14.5 22.2 9.7 41.5 22.6 57.8 38.9s29.4 35.4 39.1 57.5c9.7 22.1 14.6 45.9 14.6 71.4 0 43.4-13.6 80.9-40.9 112.6h110.9c21 0 38.9 7.5 53.7 22.6 14.8 15.1 22.2 33.1 22.2 54V526c15.6-13.2 33.3-23.8 53.1-31.9 20-8.1 41.2-12.2 63.8-12.2z" p-id="4932"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
@@ -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="1698397550956" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2700" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M970.43915282 590.98409465a55.23363335 55.23363335 0 0 0-69.04204072 35.90186227A394.36813906 394.36813906 0 0 1 521.94205392 898.63543088 392.15879331 392.15879331 0 0 1 125.36456912 512a392.15879331 392.15879331 0 0 1 396.5774848-386.63543088 400.99617502 400.99617502 0 0 1 256.8363931 92.24016784l-119.85698388-19.88410786a55.23363335 55.23363335 0 0 0-63.518677 45.8439149 55.23363335 55.23363335 0 0 0 45.84391489 63.51867829l234.19060403 38.66354218h9.38971716a55.23363335 55.23363335 0 0 0 18.77943563-3.31401797 18.22709886 18.22709886 0 0 0 5.52336243-3.31401798 43.08223369 43.08223369 0 0 0 11.04672744-6.07569919l4.97102697-6.07569919c0-2.76168122 4.97102697-4.97102697 7.18037141-8.28504493s0-5.52336372 2.76168252-7.73270948a74.01306768 74.01306768 0 0 0 3.86635343-9.94205393l41.42522469-220.93453081a55.23363335 55.23363335 0 0 0-110.46726541-20.98878138l-14.91308088 80.08876817A508.7017592 508.7017592 0 0 0 521.94205392 14.8973037 502.62606002 502.62606002 0 0 0 14.8973037 512a502.62606002 502.62606002 0 0 0 507.04475022 497.1026963A503.73073225 503.73073225 0 0 0 1009.1026963 660.02613665a55.23363335 55.23363335 0 0 0-38.66354348-69.042042z" p-id="2701"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -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="1698504394130" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4081" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M928 448h-64a19.2 19.2 0 0 1 0-38.4h64a19.2 19.2 0 0 1 0 38.4zM797.1072 738.4064l-45.2608-45.2608a19.2 19.2 0 0 1 27.1488-27.1488l45.3248 45.2608a19.2 19.2 0 0 1-27.2128 27.1488zM779.008 204.4032a19.2 19.2 0 0 1-27.1488-27.1488l45.2608-45.2608a19.2 19.2 0 0 1 27.2 27.1488z m-121.216 472.0128a282.368 282.368 0 0 0-17.2032 77.5808v20.8A37.7856 37.7856 0 0 1 614.4 810.6752V819.2H409.6v-8.5248a37.7856 37.7856 0 0 1-26.1888-35.84v-23.9488a290.0352 290.0352 0 0 0-16.9344-74.24A279.04 279.04 0 0 1 243.2 443.5968C243.2 290.56 363.52 166.4 512 166.4s268.8 124.16 268.8 277.1968a279.04 279.04 0 0 1-123.008 232.8192zM505.6 691.2a19.2 19.2 0 1 0-19.2-19.2 19.2 19.2 0 0 0 19.2 19.2z m6.4-358.4a115.2 115.2 0 0 0-114.4448 102.4h6.5024a17.728 17.728 0 1 0 20.9024 0h8.6656A79.8848 79.8848 0 0 1 512 368.64a77.7216 77.7216 0 0 1 76.8 79.36c0.8064 45.6064-64 76.8-64 76.8a97.4976 97.4976 0 0 0-34.432 46.4768 18.7392 18.7392 0 0 0-3.968 11.1232v7.68a56.6784 56.6784 0 0 0 0 11.52v6.4a19.2 19.2 0 0 0 38.4 0v-14.4768a18.6496 18.6496 0 0 1 0.384-4.6336C533.3632 557.9904 588.8 524.8 588.8 524.8c36.2368-23.296 38.4-76.8 38.4-76.8a115.2 115.2 0 0 0-115.2-115.2z m6.4-230.4A19.2 19.2 0 0 1 499.2 83.2v-64a19.2 19.2 0 1 1 38.4 0v64A19.2 19.2 0 0 1 518.4 102.4z m-264.3456 92.9536l-45.2608-45.2608A19.2 19.2 0 1 1 235.9424 122.88l45.2608 45.248a19.2 19.2 0 0 1-27.1488 27.2256zM160 448h-64a19.2 19.2 0 0 1 0-38.4h64a19.2 19.2 0 0 1 0 38.4z m94.0544 227.0464a19.2 19.2 0 0 1 27.1488 27.1488L235.9424 747.52a19.2 19.2 0 0 1-27.1488-27.1488zM652.8 846.9376a8.384 8.384 0 0 1 0.384 1.9072V870.4a8.4096 8.4096 0 0 1-0.384 1.9072V883.2H371.2v-51.2h281.6v14.9376zM627.2 947.2H396.8v-51.2h230.4v51.2z m-183.6672 23.9104H579.84a8.5248 8.5248 0 0 1 4.8896 1.6896H601.6v38.4H422.4v-38.4h16.2432a8.5248 8.5248 0 0 1 4.8896-1.6896z" fill="#1296db" p-id="4082"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -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="1698497259520" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10081" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M156.09136 606.57001a457.596822 457.596822 0 0 1 221.680239-392.516385 50.844091 50.844091 0 1 1 50.844091 86.943396 355.90864 355.90864 0 0 0-138.804369 152.532274h16.77855a152.532274 152.532274 0 1 1-152.532274 152.532274z m406.752731 0a457.596822 457.596822 0 0 1 221.680239-392.007944 50.844091 50.844091 0 1 1 50.844091 86.943396 355.90864 355.90864 0 0 0-138.804369 152.532274h16.77855a152.532274 152.532274 0 1 1-152.532274 152.532274z" fill="#E67E22" p-id="10082"></path></svg>
|
||||
|
After Width: | Height: | Size: 819 B |
@@ -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="1698491522536" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4046" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M512 642.9c81.6 0 157.6-42.3 229.5-130.9-72-88.6-147.9-130.9-229.5-130.9S354.4 423.4 282.5 512c71.9 88.6 147.9 130.9 229.5 130.9z m0 62.3c-105.3 0-201.4-53.6-288.5-160.7-15.4-18.9-15.4-46 0-64.9C310.6 372.4 406.7 318.8 512 318.8s201.4 53.6 288.5 160.7c15.4 18.9 15.4 46 0 64.9C713.4 651.6 617.3 705.2 512 705.2z m0 0" fill="#333333" p-id="4047"></path><path d="M512 540c15.5 0 28-12.5 28-28 0-15.4-12.5-28-28-28s-28 12.5-28 28c0 15.4 12.5 28 28 28z m0 71.9c-35.7 0-68.7-19-86.6-49.9-17.9-30.9-17.9-69 0-99.9 17.9-30.9 50.9-49.9 86.6-49.9 55.2 0 100 44.7 100 99.9 0 55.1-44.8 99.8-100 99.8z m0 0" fill="#333333" p-id="4048"></path><path d="M136 888V745c0-19.9-16.1-36-36-36s-36 16.1-36 36v155c0 33.1 26.9 60 60 60h155c19.9 0 36-16.1 36-36s-16.1-36-36-36H136zM136 136h143c19.9 0 36-16.1 36-36s-16.1-36-36-36H124c-33.1 0-60 26.9-60 60v155c0 19.9 16.1 36 36 36s36-16.1 36-36V136zM888 136v143c0 19.9 16.1 36 36 36s36-16.1 36-36V124c0-33.1-26.9-60-60-60H745c-19.9 0-36 16.1-36 36s16.1 36 36 36h143zM888 888H745c-19.9 0-36 16.1-36 36s16.1 36 36 36h155c33.1 0 60-26.9 60-60V745c0-19.9-16.1-36-36-36s-36 16.1-36 36v143z" fill="#333333" p-id="4049"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -92,7 +92,13 @@ const iconPaths = {
|
||||
pause: () => import('./icons/common/pause.svg'),
|
||||
'core/app/aiLight': () => import('./icons/core/app/aiLight.svg'),
|
||||
'core/app/aiFill': () => import('./icons/core/app/aiFill.svg'),
|
||||
'common/text/t': () => import('./icons/common/text/t.svg')
|
||||
'common/text/t': () => import('./icons/common/text/t.svg'),
|
||||
'common/navbar/pluginLight': () => import('./icons/common/navbar/pluginLight.svg'),
|
||||
'common/navbar/pluginFill': () => import('./icons/common/navbar/pluginFill.svg'),
|
||||
'common/refreshLight': () => import('./icons/common/refreshLight.svg'),
|
||||
'core/module/previewLight': () => import('./icons/core/module/previewLight.svg'),
|
||||
'core/chat/quoteFill': () => import('./icons/core/chat/quoteFill.svg'),
|
||||
'core/chat/QGFill': () => import('./icons/core/chat/QGFill.svg')
|
||||
};
|
||||
|
||||
export type IconName = keyof typeof iconPaths;
|
||||
|
||||
@@ -40,6 +40,13 @@ const Navbar = ({ unread }: { unread: number }) => {
|
||||
link: `/app/list`,
|
||||
activeLink: ['/app/list', '/app/detail']
|
||||
},
|
||||
{
|
||||
label: t('navbar.Plugin'),
|
||||
icon: 'common/navbar/pluginLight',
|
||||
activeIcon: 'common/navbar/pluginFill',
|
||||
link: `/plugin/list`,
|
||||
activeLink: ['/plugin/list', '/plugin/edit']
|
||||
},
|
||||
{
|
||||
label: t('navbar.Datasets'),
|
||||
icon: 'dbLight',
|
||||
|
||||
@@ -43,7 +43,6 @@ const MyModal = ({
|
||||
{...props}
|
||||
>
|
||||
{!!title && <ModalHeader>{title}</ModalHeader>}
|
||||
{onClose && <ModalCloseButton />}
|
||||
<Box
|
||||
overflow={props.overflow || 'overlay'}
|
||||
h={'100%'}
|
||||
@@ -52,6 +51,7 @@ const MyModal = ({
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
{onClose && <ModalCloseButton />}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import { Box, useTheme, type BoxProps } from '@chakra-ui/react';
|
||||
import MyBox from '../common/MyBox';
|
||||
|
||||
const PageContainer = ({ children, ...props }: BoxProps) => {
|
||||
const PageContainer = ({ children, ...props }: BoxProps & { isLoading?: boolean }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box bg={'myGray.100'} h={'100%'} p={[0, 5]} px={[0, 6]} {...props}>
|
||||
<MyBox bg={'myGray.100'} h={'100%'} p={[0, 5]} px={[0, 6]} {...props}>
|
||||
<Box
|
||||
h={'100%'}
|
||||
bg={'white'}
|
||||
@@ -14,7 +15,7 @@ const PageContainer = ({ children, ...props }: BoxProps) => {
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
</MyBox>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import MyModal from '../MyModal';
|
||||
import { Box, Button, Grid, useTheme } from '@chakra-ui/react';
|
||||
import { Box, Button, Flex, Grid, useTheme } from '@chakra-ui/react';
|
||||
import { PromptTemplateItem } from '@fastgpt/global/core/ai/type.d';
|
||||
import { ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
|
||||
@@ -13,14 +13,14 @@ const PromptTemplate = ({
|
||||
title: string;
|
||||
templates: PromptTemplateItem[];
|
||||
onClose: () => void;
|
||||
onSuccess: (e: string) => void;
|
||||
onSuccess: (e: PromptTemplateItem) => void;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [selectTemplateTitle, setSelectTemplateTitle] = useState<PromptTemplateItem>();
|
||||
|
||||
return (
|
||||
<MyModal isOpen title={title} onClose={onClose}>
|
||||
<ModalBody w={'600px'}>
|
||||
<MyModal isOpen title={title} onClose={onClose} isCentered>
|
||||
<ModalBody h="100%" w={'600px'} maxW={'90vw'} overflowY={'auto'}>
|
||||
<Grid gridTemplateColumns={['1fr', '1fr 1fr']} gridGap={4}>
|
||||
{templates.map((item) => (
|
||||
<Box
|
||||
@@ -38,8 +38,9 @@ const PromptTemplate = ({
|
||||
onClick={() => setSelectTemplateTitle(item)}
|
||||
>
|
||||
<Box>{item.title}</Box>
|
||||
|
||||
<Box color={'myGray.600'} fontSize={'sm'} whiteSpace={'pre-wrap'}>
|
||||
{item.value}
|
||||
{item.desc}
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
@@ -50,7 +51,7 @@ const PromptTemplate = ({
|
||||
disabled={!selectTemplateTitle}
|
||||
onClick={() => {
|
||||
if (!selectTemplateTitle) return;
|
||||
onSuccess(selectTemplateTitle.value);
|
||||
onSuccess(selectTemplateTitle);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
|
||||
19
projects/app/src/components/common/MyBox/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import Loading from '@/components/Loading';
|
||||
|
||||
type Props = BoxProps & {
|
||||
isLoading?: boolean;
|
||||
text?: string;
|
||||
};
|
||||
|
||||
const MyBox = ({ text, isLoading, children, ...props }: Props) => {
|
||||
return (
|
||||
<Box position={'relative'} {...props}>
|
||||
{children}
|
||||
{isLoading && <Loading fixed={false} text={text} />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyBox;
|
||||
@@ -30,9 +30,9 @@ const ParentPaths = (props: {
|
||||
{concatPaths.map((item, i) => (
|
||||
<Flex key={item.parentId} alignItems={'center'}>
|
||||
<Box
|
||||
fontSize={['md', 'lg']}
|
||||
fontSize={['sm', 'lg']}
|
||||
py={1}
|
||||
px={[0, 2]}
|
||||
px={[1, 2]}
|
||||
borderRadius={'md'}
|
||||
{...(i === concatPaths.length - 1
|
||||
? {
|
||||
@@ -51,7 +51,7 @@ const ParentPaths = (props: {
|
||||
{item.parentName}
|
||||
</Box>
|
||||
{i !== concatPaths.length - 1 && (
|
||||
<MyIcon name={'rightArrowLight'} color={'myGray.500'} w={['18px', '24px']} />
|
||||
<MyIcon name={'rightArrowLight'} color={'myGray.500'} w={['14px', '24px']} />
|
||||
)}
|
||||
</Flex>
|
||||
))}
|
||||
|
||||
19
projects/app/src/components/core/chat/Divider/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import MyIcon, { type IconName } from '@/components/Icon';
|
||||
|
||||
const ChatBoxDivider = ({ icon, text }: { icon: IconName; text: string }) => {
|
||||
return (
|
||||
<Box>
|
||||
<Flex alignItems={'center'} py={2} gap={2}>
|
||||
<MyIcon name={icon} w={'14px'} color={'myGray.900'} />
|
||||
<Box color={'myGray.500'} fontSize={'sm'}>
|
||||
{text}
|
||||
</Box>
|
||||
<Box h={'1px'} mt={1} bg={'myGray.200'} flex={'1'} />
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatBoxDivider;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { EditFormType } from '@/web/core/app/basicSettings';
|
||||
@@ -45,7 +45,6 @@ const AIChatSettingsModal = ({
|
||||
|
||||
const [selectTemplateData, setSelectTemplateData] = useState<{
|
||||
title: string;
|
||||
key: 'quoteTemplate' | 'quotePrompt';
|
||||
templates: PromptTemplateItem[];
|
||||
}>();
|
||||
|
||||
@@ -163,8 +162,7 @@ const AIChatSettingsModal = ({
|
||||
{...selectTemplateBtn}
|
||||
onClick={() =>
|
||||
setSelectTemplateData({
|
||||
title: '选择引用内容模板',
|
||||
key: 'quoteTemplate',
|
||||
title: '选择知识库提示词模板',
|
||||
templates: Prompt_QuoteTemplateList
|
||||
})
|
||||
}
|
||||
@@ -190,19 +188,6 @@ const AIChatSettingsModal = ({
|
||||
>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
|
||||
</MyTooltip>
|
||||
<Box flex={1} />
|
||||
<Box
|
||||
{...selectTemplateBtn}
|
||||
onClick={() =>
|
||||
setSelectTemplateData({
|
||||
title: '选择引用提示词模板',
|
||||
key: 'quotePrompt',
|
||||
templates: Prompt_QuotePromptList
|
||||
})
|
||||
}
|
||||
>
|
||||
选择模板
|
||||
</Box>
|
||||
</Flex>
|
||||
<Textarea
|
||||
rows={11}
|
||||
@@ -227,7 +212,12 @@ const AIChatSettingsModal = ({
|
||||
title={selectTemplateData.title}
|
||||
templates={selectTemplateData.templates}
|
||||
onClose={() => setSelectTemplateData(undefined)}
|
||||
onSuccess={(e) => setValue(selectTemplateData.key, e)}
|
||||
onSuccess={(e) => {
|
||||
const quoteVal = e.value;
|
||||
const promptVal = Prompt_QuotePromptList.find((item) => item.title === e.title)?.value;
|
||||
setValue('quoteTemplate', quoteVal);
|
||||
setValue('quotePrompt', promptVal);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</MyModal>
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AppModuleItemType } from '@/types/app';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { AppSchema } from '@/types/mongoSchema';
|
||||
import React, {
|
||||
useMemo,
|
||||
@@ -10,7 +10,7 @@ import React, {
|
||||
} from 'react';
|
||||
import { Box, Flex, IconButton } from '@chakra-ui/react';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { FlowModuleTypeEnum } from '@/constants/flow';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { streamFetch } from '@/web/common/api/fetch';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
@@ -28,7 +28,7 @@ const ChatTest = (
|
||||
onClose
|
||||
}: {
|
||||
app: AppSchema;
|
||||
modules?: AppModuleItemType[];
|
||||
modules?: ModuleItemType[];
|
||||
onClose: () => void;
|
||||
},
|
||||
ref: ForwardedRef<ChatTestComponentRef>
|
||||
@@ -41,7 +41,7 @@ const ChatTest = (
|
||||
async ({ chatList, controller, generatingMessage, variables }: StartChatFnProps) => {
|
||||
const historyMaxLen =
|
||||
modules
|
||||
?.find((item) => item.flowType === FlowModuleTypeEnum.historyNode)
|
||||
?.find((item) => item.flowType === FlowNodeTypeEnum.historyNode)
|
||||
?.inputs?.find((item) => item.key === 'maxContext')?.value || 0;
|
||||
const history = chatList.slice(-historyMaxLen - 2, -2);
|
||||
|
||||
@@ -10,9 +10,12 @@ import {
|
||||
} from 'reactflow';
|
||||
import type {
|
||||
FlowModuleItemType,
|
||||
FlowOutputTargetItemType,
|
||||
FlowModuleItemChangeProps
|
||||
} from '@/types/core/app/flow';
|
||||
FlowModuleTemplateType
|
||||
} from '@fastgpt/global/core/module/type.d';
|
||||
import type {
|
||||
FlowNodeOutputTargetItemType,
|
||||
FlowNodeChangeProps
|
||||
} from '@fastgpt/global/core/module/node/type';
|
||||
import React, {
|
||||
type SetStateAction,
|
||||
type Dispatch,
|
||||
@@ -25,17 +28,21 @@ import React, {
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { appModule2FlowEdge, appModule2FlowNode } from '@/utils/adapt';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { FlowModuleTypeEnum, FlowValueTypeEnum } from '@/constants/flow';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { AppModuleItemType } from '@/types/app';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
|
||||
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
|
||||
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
|
||||
export type useFlowStoreType = {
|
||||
appId: string;
|
||||
export type useFlowProviderStoreType = {
|
||||
reactFlowWrapper: null | React.RefObject<HTMLDivElement>;
|
||||
filterAppIds: string[];
|
||||
nodes: Node<FlowModuleItemType, string | undefined>[];
|
||||
setNodes: Dispatch<SetStateAction<Node<FlowModuleItemType, string | undefined>[]>>;
|
||||
onNodesChange: OnChange<NodeChange>;
|
||||
@@ -44,8 +51,9 @@ export type useFlowStoreType = {
|
||||
onEdgesChange: OnChange<EdgeChange>;
|
||||
onFixView: () => void;
|
||||
onDelNode: (nodeId: string) => void;
|
||||
onChangeNode: (e: FlowModuleItemChangeProps) => void;
|
||||
onChangeNode: (e: FlowNodeChangeProps) => void;
|
||||
onCopyNode: (nodeId: string) => void;
|
||||
onResetNode: (id: string, module: FlowModuleTemplateType) => void;
|
||||
onDelEdge: (e: {
|
||||
moduleId: string;
|
||||
sourceHandle?: string | undefined;
|
||||
@@ -53,12 +61,12 @@ export type useFlowStoreType = {
|
||||
}) => void;
|
||||
onDelConnect: (id: string) => void;
|
||||
onConnect: ({ connect }: { connect: Connection }) => any;
|
||||
initData: (modules: AppModuleItemType[]) => void;
|
||||
initData: (modules: ModuleItemType[]) => void;
|
||||
};
|
||||
|
||||
const StateContext = createContext<useFlowStoreType>({
|
||||
appId: '',
|
||||
const StateContext = createContext<useFlowProviderStoreType>({
|
||||
reactFlowWrapper: null,
|
||||
filterAppIds: [],
|
||||
nodes: [],
|
||||
setNodes: function (
|
||||
value: React.SetStateAction<Node<FlowModuleItemType, string | undefined>[]>
|
||||
@@ -78,11 +86,10 @@ const StateContext = createContext<useFlowStoreType>({
|
||||
onFixView: function (): void {
|
||||
return;
|
||||
},
|
||||
|
||||
onDelNode: function (nodeId: string): void {
|
||||
return;
|
||||
},
|
||||
onChangeNode: function (e: FlowModuleItemChangeProps): void {
|
||||
onChangeNode: function (e: FlowNodeChangeProps): void {
|
||||
return;
|
||||
},
|
||||
onCopyNode: function (nodeId: string): void {
|
||||
@@ -101,13 +108,22 @@ const StateContext = createContext<useFlowStoreType>({
|
||||
onConnect: function ({ connect }: { connect: Connection }) {
|
||||
return;
|
||||
},
|
||||
initData: function (modules: AppModuleItemType[]): void {
|
||||
initData: function (modules: ModuleItemType[]): void {
|
||||
throw new Error('Function not implemented.');
|
||||
},
|
||||
onResetNode: function (id: string, module: FlowModuleTemplateType): void {
|
||||
throw new Error('Function not implemented.');
|
||||
}
|
||||
});
|
||||
export const useFlowStore = () => useContext(StateContext);
|
||||
export const useFlowProviderStore = () => useContext(StateContext);
|
||||
|
||||
export const FlowProvider = ({ appId, children }: { appId: string; children: React.ReactNode }) => {
|
||||
export const FlowProvider = ({
|
||||
filterAppIds = [],
|
||||
children
|
||||
}: {
|
||||
filterAppIds?: string[];
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const reactFlowWrapper = useRef<HTMLDivElement>(null);
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
@@ -156,8 +172,11 @@ export const FlowProvider = ({ appId, children }: { appId: string; children: Rea
|
||||
({ connect }: { connect: Connection }) => {
|
||||
const source = nodes.find((node) => node.id === connect.source)?.data;
|
||||
const sourceType = (() => {
|
||||
if (source?.flowType === FlowModuleTypeEnum.classifyQuestion) {
|
||||
return FlowValueTypeEnum.boolean;
|
||||
if (source?.flowType === FlowNodeTypeEnum.classifyQuestion) {
|
||||
return FlowNodeValTypeEnum.boolean;
|
||||
}
|
||||
if (source?.flowType === FlowNodeTypeEnum.pluginInput) {
|
||||
return source?.inputs.find((input) => input.key === connect.sourceHandle)?.valueType;
|
||||
}
|
||||
return source?.outputs.find((output) => output.key === connect.sourceHandle)?.valueType;
|
||||
})();
|
||||
@@ -173,8 +192,8 @@ export const FlowProvider = ({ appId, children }: { appId: string; children: Rea
|
||||
});
|
||||
}
|
||||
if (
|
||||
sourceType !== FlowValueTypeEnum.any &&
|
||||
targetType !== FlowValueTypeEnum.any &&
|
||||
sourceType !== FlowNodeValTypeEnum.any &&
|
||||
targetType !== FlowNodeValTypeEnum.any &&
|
||||
sourceType !== targetType
|
||||
) {
|
||||
return toast({
|
||||
@@ -209,15 +228,31 @@ export const FlowProvider = ({ appId, children }: { appId: string; children: Rea
|
||||
);
|
||||
|
||||
const onChangeNode = useCallback(
|
||||
({ moduleId, key, type = 'inputs', value }: FlowModuleItemChangeProps) => {
|
||||
({ moduleId, type, key, value, index }: FlowNodeChangeProps) => {
|
||||
setNodes((nodes) =>
|
||||
nodes.map((node) => {
|
||||
if (node.id !== moduleId) return node;
|
||||
|
||||
const updateObj: Record<string, any> = {};
|
||||
|
||||
if (type === 'inputs') {
|
||||
if (type === 'attr') {
|
||||
if (key) {
|
||||
updateObj[key] = value;
|
||||
}
|
||||
} else if (type === 'updateInput') {
|
||||
updateObj.inputs = node.data.inputs.map((item) => (item.key === key ? value : item));
|
||||
} else if (type === 'replaceInput') {
|
||||
onDelEdge({ moduleId, targetHandle: key });
|
||||
const oldInputIndex = node.data.inputs.findIndex((item) => item.key === key);
|
||||
updateObj.inputs = node.data.inputs.filter((item) => item.key !== key);
|
||||
setTimeout(() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
index: oldInputIndex,
|
||||
value
|
||||
});
|
||||
});
|
||||
} else if (type === 'addInput') {
|
||||
const input = node.data.inputs.find((input) => input.key === value.key);
|
||||
if (input) {
|
||||
@@ -227,22 +262,51 @@ export const FlowProvider = ({ appId, children }: { appId: string; children: Rea
|
||||
});
|
||||
updateObj.inputs = node.data.inputs;
|
||||
} else {
|
||||
updateObj.inputs = node.data.inputs.concat(value);
|
||||
if (index !== undefined) {
|
||||
const inputs = [...node.data.inputs];
|
||||
inputs.splice(index, 0, value);
|
||||
updateObj.inputs = inputs;
|
||||
} else {
|
||||
updateObj.inputs = node.data.inputs.concat(value);
|
||||
}
|
||||
}
|
||||
} else if (type === 'delInput') {
|
||||
onDelEdge({ moduleId, targetHandle: key });
|
||||
updateObj.inputs = node.data.inputs.filter((item) => item.key !== key);
|
||||
} else if (type === 'attr') {
|
||||
updateObj[key] = value;
|
||||
} else if (type === 'outputs') {
|
||||
// del output connect
|
||||
const delOutputs = node.data.outputs.filter(
|
||||
(item) => !value.find((output: FlowOutputTargetItemType) => output.key === item.key)
|
||||
);
|
||||
delOutputs.forEach((output) => {
|
||||
onDelEdge({ moduleId, sourceHandle: output.key });
|
||||
} else if (type === 'updateOutput') {
|
||||
updateObj.outputs = node.data.outputs.map((item) => (item.key === key ? value : item));
|
||||
} else if (type === 'replaceOutput') {
|
||||
onDelEdge({ moduleId, sourceHandle: key });
|
||||
const oldOutputIndex = node.data.outputs.findIndex((item) => item.key === key);
|
||||
updateObj.outputs = node.data.outputs.filter((item) => item.key !== key);
|
||||
setTimeout(() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
index: oldOutputIndex,
|
||||
value
|
||||
});
|
||||
});
|
||||
updateObj.outputs = value;
|
||||
} else if (type === 'addOutput') {
|
||||
const output = node.data.outputs.find((output) => output.key === value.key);
|
||||
if (output) {
|
||||
toast({
|
||||
status: 'warning',
|
||||
title: 'key 重复'
|
||||
});
|
||||
updateObj.outputs = node.data.outputs;
|
||||
} else {
|
||||
if (index !== undefined) {
|
||||
const outputs = [...node.data.outputs];
|
||||
outputs.splice(index, 0, value);
|
||||
updateObj.outputs = outputs;
|
||||
} else {
|
||||
updateObj.outputs = node.data.outputs.concat(value);
|
||||
}
|
||||
}
|
||||
} else if (type === 'delOutput') {
|
||||
onDelEdge({ moduleId, sourceHandle: key });
|
||||
updateObj.outputs = node.data.outputs.filter((item) => item.key !== key);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -287,8 +351,36 @@ export const FlowProvider = ({ appId, children }: { appId: string; children: Rea
|
||||
[setNodes]
|
||||
);
|
||||
|
||||
// reset a node data. delete edge and replace it
|
||||
const onResetNode = useCallback(
|
||||
(id: string, module: FlowModuleTemplateType) => {
|
||||
setNodes((state) =>
|
||||
state.map((node) => {
|
||||
if (node.id === id) {
|
||||
// delete edge
|
||||
node.data.inputs.forEach((item) => {
|
||||
onDelEdge({ moduleId: id, targetHandle: item.key });
|
||||
});
|
||||
node.data.outputs.forEach((item) => {
|
||||
onDelEdge({ moduleId: id, sourceHandle: item.key });
|
||||
});
|
||||
return {
|
||||
...node,
|
||||
data: {
|
||||
...node.data,
|
||||
...module
|
||||
}
|
||||
};
|
||||
}
|
||||
return node;
|
||||
})
|
||||
);
|
||||
},
|
||||
[onDelEdge, setNodes]
|
||||
);
|
||||
|
||||
const initData = useCallback(
|
||||
(modules: AppModuleItemType[]) => {
|
||||
(modules: ModuleItemType[]) => {
|
||||
const edges = appModule2FlowEdge({
|
||||
modules,
|
||||
onDelete: onDelConnect
|
||||
@@ -304,7 +396,7 @@ export const FlowProvider = ({ appId, children }: { appId: string; children: Rea
|
||||
|
||||
// use eventbus to avoid refresh ReactComponents
|
||||
useEffect(() => {
|
||||
const update = (e: FlowModuleItemChangeProps) => {
|
||||
const update = (e: FlowNodeChangeProps) => {
|
||||
onChangeNode(e);
|
||||
};
|
||||
eventBus.on(EventNameEnum.updaterNode, update);
|
||||
@@ -314,8 +406,8 @@ export const FlowProvider = ({ appId, children }: { appId: string; children: Rea
|
||||
}, [onChangeNode]);
|
||||
|
||||
const value = {
|
||||
appId,
|
||||
reactFlowWrapper,
|
||||
filterAppIds,
|
||||
nodes,
|
||||
setNodes,
|
||||
onNodesChange,
|
||||
@@ -325,6 +417,7 @@ export const FlowProvider = ({ appId, children }: { appId: string; children: Rea
|
||||
onFixView,
|
||||
onDelNode,
|
||||
onChangeNode,
|
||||
onResetNode,
|
||||
onCopyNode,
|
||||
onDelEdge,
|
||||
onDelConnect,
|
||||
@@ -337,6 +430,53 @@ export const FlowProvider = ({ appId, children }: { appId: string; children: Rea
|
||||
|
||||
export default React.memo(FlowProvider);
|
||||
|
||||
export const onChangeNode = (e: FlowModuleItemChangeProps) => {
|
||||
export const onChangeNode = (e: FlowNodeChangeProps) => {
|
||||
eventBus.emit(EventNameEnum.updaterNode, e);
|
||||
};
|
||||
|
||||
export function flowNode2Modules({
|
||||
nodes,
|
||||
edges
|
||||
}: {
|
||||
nodes: Node<FlowModuleItemType, string | undefined>[];
|
||||
edges: Edge<any>[];
|
||||
}) {
|
||||
const modules: ModuleItemType[] = nodes.map((item) => ({
|
||||
moduleId: item.data.moduleId,
|
||||
name: item.data.name,
|
||||
logo: item.data.logo,
|
||||
flowType: item.data.flowType,
|
||||
showStatus: item.data.showStatus,
|
||||
position: item.position,
|
||||
inputs: item.data.inputs.map((item) => ({
|
||||
...item,
|
||||
connected: item.connected ?? item.type !== FlowNodeInputTypeEnum.target
|
||||
})),
|
||||
outputs: item.data.outputs.map((item) => ({
|
||||
...item,
|
||||
targets: [] as FlowNodeOutputTargetItemType[]
|
||||
}))
|
||||
}));
|
||||
|
||||
// update inputs and outputs
|
||||
modules.forEach((module) => {
|
||||
module.inputs.forEach((input) => {
|
||||
input.connected =
|
||||
input.connected ||
|
||||
!!edges.find((edge) => edge.target === module.moduleId && edge.targetHandle === input.key);
|
||||
});
|
||||
module.outputs.forEach((output) => {
|
||||
output.targets = edges
|
||||
.filter(
|
||||
(edge) =>
|
||||
edge.source === module.moduleId && edge.sourceHandle === output.key && edge.targetHandle
|
||||
)
|
||||
.map((edge) => ({
|
||||
moduleId: edge.target,
|
||||
key: edge.targetHandle || ''
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
return modules;
|
||||
}
|
||||
@@ -3,13 +3,13 @@ import { Textarea, Button, ModalBody, ModalFooter } from '@chakra-ui/react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { useFlowStore } from './Provider';
|
||||
import { useFlowProviderStore } from './FlowProvider';
|
||||
|
||||
const ImportSettings = ({ onClose }: { onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const [value, setValue] = useState('');
|
||||
const { setNodes, setEdges, initData } = useFlowStore();
|
||||
const { setNodes, setEdges, initData } = useFlowProviderStore();
|
||||
|
||||
return (
|
||||
<MyModal isOpen w={'600px'} onClose={onClose} title={t('app.Import Config')}>
|
||||
@@ -1,22 +1,22 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { ModalBody, Flex, Box, useTheme, ModalFooter, Button } from '@chakra-ui/react';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import { getMyModels } from '@/web/core/app/api';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { SelectAppItemType } from '@/types/core/app/flow';
|
||||
import type { SelectAppItemType } from '@fastgpt/global/core/module/type';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLoading } from '@/web/common/hooks/useLoading';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
|
||||
const SelectAppModal = ({
|
||||
defaultApps = [],
|
||||
filterApps = [],
|
||||
filterAppIds = [],
|
||||
max = 1,
|
||||
onClose,
|
||||
onSuccess
|
||||
}: {
|
||||
defaultApps: string[];
|
||||
filterApps?: string[];
|
||||
filterAppIds?: string[];
|
||||
max?: number;
|
||||
onClose: () => void;
|
||||
onSuccess: (e: SelectAppItemType[]) => void;
|
||||
@@ -26,11 +26,12 @@ const SelectAppModal = ({
|
||||
const theme = useTheme();
|
||||
const [selectedApps, setSelectedApps] = React.useState<string[]>(defaultApps);
|
||||
/* 加载模型 */
|
||||
const { data = [], isLoading } = useQuery(['loadMyApos'], () => getMyModels());
|
||||
const { myApps, loadMyApps } = useUserStore();
|
||||
const { isLoading } = useQuery(['loadMyApos'], () => loadMyApps());
|
||||
|
||||
const apps = useMemo(
|
||||
() => data.filter((app) => !filterApps.includes(app._id)),
|
||||
[data, filterApps]
|
||||
() => myApps.filter((app) => !filterAppIds.includes(app._id)),
|
||||
[myApps, filterAppIds]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -38,13 +39,13 @@ const SelectAppModal = ({
|
||||
isOpen
|
||||
title={`选择应用${max > 1 ? `(${selectedApps.length}/${max})` : ''}`}
|
||||
onClose={onClose}
|
||||
w={'700px'}
|
||||
minW={'700px'}
|
||||
position={'relative'}
|
||||
>
|
||||
<ModalBody
|
||||
minH={'300px'}
|
||||
display={'grid'}
|
||||
gridTemplateColumns={['1fr', 'repeat(3,1fr)']}
|
||||
gridTemplateColumns={['1fr', 'repeat(3, minmax(0, 1fr))']}
|
||||
gridGap={4}
|
||||
>
|
||||
{apps.map((app) => (
|
||||
@@ -53,8 +54,7 @@ const SelectAppModal = ({
|
||||
alignItems={'center'}
|
||||
border={theme.borders.base}
|
||||
borderRadius={'md'}
|
||||
px={1}
|
||||
py={2}
|
||||
p={2}
|
||||
cursor={'pointer'}
|
||||
{...(selectedApps.includes(app._id)
|
||||
? {
|
||||
243
projects/app/src/components/core/module/Flow/TemplateList.tsx
Normal file
@@ -0,0 +1,243 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import type {
|
||||
FlowModuleTemplateType,
|
||||
SystemModuleTemplateType
|
||||
} from '@fastgpt/global/core/module/type.d';
|
||||
import { useViewport, XYPosition } from 'reactflow';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { useFlowProviderStore } from './FlowProvider';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { appModule2FlowNode } from '@/utils/adapt';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import MyIcon from '@/components/Icon';
|
||||
import EmptyTip from '@/components/EmptyTip';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { getPluginModuleDetail } from '@/web/core/plugin/api';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
|
||||
enum TemplateTypeEnum {
|
||||
system = 'system',
|
||||
combine = 'combine'
|
||||
}
|
||||
|
||||
export type ModuleTemplateProps = {
|
||||
systemTemplates: SystemModuleTemplateType;
|
||||
pluginTemplates: SystemModuleTemplateType;
|
||||
show2Plugin?: boolean;
|
||||
};
|
||||
|
||||
const ModuleTemplateList = ({
|
||||
systemTemplates,
|
||||
pluginTemplates,
|
||||
show2Plugin = false,
|
||||
isOpen,
|
||||
onClose
|
||||
}: ModuleTemplateProps & {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const [templateType, setTemplateType] = React.useState(TemplateTypeEnum.system);
|
||||
|
||||
const typeList = useMemo(
|
||||
() => [
|
||||
{
|
||||
type: TemplateTypeEnum.system,
|
||||
label: t('app.module.System Module'),
|
||||
child: <RenderList templates={systemTemplates} onClose={onClose} />
|
||||
},
|
||||
{
|
||||
type: TemplateTypeEnum.combine,
|
||||
label: t('plugin.Plugin Module'),
|
||||
child: <RenderList templates={pluginTemplates} onClose={onClose} />
|
||||
}
|
||||
],
|
||||
[pluginTemplates, onClose, systemTemplates, t]
|
||||
);
|
||||
const TemplateItem = useMemo(
|
||||
() => typeList.find((item) => item.type === templateType)?.child,
|
||||
[templateType, typeList]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
zIndex={2}
|
||||
display={isOpen ? 'block' : 'none'}
|
||||
position={'absolute'}
|
||||
top={0}
|
||||
left={0}
|
||||
bottom={0}
|
||||
w={'360px'}
|
||||
onClick={onClose}
|
||||
/>
|
||||
<Flex
|
||||
zIndex={3}
|
||||
flexDirection={'column'}
|
||||
position={'absolute'}
|
||||
top={'65px'}
|
||||
left={0}
|
||||
pb={4}
|
||||
h={isOpen ? 'calc(100% - 100px)' : '0'}
|
||||
w={isOpen ? ['100%', '360px'] : '0'}
|
||||
bg={'white'}
|
||||
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
|
||||
borderRadius={'20px'}
|
||||
overflow={'hidden'}
|
||||
transition={'.2s ease'}
|
||||
userSelect={'none'}
|
||||
>
|
||||
<Flex pt={4} pb={1} px={5} gap={4} alignItems={'center'} fontSize={['md', 'xl']}>
|
||||
{typeList.map((item) => (
|
||||
<Box
|
||||
key={item.label}
|
||||
borderBottom={'2px solid transparent'}
|
||||
{...(item.type === templateType
|
||||
? {
|
||||
color: 'myBlue.700',
|
||||
borderBottomColor: 'myBlue.700',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
: {
|
||||
cursor: 'pointer',
|
||||
onClick: () => setTemplateType(item.type)
|
||||
})}
|
||||
>
|
||||
{item.label}
|
||||
</Box>
|
||||
))}
|
||||
<Box flex={1} />
|
||||
{show2Plugin && templateType === TemplateTypeEnum.combine && (
|
||||
<Flex
|
||||
alignItems={'center'}
|
||||
_hover={{ textDecoration: 'underline' }}
|
||||
cursor={'pointer'}
|
||||
onClick={() => router.push('/plugin/list')}
|
||||
>
|
||||
<Box fontSize={'sm'} transform={'translateY(-1px)'}>
|
||||
{t('plugin.To Edit Plugin')}
|
||||
</Box>
|
||||
<MyIcon name={'rightArrowLight'} w={'12px'} />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
{TemplateItem}
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ModuleTemplateList);
|
||||
|
||||
var RenderList = React.memo(function RenderList({
|
||||
templates,
|
||||
onClose
|
||||
}: {
|
||||
templates: {
|
||||
label: string;
|
||||
list: FlowModuleTemplateType[];
|
||||
}[];
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { isPc } = useSystemStore();
|
||||
const { setNodes, reactFlowWrapper } = useFlowProviderStore();
|
||||
const { x, y, zoom } = useViewport();
|
||||
const { setLoading } = useSystemStore();
|
||||
const { toast } = useToast();
|
||||
|
||||
const onAddNode = useCallback(
|
||||
async ({ template, position }: { template: FlowModuleTemplateType; position: XYPosition }) => {
|
||||
if (!reactFlowWrapper?.current) return;
|
||||
|
||||
let templateModule = { ...template };
|
||||
|
||||
// get plugin module
|
||||
try {
|
||||
if (templateModule.flowType === FlowNodeTypeEnum.pluginModule) {
|
||||
setLoading(true);
|
||||
const pluginModule = await getPluginModuleDetail(templateModule.id);
|
||||
templateModule = {
|
||||
...templateModule,
|
||||
...pluginModule
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
return toast({
|
||||
status: 'error',
|
||||
title: getErrText(e, t('plugin.Get Plugin Module Detail Failed'))
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||
const mouseX = (position.x - reactFlowBounds.left - x) / zoom - 100;
|
||||
const mouseY = (position.y - reactFlowBounds.top - y) / zoom;
|
||||
|
||||
setNodes((state) =>
|
||||
state.concat(
|
||||
appModule2FlowNode({
|
||||
item: {
|
||||
...templateModule,
|
||||
moduleId: nanoid(),
|
||||
position: { x: mouseX, y: mouseY - 20 }
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
[reactFlowWrapper, setLoading, setNodes, t, toast, x, y, zoom]
|
||||
);
|
||||
|
||||
const list = useMemo(() => templates.map((item) => item.list).flat(), [templates]);
|
||||
|
||||
return list.length === 0 ? (
|
||||
<EmptyTip text={t('app.module.No Modules')} />
|
||||
) : (
|
||||
<Box flex={'1 0 0'} overflow={'overlay'}>
|
||||
<Box w={['100%', '330px']} mx={'auto'}>
|
||||
{list.map((item) => (
|
||||
<Flex
|
||||
key={item.id}
|
||||
alignItems={'center'}
|
||||
p={5}
|
||||
cursor={'pointer'}
|
||||
_hover={{ bg: 'myWhite.600' }}
|
||||
borderRadius={'md'}
|
||||
draggable
|
||||
onDragEnd={(e) => {
|
||||
if (e.clientX < 360) return;
|
||||
onAddNode({
|
||||
template: item,
|
||||
position: { x: e.clientX, y: e.clientY }
|
||||
});
|
||||
}}
|
||||
onClick={(e) => {
|
||||
if (isPc) return;
|
||||
onClose();
|
||||
onAddNode({
|
||||
template: item,
|
||||
position: { x: e.clientX, y: e.clientY }
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Avatar src={item.logo} w={'34px'} objectFit={'contain'} borderRadius={'0'} />
|
||||
<Box ml={5} flex={'1 0 0'}>
|
||||
<Box color={'black'}>{item.name}</Box>
|
||||
<Box className="textEllipsis3" color={'myGray.500'} fontSize={'sm'}>
|
||||
{item.intro}
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,153 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
ModalFooter,
|
||||
ModalBody,
|
||||
Flex,
|
||||
Switch,
|
||||
Input,
|
||||
Textarea
|
||||
} from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MyModal from '@/components/MyModal';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import { FlowNodeValTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import MySelect from '@/components/Select';
|
||||
|
||||
const typeSelectList = [
|
||||
{
|
||||
label: '字符串',
|
||||
value: FlowNodeValTypeEnum.string
|
||||
},
|
||||
{
|
||||
label: '数字',
|
||||
value: FlowNodeValTypeEnum.number
|
||||
},
|
||||
{
|
||||
label: '布尔',
|
||||
value: FlowNodeValTypeEnum.boolean
|
||||
},
|
||||
{
|
||||
label: '历史记录',
|
||||
value: FlowNodeValTypeEnum.chatHistory
|
||||
},
|
||||
{
|
||||
label: '引用内容',
|
||||
value: FlowNodeValTypeEnum.datasetQuote
|
||||
},
|
||||
{
|
||||
label: '任意',
|
||||
value: FlowNodeValTypeEnum.any
|
||||
}
|
||||
];
|
||||
|
||||
export type EditFieldModeType = 'input' | 'output' | 'pluginInput';
|
||||
export type EditFieldType = {
|
||||
key: string;
|
||||
label?: string;
|
||||
valueType?: `${FlowNodeValTypeEnum}`;
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
};
|
||||
|
||||
const FieldEditModal = ({
|
||||
mode,
|
||||
defaultField = {
|
||||
label: '',
|
||||
key: '',
|
||||
description: '',
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
required: false
|
||||
},
|
||||
onClose,
|
||||
onSubmit
|
||||
}: {
|
||||
mode: EditFieldModeType;
|
||||
defaultField?: EditFieldType;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: EditFieldType) => void;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { register, getValues, setValue, handleSubmit } = useForm<EditFieldType>({
|
||||
defaultValues: defaultField
|
||||
});
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
|
||||
const title = ['input', 'pluginInput'].includes(mode)
|
||||
? t('app.Input Field Settings')
|
||||
: t('app.Output Field Settings');
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
isOpen={true}
|
||||
title={
|
||||
<Flex alignItems={'center'}>
|
||||
<Avatar src={'/imgs/module/extract.png'} mr={2} w={'20px'} objectFit={'cover'} />
|
||||
{title}
|
||||
</Flex>
|
||||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
<ModalBody minH={'260px'} overflow={'visible'}>
|
||||
{mode === 'input' && (
|
||||
<Flex alignItems={'center'} mb={5}>
|
||||
<Box flex={'0 0 70px'}>必填</Box>
|
||||
<Switch {...register('required')} />
|
||||
</Flex>
|
||||
)}
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段类型</Box>
|
||||
<MySelect
|
||||
w={'288px'}
|
||||
list={typeSelectList}
|
||||
value={getValues('valueType')}
|
||||
onchange={(e: string) => {
|
||||
const type = e as `${FlowNodeValTypeEnum}`;
|
||||
setValue('valueType', type);
|
||||
|
||||
if (
|
||||
type === FlowNodeValTypeEnum.chatHistory ||
|
||||
type === FlowNodeValTypeEnum.datasetQuote
|
||||
) {
|
||||
const label = typeSelectList.find((item) => item.value === type)?.label;
|
||||
setValue('label', label);
|
||||
}
|
||||
|
||||
setRefresh(!refresh);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段名</Box>
|
||||
<Input
|
||||
placeholder="预约字段/sql语句……"
|
||||
{...register('label', { required: '字段名不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex mb={5} alignItems={'center'}>
|
||||
<Box flex={'0 0 70px'}>字段 key</Box>
|
||||
<Input
|
||||
placeholder="appointment/sql"
|
||||
{...register('key', { required: '字段 key 不能为空' })}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex mb={5} alignItems={'flex-start'}>
|
||||
<Box flex={'0 0 70px'}>字段描述</Box>
|
||||
<Textarea placeholder="可选" rows={3} {...register('description')} />
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button onClick={handleSubmit(onSubmit)}>确认</Button>
|
||||
</ModalFooter>
|
||||
</MyModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(FieldEditModal);
|
||||
@@ -0,0 +1,196 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, Flex, useTheme, Menu, MenuButton, MenuList, MenuItem } from '@chakra-ui/react';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import type { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useEditTitle } from '@/web/common/hooks/useEditTitle';
|
||||
import { useToast } from '@/web/common/hooks/useToast';
|
||||
import { useFlowProviderStore, onChangeNode } from '../../FlowProvider';
|
||||
import {
|
||||
FlowNodeSpecialInputKeyEnum,
|
||||
FlowNodeTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import { getPluginModuleDetail } from '@/web/core/plugin/api';
|
||||
import { getErrText } from '@fastgpt/global/common/error/utils';
|
||||
import { useConfirm } from '@/web/common/hooks/useConfirm';
|
||||
|
||||
type Props = FlowModuleItemType & {
|
||||
children?: React.ReactNode | React.ReactNode[] | string;
|
||||
minW?: string | number;
|
||||
isPreview?: boolean;
|
||||
};
|
||||
|
||||
const NodeCard = (props: Props) => {
|
||||
const {
|
||||
children,
|
||||
logo = '/icon/logo.svg',
|
||||
name = '未知模块',
|
||||
description,
|
||||
minW = '300px',
|
||||
moduleId,
|
||||
flowType,
|
||||
inputs,
|
||||
isPreview
|
||||
} = props;
|
||||
const { onCopyNode, onResetNode, onDelNode } = useFlowProviderStore();
|
||||
const { t } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const { toast } = useToast();
|
||||
const { setLoading } = useSystemStore();
|
||||
|
||||
// custom title edit
|
||||
const { onOpenModal, EditModal: EditTitleModal } = useEditTitle({
|
||||
title: t('common.Custom Title'),
|
||||
placeholder: t('app.module.Custom Title Tip') || ''
|
||||
});
|
||||
const { openConfirm, ConfirmModal } = useConfirm({
|
||||
content: t('module.Confirm Sync Plugin')
|
||||
});
|
||||
|
||||
const menuList = useMemo(
|
||||
() => [
|
||||
...(flowType === FlowNodeTypeEnum.pluginModule
|
||||
? [
|
||||
{
|
||||
icon: 'common/refreshLight',
|
||||
label: t('plugin.Synchronous version'),
|
||||
onClick: () => {
|
||||
const pluginId = inputs.find(
|
||||
(item) => item.key === FlowNodeSpecialInputKeyEnum.pluginId
|
||||
)?.value;
|
||||
if (!pluginId) return;
|
||||
openConfirm(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const pluginModule = await getPluginModuleDetail(pluginId);
|
||||
onResetNode(moduleId, pluginModule);
|
||||
} catch (e) {
|
||||
return toast({
|
||||
status: 'error',
|
||||
title: getErrText(e, t('plugin.Get Plugin Module Detail Failed'))
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
})();
|
||||
}
|
||||
}
|
||||
]
|
||||
: [
|
||||
{
|
||||
icon: 'edit',
|
||||
label: t('common.Rename'),
|
||||
onClick: () =>
|
||||
onOpenModal({
|
||||
defaultVal: name,
|
||||
onSuccess: (e) => {
|
||||
if (!e) {
|
||||
return toast({
|
||||
title: t('app.modules.Title is required'),
|
||||
status: 'warning'
|
||||
});
|
||||
}
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'attr',
|
||||
key: 'name',
|
||||
value: e
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
]),
|
||||
{
|
||||
icon: 'copy',
|
||||
label: t('common.Copy'),
|
||||
onClick: () => onCopyNode(moduleId)
|
||||
},
|
||||
{
|
||||
icon: 'delete',
|
||||
label: t('common.Delete'),
|
||||
onClick: () => onDelNode(moduleId)
|
||||
},
|
||||
|
||||
{
|
||||
icon: 'back',
|
||||
label: t('common.Back'),
|
||||
onClick: () => {}
|
||||
}
|
||||
],
|
||||
[
|
||||
flowType,
|
||||
inputs,
|
||||
moduleId,
|
||||
name,
|
||||
onCopyNode,
|
||||
onDelNode,
|
||||
onOpenModal,
|
||||
onResetNode,
|
||||
openConfirm,
|
||||
setLoading,
|
||||
t,
|
||||
toast
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
minW={minW}
|
||||
maxW={'500px'}
|
||||
bg={'white'}
|
||||
border={theme.borders.md}
|
||||
borderRadius={'md'}
|
||||
boxShadow={'sm'}
|
||||
className={isPreview ? 'nodrag' : ''}
|
||||
>
|
||||
<Flex className="custom-drag-handle" px={4} py={3} alignItems={'center'}>
|
||||
<Avatar src={logo} borderRadius={'md'} objectFit={'contain'} w={'30px'} h={'30px'} />
|
||||
<Box ml={3} fontSize={'lg'} color={'myGray.600'}>
|
||||
{name}
|
||||
</Box>
|
||||
{description && (
|
||||
<MyTooltip label={description} forceShow>
|
||||
<QuestionOutlineIcon
|
||||
display={['none', 'inline']}
|
||||
transform={'translateY(1px)'}
|
||||
mb={'1px'}
|
||||
ml={1}
|
||||
/>
|
||||
</MyTooltip>
|
||||
)}
|
||||
<Box flex={1} />
|
||||
{!isPreview && (
|
||||
<Menu autoSelect={false} isLazy>
|
||||
<MenuButton
|
||||
className={'nodrag'}
|
||||
_hover={{ bg: 'myWhite.600' }}
|
||||
cursor={'pointer'}
|
||||
borderRadius={'md'}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<MyIcon name={'more'} w={'14px'} p={2} />
|
||||
</MenuButton>
|
||||
<MenuList color={'myGray.700'} minW={`120px !important`} zIndex={10}>
|
||||
{menuList.map((item) => (
|
||||
<MenuItem key={item.label} onClick={item.onClick} py={[2, 3]}>
|
||||
<MyIcon name={item.icon as any} w={['14px', '16px']} />
|
||||
<Box ml={[1, 2]}>{item.label}</Box>
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
)}
|
||||
</Flex>
|
||||
{children}
|
||||
<EditTitleModal />
|
||||
<ConfirmModal />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(NodeCard);
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { Box, Input, Button, Flex, Textarea } from '@chakra-ui/react';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
@@ -10,11 +10,15 @@ import type { ClassifyQuestionAgentItemType } from '@/types/app';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 4);
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { FlowOutputItemTypeEnum, FlowValueTypeEnum, SpecialInputKeyEnum } from '@/constants/flow';
|
||||
import {
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeValTypeEnum,
|
||||
FlowNodeSpecialInputKeyEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import SourceHandle from '../render/SourceHandle';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { onChangeNode } from '../Provider';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -28,7 +32,7 @@ const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
moduleId={moduleId}
|
||||
flowInputList={inputs}
|
||||
CustomComponent={{
|
||||
[SpecialInputKeyEnum.agents]: ({
|
||||
[FlowNodeSpecialInputKeyEnum.agents]: ({
|
||||
key: agentKey,
|
||||
value: agents = [],
|
||||
...props
|
||||
@@ -50,26 +54,20 @@ const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
color={'myGray.600'}
|
||||
_hover={{ color: 'red.600' }}
|
||||
onClick={() => {
|
||||
const newInputValue = agents.filter((input) => input.key !== item.key);
|
||||
const newOutputVal = outputs.filter(
|
||||
(output) => output.key !== item.key
|
||||
);
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: agentKey,
|
||||
value: {
|
||||
...props,
|
||||
key: agentKey,
|
||||
value: newInputValue
|
||||
value: agents.filter((input) => input.key !== item.key)
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: newOutputVal
|
||||
type: 'delOutput',
|
||||
key: item.key
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -92,7 +90,7 @@ const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
);
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: agentKey,
|
||||
value: {
|
||||
...props,
|
||||
@@ -102,36 +100,35 @@ const NodeCQNode = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<SourceHandle handleKey={item.key} valueType={FlowValueTypeEnum.boolean} />
|
||||
<SourceHandle handleKey={item.key} valueType={FlowNodeValTypeEnum.boolean} />
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
<Button
|
||||
onClick={() => {
|
||||
const key = nanoid();
|
||||
const newInputValue = agents.concat({ value: '', key });
|
||||
const newOutputValue = outputs.concat({
|
||||
key,
|
||||
label: '',
|
||||
type: FlowOutputItemTypeEnum.hidden,
|
||||
targets: []
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: agentKey,
|
||||
value: {
|
||||
...props,
|
||||
key: agentKey,
|
||||
value: newInputValue
|
||||
value: agents.concat({ value: '', key })
|
||||
}
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
type: 'updateOutput',
|
||||
key: agentKey,
|
||||
value: newOutputValue
|
||||
value: outputs.concat({
|
||||
key,
|
||||
label: '',
|
||||
type: FlowNodeOutputTypeEnum.hidden,
|
||||
targets: []
|
||||
})
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
|
||||
const NodeAnswer = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
return <NodeCard {...data}></NodeCard>;
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Button, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@chakra-ui/react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import Container from '../modules/Container';
|
||||
@@ -13,14 +13,17 @@ import RenderOutput from '../render/RenderOutput';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import ExtractFieldModal from '../modules/ExtractFieldModal';
|
||||
import { ContextExtractEnum } from '@/constants/flow/flowField';
|
||||
import { FlowOutputItemTypeEnum, FlowValueTypeEnum } from '@/constants/flow';
|
||||
import { useFlowStore, onChangeNode } from '../Provider';
|
||||
import {
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { useFlowProviderStore, onChangeNode } from '../../FlowProvider';
|
||||
|
||||
const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { inputs, outputs, moduleId } = data;
|
||||
const { t } = useTranslation();
|
||||
const [editExtractFiled, setEditExtractField] = useState<ContextExtractAgentItemType>();
|
||||
const { onDelEdge } = useFlowStore();
|
||||
const { onDelEdge } = useFlowProviderStore();
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} {...data}>
|
||||
@@ -88,29 +91,21 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
w={'16px'}
|
||||
cursor={'pointer'}
|
||||
onClick={() => {
|
||||
const newInputValue = extractKeys.filter(
|
||||
(extract) => item.key !== extract.key
|
||||
);
|
||||
const newOutputVal = outputs.filter(
|
||||
(output) => output.key !== item.key
|
||||
);
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: ContextExtractEnum.extractKeys,
|
||||
value: {
|
||||
...props,
|
||||
value: newInputValue
|
||||
value: extractKeys.filter((extract) => item.key !== extract.key)
|
||||
}
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: newOutputVal
|
||||
type: 'delOutput',
|
||||
key: item.key
|
||||
});
|
||||
onDelEdge({ moduleId, sourceHandle: item.key });
|
||||
}}
|
||||
/>
|
||||
</Td>
|
||||
@@ -145,7 +140,7 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: ContextExtractEnum.extractKeys,
|
||||
value: {
|
||||
...inputs.find((input) => input.key === ContextExtractEnum.extractKeys),
|
||||
@@ -153,62 +148,42 @@ const NodeExtract = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
}
|
||||
});
|
||||
|
||||
if (!exists) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: outputs.concat({
|
||||
key: data.key,
|
||||
label: `提取结果-${data.desc}`,
|
||||
description: '无法提取时不会返回',
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
targets: []
|
||||
})
|
||||
});
|
||||
} else {
|
||||
const newOutput = {
|
||||
key: data.key,
|
||||
label: `提取结果-${data.desc}`,
|
||||
description: '无法提取时不会返回',
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
};
|
||||
|
||||
if (exists) {
|
||||
if (editExtractFiled.key === data.key) {
|
||||
const output = outputs.find((output) => output.key === data.key);
|
||||
// update
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: outputs.map((output) =>
|
||||
output.key === data.key
|
||||
? {
|
||||
...output,
|
||||
label: `提取结果-${data.desc}`
|
||||
}
|
||||
: output
|
||||
)
|
||||
type: 'updateOutput',
|
||||
key: data.key,
|
||||
value: {
|
||||
...output,
|
||||
label: `提取结果-${data.desc}`
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// del and push
|
||||
const newOutputs = outputs.filter((output) => output.key !== editExtractFiled.key);
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: newOutputs
|
||||
type: 'replaceOutput',
|
||||
key: editExtractFiled.key,
|
||||
value: newOutput
|
||||
});
|
||||
setTimeout(() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: newOutputs.concat({
|
||||
key: data.key,
|
||||
label: `提取结果-${data.desc}`,
|
||||
description: '无法提取时不会返回',
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
targets: []
|
||||
})
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: newOutput
|
||||
});
|
||||
}
|
||||
|
||||
setEditExtractField(undefined);
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
@@ -9,10 +9,14 @@ import { Box, Button } from '@chakra-ui/react';
|
||||
import { SmallAddIcon } from '@chakra-ui/icons';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
|
||||
import { FlowInputItemTypeEnum, FlowOutputItemTypeEnum, FlowValueTypeEnum } from '@/constants/flow';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import { onChangeNode } from '../Provider';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
const NodeHttp = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
@@ -33,8 +37,8 @@ const NodeHttp = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
key,
|
||||
value: {
|
||||
key,
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: `入参${inputs.length - 1}`,
|
||||
edit: true
|
||||
}
|
||||
@@ -52,21 +56,17 @@ const NodeHttp = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
variant={'base'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
const key = nanoid();
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key,
|
||||
value: [
|
||||
{
|
||||
key,
|
||||
label: `出参${outputs.length}`,
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
].concat(outputs as any)
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key: nanoid(),
|
||||
label: `出参${outputs.length}`,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -0,0 +1,191 @@
|
||||
import React, { useState } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import Container from '../modules/Container';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import SourceHandle from '../render/SourceHandle';
|
||||
import { EditFieldType } from '../modules/FieldEditModal';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
|
||||
const NodeInput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
|
||||
return (
|
||||
<NodeCard minW={'300px'} {...data}>
|
||||
<Container mt={1} borderTop={'2px solid'} borderTopColor={'myGray.300'}>
|
||||
{inputs.map((item) => (
|
||||
<Flex
|
||||
key={item.key}
|
||||
className="nodrag"
|
||||
cursor={'default'}
|
||||
justifyContent={'right'}
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
mb={4}
|
||||
>
|
||||
<MyIcon
|
||||
name={'settingLight'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
mr={3}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
valueType: item.valueType,
|
||||
description: item.description,
|
||||
required: item.required
|
||||
})
|
||||
}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
mr={3}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delInput',
|
||||
key: item.key
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delOutput',
|
||||
key: item.key
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
{item.description && (
|
||||
<MyTooltip label={item.description} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
<Box position={'relative'}>
|
||||
{item.label}
|
||||
{item.required && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
right={'-6px'}
|
||||
top={'-3px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<SourceHandle handleKey={item.key} valueType={item.valueType} />
|
||||
</Flex>
|
||||
))}
|
||||
<Box textAlign={'right'} mt={5}>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
const key = nanoid();
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
value: {
|
||||
key,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: `入参${inputs.length + 1}`,
|
||||
edit: true,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key,
|
||||
label: `入参${inputs.length + 1}`,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
添加入参
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
mode={'pluginInput'}
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(e) => {
|
||||
const memInput = inputs.find((item) => item.key === editField.key);
|
||||
const memOutput = outputs.find((item) => item.key === editField.key);
|
||||
|
||||
if (!memInput || !memOutput) return setEditField(undefined);
|
||||
const input = {
|
||||
...memInput,
|
||||
...e
|
||||
};
|
||||
const output = {
|
||||
...memOutput,
|
||||
...e
|
||||
};
|
||||
// not update key
|
||||
if (editField.key === e.key) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: editField.key,
|
||||
value: input
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: editField.key,
|
||||
value: output
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: input
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: output
|
||||
});
|
||||
}
|
||||
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeInput);
|
||||
@@ -0,0 +1,191 @@
|
||||
import React, { useState } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import Container from '../modules/Container';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { EditFieldType } from '../modules/FieldEditModal';
|
||||
import TargetHandle from '../render/TargetHandle';
|
||||
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
|
||||
const NodeOutput = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
|
||||
return (
|
||||
<NodeCard minW={'300px'} {...data}>
|
||||
<Container mt={1} borderTop={'2px solid'} borderTopColor={'myGray.300'}>
|
||||
{inputs.map((item) => (
|
||||
<Flex
|
||||
key={item.key}
|
||||
className="nodrag"
|
||||
cursor={'default'}
|
||||
justifyContent={'left'}
|
||||
alignItems={'center'}
|
||||
position={'relative'}
|
||||
mb={4}
|
||||
>
|
||||
<TargetHandle handleKey={item.key} valueType={item.valueType} />
|
||||
<Box position={'relative'}>
|
||||
{item.label}
|
||||
{item.required && (
|
||||
<Box
|
||||
position={'absolute'}
|
||||
right={'-6px'}
|
||||
top={'-3px'}
|
||||
color={'red.500'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
*
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<MyIcon
|
||||
name={'settingLight'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={3}
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
valueType: item.valueType,
|
||||
description: item.description
|
||||
})
|
||||
}
|
||||
/>
|
||||
<MyIcon
|
||||
className="delete"
|
||||
name={'delete'}
|
||||
w={'14px'}
|
||||
cursor={'pointer'}
|
||||
ml={3}
|
||||
_hover={{ color: 'red.500' }}
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delInput',
|
||||
key: item.key,
|
||||
value: ''
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delOutput',
|
||||
key: item.key
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
{item.description && (
|
||||
<MyTooltip label={item.description} forceShow>
|
||||
<QuestionOutlineIcon display={['none', 'inline']} mr={1} />
|
||||
</MyTooltip>
|
||||
)}
|
||||
</Flex>
|
||||
))}
|
||||
<Box textAlign={'left'} mt={5}>
|
||||
<Button
|
||||
variant={'base'}
|
||||
leftIcon={<SmallAddIcon />}
|
||||
onClick={() => {
|
||||
const key = nanoid();
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
value: {
|
||||
key,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: `入参${inputs.length + 1}`,
|
||||
edit: true,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addOutput',
|
||||
value: {
|
||||
key,
|
||||
label: `入参${inputs.length + 1}`,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
edit: true,
|
||||
targets: []
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
添加入参
|
||||
</Button>
|
||||
</Box>
|
||||
</Container>
|
||||
{!!editField && (
|
||||
<FieldEditModal
|
||||
mode={'output'}
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(e) => {
|
||||
const memInput = inputs.find((item) => item.key === editField.key);
|
||||
const memOutput = outputs.find((item) => item.key === editField.key);
|
||||
if (!memInput || !memOutput) return;
|
||||
const input = {
|
||||
...memInput,
|
||||
...e
|
||||
};
|
||||
const output = {
|
||||
...memOutput,
|
||||
...e
|
||||
};
|
||||
// not update key
|
||||
if (editField.key === e.key) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateInput',
|
||||
key: editField.key,
|
||||
value: input
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'updateOutput',
|
||||
key: editField.key,
|
||||
value: output
|
||||
});
|
||||
} else {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: input
|
||||
});
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: output
|
||||
});
|
||||
}
|
||||
|
||||
setEditField(undefined);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeOutput);
|
||||
@@ -1,17 +1,17 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
|
||||
const NodeDatasetSearch = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const NodeSimple = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} {...data}>
|
||||
<NodeCard minW={'300px'} isPreview {...data}>
|
||||
<Divider text="Input" />
|
||||
<Container>
|
||||
<RenderInput moduleId={moduleId} flowInputList={inputs} />
|
||||
@@ -23,4 +23,4 @@ const NodeDatasetSearch = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeDatasetSearch);
|
||||
export default React.memo(NodeSimple);
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Container from '../modules/Container';
|
||||
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
|
||||
const NodeAPP = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const NodeRunAPP = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
|
||||
return (
|
||||
@@ -22,4 +22,4 @@ const NodeAPP = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeAPP);
|
||||
export default React.memo(NodeRunAPP);
|
||||
@@ -1,17 +1,17 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
|
||||
const NodeChat = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const NodeSimple = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { moduleId, inputs, outputs } = data;
|
||||
|
||||
return (
|
||||
<NodeCard minW={'400px'} {...data}>
|
||||
<NodeCard minW={'300px'} {...data}>
|
||||
<Divider text="Input" />
|
||||
<Container>
|
||||
<RenderInput moduleId={moduleId} flowInputList={inputs} />
|
||||
@@ -23,4 +23,4 @@ const NodeChat = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeChat);
|
||||
export default React.memo(NodeSimple);
|
||||
@@ -15,10 +15,10 @@ import {
|
||||
Switch
|
||||
} from '@chakra-ui/react';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { SystemInputEnum } from '@/constants/app';
|
||||
import { welcomeTextTip, variableTip, questionGuideTip } from '@/constants/flow/ModuleTemplate';
|
||||
import { onChangeNode } from '../Provider';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
import VariableEditModal, { addVariable } from '../../../VariableEditModal';
|
||||
import MyIcon from '@/components/Icon';
|
||||
@@ -76,7 +76,7 @@ export function WelcomeText({ data }: { data: FlowModuleItemType }) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: SystemInputEnum.welcomeText,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...welcomeText,
|
||||
value: e.target.value
|
||||
@@ -106,14 +106,14 @@ function ChatStartVariable({ data }: { data: FlowModuleItemType }) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: SystemInputEnum.variables,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...inputs.find((item) => item.key === SystemInputEnum.variables),
|
||||
value
|
||||
}
|
||||
});
|
||||
},
|
||||
[inputs, onChangeNode, moduleId]
|
||||
[inputs, moduleId]
|
||||
);
|
||||
|
||||
const onclickSubmit = useCallback(
|
||||
@@ -230,7 +230,7 @@ function QuestionGuide({ data }: { data: FlowModuleItemType }) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: SystemInputEnum.questionGuide,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...inputs.find((item) => item.key === SystemInputEnum.questionGuide),
|
||||
value
|
||||
@@ -4,7 +4,7 @@ import { NodeProps } from 'reactflow';
|
||||
import { Box, Button, Table, Thead, Tbody, Tr, Th, Td, TableContainer } from '@chakra-ui/react';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import { FlowModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import Container from '../modules/Container';
|
||||
import { SystemInputEnum, VariableInputEnum } from '@/constants/app';
|
||||
import type { VariableItemType } from '@/types/app';
|
||||
@@ -12,7 +12,7 @@ import MyIcon from '@/components/Icon';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
|
||||
import VariableEditModal, { addVariable } from '../../../VariableEditModal';
|
||||
import { onChangeNode } from '../Provider';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
|
||||
export const defaultVariable: VariableItemType = {
|
||||
id: nanoid(),
|
||||
@@ -41,14 +41,14 @@ const NodeUserGuide = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: SystemInputEnum.variables,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...inputs.find((item) => item.key === SystemInputEnum.variables),
|
||||
value
|
||||
}
|
||||
});
|
||||
},
|
||||
[inputs, onChangeNode, moduleId]
|
||||
[inputs, moduleId]
|
||||
);
|
||||
|
||||
const onclickSubmit = useCallback(
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import type { FlowInputItemType, SelectAppItemType } from '@/types/core/app/flow';
|
||||
import type { SelectAppItemType } from '@fastgpt/global/core/module/type';
|
||||
import type { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import {
|
||||
Box,
|
||||
Textarea,
|
||||
@@ -15,10 +16,10 @@ import {
|
||||
useTheme,
|
||||
Grid
|
||||
} from '@chakra-ui/react';
|
||||
import { FlowInputItemTypeEnum } from '@/constants/flow';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { onChangeNode, useFlowStore } from '../Provider';
|
||||
import { onChangeNode, useFlowProviderStore } from '../../FlowProvider';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MySelect from '@/components/Select';
|
||||
import MySlider from '@/components/Slider';
|
||||
@@ -33,22 +34,25 @@ import { useDatasetStore } from '@/web/core/dataset/store/dataset';
|
||||
import { SelectedDatasetType } from '@/types/core/dataset';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { LLMModelItemType } from '@/types/model';
|
||||
import type { EditFieldModeType, EditFieldType } from '../modules/FieldEditModal';
|
||||
|
||||
const SetInputFieldModal = dynamic(() => import('../modules/SetInputFieldModal'));
|
||||
const SelectAppModal = dynamic(() => import('../../../SelectAppModal'));
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
const SelectAppModal = dynamic(() => import('../../SelectAppModal'));
|
||||
const AIChatSettingsModal = dynamic(() => import('../../../AIChatSettingsModal'));
|
||||
const DatasetSelectModal = dynamic(() => import('../../../DatasetSelectModal'));
|
||||
|
||||
export const Label = React.memo(function Label({
|
||||
moduleId,
|
||||
inputKey,
|
||||
editFiledType = 'input',
|
||||
...item
|
||||
}: FlowInputItemType & {
|
||||
}: FlowNodeInputItemType & {
|
||||
moduleId: string;
|
||||
inputKey: string;
|
||||
editFiledType?: EditFieldModeType;
|
||||
}) {
|
||||
const { required = false, description, edit, label, type, valueType } = item;
|
||||
const [editField, setEditField] = useState<FlowInputItemType>();
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
|
||||
return (
|
||||
<Flex className="nodrag" cursor={'default'} alignItems={'center'} position={'relative'}>
|
||||
@@ -72,7 +76,7 @@ export const Label = React.memo(function Label({
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{(type === FlowInputItemTypeEnum.target || valueType) && (
|
||||
{(type === FlowNodeInputTypeEnum.target || valueType) && (
|
||||
<TargetHandle handleKey={inputKey} valueType={valueType} />
|
||||
)}
|
||||
|
||||
@@ -86,8 +90,11 @@ export const Label = React.memo(function Label({
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
...item,
|
||||
key: inputKey
|
||||
label: item.label,
|
||||
valueType: item.valueType,
|
||||
required: item.required,
|
||||
key: inputKey,
|
||||
description: item.description
|
||||
})
|
||||
}
|
||||
/>
|
||||
@@ -110,34 +117,31 @@ export const Label = React.memo(function Label({
|
||||
</>
|
||||
)}
|
||||
{!!editField && (
|
||||
<SetInputFieldModal
|
||||
<FieldEditModal
|
||||
mode={editFiledType}
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(data) => {
|
||||
onSubmit={(e) => {
|
||||
const data = {
|
||||
...item,
|
||||
...e
|
||||
};
|
||||
// same key
|
||||
if (editField.key === data.key) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
key: inputKey,
|
||||
type: 'updateInput',
|
||||
key: data.key,
|
||||
value: data
|
||||
});
|
||||
} else {
|
||||
// diff key. del and add
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'addInput',
|
||||
key: data.key,
|
||||
type: 'replaceInput',
|
||||
key: editField.key,
|
||||
value: data
|
||||
});
|
||||
setTimeout(() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'delInput',
|
||||
key: editField.key,
|
||||
value: ''
|
||||
});
|
||||
});
|
||||
}
|
||||
setEditField(undefined);
|
||||
}}
|
||||
@@ -150,55 +154,64 @@ export const Label = React.memo(function Label({
|
||||
const RenderInput = ({
|
||||
flowInputList,
|
||||
moduleId,
|
||||
CustomComponent = {}
|
||||
CustomComponent = {},
|
||||
editFiledType
|
||||
}: {
|
||||
flowInputList: FlowInputItemType[];
|
||||
flowInputList: FlowNodeInputItemType[];
|
||||
moduleId: string;
|
||||
CustomComponent?: Record<string, (e: FlowInputItemType) => React.ReactNode>;
|
||||
CustomComponent?: Record<string, (e: FlowNodeInputItemType) => React.ReactNode>;
|
||||
editFiledType?: EditFieldModeType;
|
||||
}) => {
|
||||
const sortInputs = useMemo(
|
||||
() => flowInputList.sort((a, b) => (a.key === FlowInputItemTypeEnum.switch ? -1 : 1)),
|
||||
() => flowInputList.sort((a, b) => (a.key === FlowNodeInputTypeEnum.switch ? -1 : 1)),
|
||||
[flowInputList]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{sortInputs.map(
|
||||
(item) =>
|
||||
item.type !== FlowInputItemTypeEnum.hidden && (
|
||||
item.type !== FlowNodeInputTypeEnum.hidden && (
|
||||
<Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}>
|
||||
{!!item.label && <Label moduleId={moduleId} inputKey={item.key} {...item} />}
|
||||
{!!item.label && (
|
||||
<Label
|
||||
editFiledType={editFiledType}
|
||||
moduleId={moduleId}
|
||||
inputKey={item.key}
|
||||
{...item}
|
||||
/>
|
||||
)}
|
||||
<Box mt={2} className={'nodrag'}>
|
||||
{item.type === FlowInputItemTypeEnum.numberInput && (
|
||||
{item.type === FlowNodeInputTypeEnum.numberInput && (
|
||||
<NumberInputRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.input && (
|
||||
{item.type === FlowNodeInputTypeEnum.input && (
|
||||
<TextInputRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.textarea && (
|
||||
{item.type === FlowNodeInputTypeEnum.textarea && (
|
||||
<TextareaRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.select && (
|
||||
{item.type === FlowNodeInputTypeEnum.select && (
|
||||
<SelectRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.slider && (
|
||||
{item.type === FlowNodeInputTypeEnum.slider && (
|
||||
<SliderRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.selectApp && (
|
||||
{item.type === FlowNodeInputTypeEnum.selectApp && (
|
||||
<SelectAppRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.aiSettings && (
|
||||
{item.type === FlowNodeInputTypeEnum.aiSettings && (
|
||||
<AISetting inputs={sortInputs} item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.maxToken && (
|
||||
{item.type === FlowNodeInputTypeEnum.maxToken && (
|
||||
<MaxTokenRender inputs={sortInputs} item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.selectChatModel && (
|
||||
{item.type === FlowNodeInputTypeEnum.selectChatModel && (
|
||||
<SelectChatModelRender inputs={sortInputs} item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.selectDataset && (
|
||||
{item.type === FlowNodeInputTypeEnum.selectDataset && (
|
||||
<SelectDatasetRender item={item} moduleId={moduleId} />
|
||||
)}
|
||||
{item.type === FlowInputItemTypeEnum.custom && CustomComponent[item.key] && (
|
||||
{item.type === FlowNodeInputTypeEnum.custom && CustomComponent[item.key] && (
|
||||
<>{CustomComponent[item.key]({ ...item })}</>
|
||||
)}
|
||||
</Box>
|
||||
@@ -212,8 +225,8 @@ const RenderInput = ({
|
||||
export default React.memo(RenderInput);
|
||||
|
||||
type RenderProps = {
|
||||
inputs?: FlowInputItemType[];
|
||||
item: FlowInputItemType;
|
||||
inputs?: FlowNodeInputItemType[];
|
||||
item: FlowNodeInputItemType;
|
||||
moduleId: string;
|
||||
};
|
||||
|
||||
@@ -226,7 +239,7 @@ var NumberInputRender = React.memo(function NumberInputRender({ item, moduleId }
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
@@ -252,7 +265,7 @@ var TextInputRender = React.memo(function TextInputRender({ item, moduleId }: Re
|
||||
onBlur={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
@@ -274,7 +287,7 @@ var TextareaRender = React.memo(function TextareaRender({ item, moduleId }: Rend
|
||||
onBlur={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
@@ -295,7 +308,7 @@ var SelectRender = React.memo(function SelectRender({ item, moduleId }: RenderPr
|
||||
onchange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
@@ -320,7 +333,7 @@ var SliderRender = React.memo(function SliderRender({ item, moduleId }: RenderPr
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
@@ -368,7 +381,7 @@ var AISetting = React.memo(function AISetting({ inputs = [], moduleId }: RenderP
|
||||
if (!item) continue;
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key,
|
||||
value: {
|
||||
...item,
|
||||
@@ -411,7 +424,7 @@ var MaxTokenRender = React.memo(function MaxTokenRender({
|
||||
onChange={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
@@ -435,7 +448,7 @@ var SelectChatModelRender = React.memo(function SelectChatModelRender({
|
||||
{
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: item.key,
|
||||
value: {
|
||||
...item,
|
||||
@@ -449,7 +462,7 @@ var SelectChatModelRender = React.memo(function SelectChatModelRender({
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: 'maxToken',
|
||||
value: {
|
||||
...inputs.find((input) => input.key === 'maxToken'),
|
||||
@@ -477,7 +490,15 @@ var SelectChatModelRender = React.memo(function SelectChatModelRender({
|
||||
onChangeModel(list[0].value);
|
||||
}
|
||||
|
||||
return <MySelect width={'100%'} value={item.value} list={list} onchange={onChangeModel} />;
|
||||
return (
|
||||
<MySelect
|
||||
minW={'350px'}
|
||||
width={'100%'}
|
||||
value={item.value}
|
||||
list={list}
|
||||
onchange={onChangeModel}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
var SelectDatasetRender = React.memo(function SelectDatasetRender({ item, moduleId }: RenderProps) {
|
||||
@@ -498,7 +519,7 @@ var SelectDatasetRender = React.memo(function SelectDatasetRender({ item, module
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid gridTemplateColumns={'repeat(2, minmax(0, 1fr))'} gridGap={4} w={'100%'}>
|
||||
<Grid gridTemplateColumns={'repeat(2, minmax(0, 1fr))'} gridGap={4} minW={'350px'} w={'100%'}>
|
||||
<Button h={'36px'} onClick={onOpenKbSelect}>
|
||||
选择知识库
|
||||
</Button>
|
||||
@@ -532,7 +553,7 @@ var SelectDatasetRender = React.memo(function SelectDatasetRender({ item, module
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
key: item.key,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
value: {
|
||||
...item,
|
||||
value: e
|
||||
@@ -546,7 +567,7 @@ var SelectDatasetRender = React.memo(function SelectDatasetRender({ item, module
|
||||
});
|
||||
|
||||
var SelectAppRender = React.memo(function SelectAppRender({ item, moduleId }: RenderProps) {
|
||||
const { appId } = useFlowStore();
|
||||
const { filterAppIds } = useFlowProviderStore();
|
||||
const theme = useTheme();
|
||||
|
||||
const {
|
||||
@@ -577,12 +598,12 @@ var SelectAppRender = React.memo(function SelectAppRender({ item, moduleId }: Re
|
||||
{isOpenSelectApp && (
|
||||
<SelectAppModal
|
||||
defaultApps={item.value?.id ? [item.value.id] : []}
|
||||
filterApps={[appId]}
|
||||
filterAppIds={filterAppIds}
|
||||
onClose={onCloseSelectApp}
|
||||
onSuccess={(e) => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'inputs',
|
||||
type: 'updateInput',
|
||||
key: 'app',
|
||||
value: {
|
||||
...item,
|
||||
@@ -1,28 +1,32 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import type { FlowOutputItemType } from '@/types/core/app/flow';
|
||||
import type { FlowNodeOutputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { FlowOutputItemTypeEnum } from '@/constants/flow';
|
||||
import { FlowNodeOutputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { QuestionOutlineIcon } from '@chakra-ui/icons';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import SourceHandle from './SourceHandle';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import dynamic from 'next/dynamic';
|
||||
const SetOutputFieldModal = dynamic(() => import('../modules/SetOutputFieldModal'));
|
||||
import { onChangeNode } from '../Provider';
|
||||
import { onChangeNode } from '../../FlowProvider';
|
||||
import { SystemOutputEnum } from '@/constants/app';
|
||||
|
||||
const Label = ({
|
||||
import type { EditFieldType, EditFieldModeType } from '../modules/FieldEditModal';
|
||||
const FieldEditModal = dynamic(() => import('../modules/FieldEditModal'));
|
||||
|
||||
export const Label = ({
|
||||
moduleId,
|
||||
outputKey,
|
||||
outputs,
|
||||
editFiledType = 'output',
|
||||
...item
|
||||
}: FlowOutputItemType & {
|
||||
}: FlowNodeOutputItemType & {
|
||||
outputKey: string;
|
||||
moduleId: string;
|
||||
outputs: FlowOutputItemType[];
|
||||
outputs: FlowNodeOutputItemType[];
|
||||
editFiledType?: EditFieldModeType;
|
||||
}) => {
|
||||
const { label, description, edit } = item;
|
||||
const [editField, setEditField] = useState<FlowOutputItemType>();
|
||||
const [editField, setEditField] = useState<EditFieldType>();
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -42,8 +46,10 @@ const Label = ({
|
||||
_hover={{ color: 'myBlue.600' }}
|
||||
onClick={() =>
|
||||
setEditField({
|
||||
...item,
|
||||
key: outputKey
|
||||
label: item.label,
|
||||
valueType: item.valueType,
|
||||
key: outputKey,
|
||||
description: item.description
|
||||
})
|
||||
}
|
||||
/>
|
||||
@@ -57,9 +63,8 @@ const Label = ({
|
||||
onClick={() => {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: outputs.filter((output) => output.key !== outputKey)
|
||||
type: 'delOutput',
|
||||
key: outputKey
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -73,43 +78,29 @@ const Label = ({
|
||||
<Box>{label}</Box>
|
||||
|
||||
{!!editField && (
|
||||
<SetOutputFieldModal
|
||||
<FieldEditModal
|
||||
mode={editFiledType}
|
||||
defaultField={editField}
|
||||
onClose={() => setEditField(undefined)}
|
||||
onSubmit={(data) => {
|
||||
onSubmit={(e) => {
|
||||
const data = {
|
||||
...item,
|
||||
...e
|
||||
};
|
||||
if (editField.key === data.key) {
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: outputs.map((output) => (output.key === outputKey ? data : output))
|
||||
type: 'updateOutput',
|
||||
key: data.key,
|
||||
value: data
|
||||
});
|
||||
} else {
|
||||
let index = 0;
|
||||
const storeOutputs = outputs.filter((output, i) => {
|
||||
if (output.key !== editField.key) {
|
||||
return true;
|
||||
}
|
||||
index = i;
|
||||
return false;
|
||||
});
|
||||
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: storeOutputs
|
||||
type: 'replaceOutput',
|
||||
key: editField.key,
|
||||
value: data
|
||||
});
|
||||
setTimeout(() => {
|
||||
storeOutputs.splice(index, 0, data);
|
||||
console.log(index, storeOutputs);
|
||||
onChangeNode({
|
||||
moduleId,
|
||||
type: 'outputs',
|
||||
key: '',
|
||||
value: [...storeOutputs]
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
|
||||
setEditField(undefined);
|
||||
@@ -122,10 +113,12 @@ const Label = ({
|
||||
|
||||
const RenderOutput = ({
|
||||
moduleId,
|
||||
flowOutputList
|
||||
flowOutputList,
|
||||
editFiledType
|
||||
}: {
|
||||
moduleId: string;
|
||||
flowOutputList: FlowOutputItemType[];
|
||||
flowOutputList: FlowNodeOutputItemType[];
|
||||
editFiledType?: EditFieldModeType;
|
||||
}) => {
|
||||
const sortOutput = useMemo(
|
||||
() =>
|
||||
@@ -141,11 +134,17 @@ const RenderOutput = ({
|
||||
<>
|
||||
{sortOutput.map(
|
||||
(item) =>
|
||||
item.type !== FlowOutputItemTypeEnum.hidden && (
|
||||
item.type !== FlowNodeOutputTypeEnum.hidden && (
|
||||
<Box key={item.key} _notLast={{ mb: 7 }} position={'relative'}>
|
||||
<Label moduleId={moduleId} outputKey={item.key} outputs={sortOutput} {...item} />
|
||||
<Box mt={FlowOutputItemTypeEnum.answer ? 0 : 2} className={'nodrag'}>
|
||||
{item.type === FlowOutputItemTypeEnum.source && (
|
||||
<Label
|
||||
editFiledType={editFiledType}
|
||||
moduleId={moduleId}
|
||||
outputKey={item.key}
|
||||
outputs={sortOutput}
|
||||
{...item}
|
||||
/>
|
||||
<Box mt={FlowNodeOutputTypeEnum.answer ? 0 : 2} className={'nodrag'}>
|
||||
{item.type === FlowNodeOutputTypeEnum.source && (
|
||||
<SourceHandle handleKey={item.key} valueType={item.valueType} />
|
||||
)}
|
||||
</Box>
|
||||
@@ -1,20 +1,26 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useTransition } from 'react';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import { FlowValueTypeEnum, FlowValueTypeStyle } from '@/constants/flow';
|
||||
import { FlowValueTypeStyle, FlowValueTypeTip } from '@/constants/flow';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { FlowNodeValTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
interface Props extends BoxProps {
|
||||
handleKey: string;
|
||||
valueType?: `${FlowValueTypeEnum}`;
|
||||
valueType?: `${FlowNodeValTypeEnum}`;
|
||||
}
|
||||
|
||||
const SourceHandle = ({ handleKey, valueType, ...props }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const valType = valueType ?? FlowNodeValTypeEnum.any;
|
||||
|
||||
const valueStyle = useMemo(
|
||||
() =>
|
||||
valueType
|
||||
? FlowValueTypeStyle[valueType]
|
||||
: (FlowValueTypeStyle[FlowValueTypeEnum.any] as any),
|
||||
: (FlowValueTypeStyle[FlowNodeValTypeEnum.any] as any),
|
||||
[valueType]
|
||||
);
|
||||
|
||||
@@ -26,7 +32,12 @@ const SourceHandle = ({ handleKey, valueType, ...props }: Props) => {
|
||||
transform={'translate(50%,-50%)'}
|
||||
{...props}
|
||||
>
|
||||
<MyTooltip label={`${valueType}类型`}>
|
||||
<MyTooltip
|
||||
label={t('app.module.type', {
|
||||
type: t(FlowValueTypeTip[valType].label),
|
||||
example: FlowValueTypeTip[valType].example
|
||||
})}
|
||||
>
|
||||
<Handle
|
||||
style={{
|
||||
width: '12px',
|
||||
@@ -1,21 +1,26 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import { Handle, OnConnect, Position } from 'reactflow';
|
||||
import { FlowValueTypeEnum, FlowValueTypeStyle } from '@/constants/flow';
|
||||
import { FlowValueTypeStyle, FlowValueTypeTip } from '@/constants/flow';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { FlowNodeValTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
interface Props extends BoxProps {
|
||||
handleKey: string;
|
||||
valueType?: `${FlowValueTypeEnum}`;
|
||||
valueType?: `${FlowNodeValTypeEnum}`;
|
||||
onConnect?: OnConnect;
|
||||
}
|
||||
|
||||
const TargetHandle = ({ handleKey, valueType, onConnect, ...props }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const valType = valueType ?? FlowNodeValTypeEnum.any;
|
||||
const valueStyle = useMemo(
|
||||
() =>
|
||||
valueType
|
||||
? FlowValueTypeStyle[valueType]
|
||||
: (FlowValueTypeStyle[FlowValueTypeEnum.any] as any),
|
||||
: (FlowValueTypeStyle[FlowNodeValTypeEnum.any] as any),
|
||||
[valueType]
|
||||
);
|
||||
|
||||
@@ -28,7 +33,12 @@ const TargetHandle = ({ handleKey, valueType, onConnect, ...props }: Props) => {
|
||||
transform={'translate(50%,-50%)'}
|
||||
{...props}
|
||||
>
|
||||
<MyTooltip label={`${valueType}类型`}>
|
||||
<MyTooltip
|
||||
label={t('app.module.type', {
|
||||
type: t(FlowValueTypeTip[valType].label),
|
||||
example: FlowValueTypeTip[valType].example
|
||||
})}
|
||||
>
|
||||
<Handle
|
||||
style={{
|
||||
width: '12px',
|
||||
141
projects/app/src/components/core/module/Flow/index.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import ReactFlow, { Background, Controls, ReactFlowProvider } from 'reactflow';
|
||||
import { Box, Flex, IconButton, useDisclosure } from '@chakra-ui/react';
|
||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||
import { edgeOptions, connectionLineStyle } from '@/constants/flow';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import ButtonEdge from './components/modules/ButtonEdge';
|
||||
import TemplateList, { type ModuleTemplateProps } from './TemplateList';
|
||||
import { useFlowProviderStore } from './FlowProvider';
|
||||
|
||||
import 'reactflow/dist/style.css';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
|
||||
const NodeSimple = dynamic(() => import('./components/nodes/NodeSimple'));
|
||||
const nodeTypes = {
|
||||
[FlowNodeTypeEnum.userGuide]: dynamic(() => import('./components/nodes/NodeUserGuide')),
|
||||
[FlowNodeTypeEnum.variable]: dynamic(() => import('./components/nodes/NodeVariable')),
|
||||
[FlowNodeTypeEnum.questionInput]: dynamic(() => import('./components/nodes/NodeQuestionInput')),
|
||||
[FlowNodeTypeEnum.historyNode]: NodeSimple,
|
||||
[FlowNodeTypeEnum.chatNode]: NodeSimple,
|
||||
[FlowNodeTypeEnum.datasetSearchNode]: NodeSimple,
|
||||
[FlowNodeTypeEnum.answerNode]: dynamic(() => import('./components/nodes/NodeAnswer')),
|
||||
[FlowNodeTypeEnum.classifyQuestion]: dynamic(() => import('./components/nodes/NodeCQNode')),
|
||||
[FlowNodeTypeEnum.contentExtract]: dynamic(() => import('./components/nodes/NodeExtract')),
|
||||
[FlowNodeTypeEnum.httpRequest]: dynamic(() => import('./components/nodes/NodeHttp')),
|
||||
[FlowNodeTypeEnum.runApp]: NodeSimple,
|
||||
[FlowNodeTypeEnum.pluginInput]: dynamic(() => import('./components/nodes/NodeInput')),
|
||||
[FlowNodeTypeEnum.pluginOutput]: dynamic(() => import('./components/nodes/NodeOutput')),
|
||||
[FlowNodeTypeEnum.pluginModule]: NodeSimple
|
||||
};
|
||||
const edgeTypes = {
|
||||
buttonedge: ButtonEdge
|
||||
};
|
||||
type Props = {
|
||||
modules: ModuleItemType[];
|
||||
Header: React.ReactNode;
|
||||
} & ModuleTemplateProps;
|
||||
|
||||
const Container = React.memo(function Container(props: Props) {
|
||||
const { modules = [], Header, systemTemplates, pluginTemplates, show2Plugin } = props;
|
||||
|
||||
const {
|
||||
isOpen: isOpenTemplate,
|
||||
onOpen: onOpenTemplate,
|
||||
onClose: onCloseTemplate
|
||||
} = useDisclosure();
|
||||
|
||||
const { reactFlowWrapper, nodes, onNodesChange, edges, onEdgesChange, onConnect, initData } =
|
||||
useFlowProviderStore();
|
||||
|
||||
useEffect(() => {
|
||||
initData(JSON.parse(JSON.stringify(modules)));
|
||||
}, [modules.length]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* header */}
|
||||
{Header}
|
||||
<Box
|
||||
minH={'400px'}
|
||||
flex={'1 0 0'}
|
||||
w={'100%'}
|
||||
h={0}
|
||||
position={'relative'}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}}
|
||||
>
|
||||
{/* open module template */}
|
||||
<IconButton
|
||||
position={'absolute'}
|
||||
top={5}
|
||||
left={5}
|
||||
w={'38px'}
|
||||
h={'38px'}
|
||||
borderRadius={'50%'}
|
||||
icon={<SmallCloseIcon fontSize={'26px'} />}
|
||||
transform={isOpenTemplate ? '' : 'rotate(135deg)'}
|
||||
transition={'0.2s ease'}
|
||||
aria-label={''}
|
||||
zIndex={1}
|
||||
boxShadow={'2px 2px 6px #85b1ff'}
|
||||
onClick={() => {
|
||||
isOpenTemplate ? onCloseTemplate() : onOpenTemplate();
|
||||
}}
|
||||
/>
|
||||
|
||||
<ReactFlow
|
||||
ref={reactFlowWrapper}
|
||||
fitView
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
minZoom={0.1}
|
||||
maxZoom={1.5}
|
||||
defaultEdgeOptions={edgeOptions}
|
||||
connectionLineStyle={connectionLineStyle}
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={(connect) => {
|
||||
connect.sourceHandle &&
|
||||
connect.targetHandle &&
|
||||
onConnect({
|
||||
connect
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Background />
|
||||
<Controls position={'bottom-right'} style={{ display: 'flex' }} showInteractive={false} />
|
||||
</ReactFlow>
|
||||
|
||||
<TemplateList
|
||||
systemTemplates={systemTemplates}
|
||||
pluginTemplates={pluginTemplates}
|
||||
show2Plugin={show2Plugin}
|
||||
isOpen={isOpenTemplate}
|
||||
onClose={onCloseTemplate}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
const Flow = (data: Props) => {
|
||||
return (
|
||||
<Box h={'100%'} position={'fixed'} zIndex={999} top={0} left={0} right={0} bottom={0}>
|
||||
<ReactFlowProvider>
|
||||
<Flex h={'100%'} flexDirection={'column'} bg={'#fff'}>
|
||||
<Container {...data} />
|
||||
</Flex>
|
||||
</ReactFlowProvider>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(Flow);
|
||||
@@ -1,14 +1,14 @@
|
||||
import { AppTypeEnum, SystemInputEnum } from '../app';
|
||||
import { TaskResponseKeyEnum } from '../chat';
|
||||
import {
|
||||
FlowModuleTypeEnum,
|
||||
FlowInputItemTypeEnum,
|
||||
FlowOutputItemTypeEnum,
|
||||
SpecialInputKeyEnum,
|
||||
FlowValueTypeEnum
|
||||
} from './index';
|
||||
FlowNodeTypeEnum,
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeSpecialInputKeyEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
import type { AppItemType } from '@/types/app';
|
||||
import type { FlowModuleTemplateType } from '@/types/core/app/flow';
|
||||
import type { FlowModuleTemplateType } from '@fastgpt/global/core/module/type';
|
||||
import { chatModelList, cqModelList } from '@/web/common/system/staticData';
|
||||
import {
|
||||
Input_Template_History,
|
||||
@@ -30,7 +30,8 @@ export const variableTip =
|
||||
export const questionGuideTip = `对话结束后,会为生成 3 个引导性问题。`;
|
||||
|
||||
export const VariableModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.variable,
|
||||
id: FlowNodeTypeEnum.variable,
|
||||
flowType: FlowNodeTypeEnum.variable,
|
||||
logo: '/imgs/module/variable.png',
|
||||
name: '全局变量',
|
||||
intro: variableTip,
|
||||
@@ -39,7 +40,7 @@ export const VariableModule: FlowModuleTemplateType = {
|
||||
inputs: [
|
||||
{
|
||||
key: SystemInputEnum.variables,
|
||||
type: FlowInputItemTypeEnum.systemInput,
|
||||
type: FlowNodeInputTypeEnum.systemInput,
|
||||
label: '变量输入',
|
||||
value: []
|
||||
}
|
||||
@@ -47,39 +48,41 @@ export const VariableModule: FlowModuleTemplateType = {
|
||||
outputs: []
|
||||
};
|
||||
export const UserGuideModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.userGuide,
|
||||
id: FlowNodeTypeEnum.userGuide,
|
||||
flowType: FlowNodeTypeEnum.userGuide,
|
||||
logo: '/imgs/module/userGuide.png',
|
||||
name: '用户引导',
|
||||
intro: userGuideTip,
|
||||
inputs: [
|
||||
{
|
||||
key: SystemInputEnum.welcomeText,
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '开场白'
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.variables,
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '对话框变量',
|
||||
value: []
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.questionGuide,
|
||||
type: FlowInputItemTypeEnum.switch,
|
||||
type: FlowNodeInputTypeEnum.switch,
|
||||
label: '问题引导'
|
||||
}
|
||||
],
|
||||
outputs: []
|
||||
};
|
||||
export const UserInputModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.questionInput,
|
||||
id: FlowNodeTypeEnum.questionInput,
|
||||
flowType: FlowNodeTypeEnum.questionInput,
|
||||
logo: '/imgs/module/userChatInput.png',
|
||||
name: '用户问题(对话入口)',
|
||||
intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。',
|
||||
inputs: [
|
||||
{
|
||||
key: SystemInputEnum.userChatInput,
|
||||
type: FlowInputItemTypeEnum.systemInput,
|
||||
type: FlowNodeInputTypeEnum.systemInput,
|
||||
label: '用户问题'
|
||||
}
|
||||
],
|
||||
@@ -87,21 +90,22 @@ export const UserInputModule: FlowModuleTemplateType = {
|
||||
{
|
||||
key: SystemInputEnum.userChatInput,
|
||||
label: '用户问题',
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
};
|
||||
export const HistoryModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.historyNode,
|
||||
id: FlowNodeTypeEnum.historyNode,
|
||||
flowType: FlowNodeTypeEnum.historyNode,
|
||||
logo: '/imgs/module/history.png',
|
||||
name: '聊天记录',
|
||||
intro: '用户输入的内容。该模块通常作为应用的入口,用户在发送消息后会首先执行该模块。',
|
||||
inputs: [
|
||||
{
|
||||
key: 'maxContext',
|
||||
type: FlowInputItemTypeEnum.numberInput,
|
||||
type: FlowNodeInputTypeEnum.numberInput,
|
||||
label: '最长记录数',
|
||||
value: 6,
|
||||
min: 0,
|
||||
@@ -109,7 +113,7 @@ export const HistoryModule: FlowModuleTemplateType = {
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.history,
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '聊天记录'
|
||||
}
|
||||
],
|
||||
@@ -117,15 +121,15 @@ export const HistoryModule: FlowModuleTemplateType = {
|
||||
{
|
||||
key: SystemInputEnum.history,
|
||||
label: '聊天记录',
|
||||
valueType: FlowValueTypeEnum.chatHistory,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.chatHistory,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const ChatModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.chatNode,
|
||||
id: FlowNodeTypeEnum.chatNode,
|
||||
flowType: FlowNodeTypeEnum.chatNode,
|
||||
logo: '/imgs/module/AI.png',
|
||||
name: 'AI 对话',
|
||||
intro: 'AI 大模型对话',
|
||||
@@ -134,7 +138,7 @@ export const ChatModule: FlowModuleTemplateType = {
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: 'model',
|
||||
type: FlowInputItemTypeEnum.selectChatModel,
|
||||
type: FlowNodeInputTypeEnum.selectChatModel,
|
||||
label: '对话模型',
|
||||
value: chatModelList?.[0]?.model,
|
||||
customData: () => chatModelList,
|
||||
@@ -143,7 +147,7 @@ export const ChatModule: FlowModuleTemplateType = {
|
||||
},
|
||||
{
|
||||
key: 'temperature',
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '温度',
|
||||
value: 0,
|
||||
min: 0,
|
||||
@@ -156,7 +160,7 @@ export const ChatModule: FlowModuleTemplateType = {
|
||||
},
|
||||
{
|
||||
key: 'maxToken',
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '回复上限',
|
||||
value: chatModelList?.[0] ? chatModelList[0].maxToken / 2 : 2000,
|
||||
min: 100,
|
||||
@@ -172,47 +176,47 @@ export const ChatModule: FlowModuleTemplateType = {
|
||||
},
|
||||
{
|
||||
key: 'aiSettings',
|
||||
type: FlowInputItemTypeEnum.aiSettings,
|
||||
type: FlowNodeInputTypeEnum.aiSettings,
|
||||
label: '',
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'systemPrompt',
|
||||
type: FlowInputItemTypeEnum.textarea,
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
label: '系统提示词',
|
||||
max: 300,
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
description: ChatModelSystemTip,
|
||||
placeholder: ChatModelSystemTip,
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.isResponseAnswerText,
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '返回AI内容',
|
||||
valueType: FlowValueTypeEnum.boolean,
|
||||
valueType: FlowNodeValTypeEnum.boolean,
|
||||
value: true
|
||||
},
|
||||
{
|
||||
key: 'quoteTemplate',
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '引用内容模板',
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
key: 'quotePrompt',
|
||||
type: FlowInputItemTypeEnum.hidden,
|
||||
type: FlowNodeInputTypeEnum.hidden,
|
||||
label: '引用内容提示词',
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
key: 'quoteQA',
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '引用内容',
|
||||
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
|
||||
valueType: FlowValueTypeEnum.kbQuote,
|
||||
valueType: FlowNodeValTypeEnum.datasetQuote,
|
||||
connected: false
|
||||
},
|
||||
Input_Template_History,
|
||||
@@ -223,24 +227,24 @@ export const ChatModule: FlowModuleTemplateType = {
|
||||
key: TaskResponseKeyEnum.history,
|
||||
label: '新的上下文',
|
||||
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
|
||||
valueType: FlowValueTypeEnum.chatHistory,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.chatHistory,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: TaskResponseKeyEnum.answerText,
|
||||
label: 'AI回复',
|
||||
description: '将在 stream 回复完毕后触发',
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
Output_Template_Finish
|
||||
]
|
||||
};
|
||||
|
||||
export const KBSearchModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.datasetSearchNode,
|
||||
export const DatasetSearchModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.datasetSearchNode,
|
||||
flowType: FlowNodeTypeEnum.datasetSearchNode,
|
||||
logo: '/imgs/module/db.png',
|
||||
name: '知识库搜索',
|
||||
intro: '去知识库中搜索对应的答案。可作为 AI 对话引用参考。',
|
||||
@@ -249,7 +253,7 @@ export const KBSearchModule: FlowModuleTemplateType = {
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: 'datasets',
|
||||
type: FlowInputItemTypeEnum.selectDataset,
|
||||
type: FlowNodeInputTypeEnum.selectDataset,
|
||||
label: '关联的知识库',
|
||||
value: [],
|
||||
list: [],
|
||||
@@ -258,7 +262,7 @@ export const KBSearchModule: FlowModuleTemplateType = {
|
||||
},
|
||||
{
|
||||
key: 'similarity',
|
||||
type: FlowInputItemTypeEnum.slider,
|
||||
type: FlowNodeInputTypeEnum.slider,
|
||||
label: '相似度',
|
||||
value: 0.4,
|
||||
min: 0,
|
||||
@@ -271,7 +275,7 @@ export const KBSearchModule: FlowModuleTemplateType = {
|
||||
},
|
||||
{
|
||||
key: 'limit',
|
||||
type: FlowInputItemTypeEnum.slider,
|
||||
type: FlowNodeInputTypeEnum.slider,
|
||||
label: '单次搜索上限',
|
||||
description: '最多取 n 条记录作为本次问题引用',
|
||||
value: 5,
|
||||
@@ -289,15 +293,15 @@ export const KBSearchModule: FlowModuleTemplateType = {
|
||||
{
|
||||
key: 'isEmpty',
|
||||
label: '搜索结果为空',
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowValueTypeEnum.boolean,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.boolean,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'unEmpty',
|
||||
label: '搜索结果不为空',
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowValueTypeEnum.boolean,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.boolean,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
@@ -305,16 +309,16 @@ export const KBSearchModule: FlowModuleTemplateType = {
|
||||
label: '引用内容',
|
||||
description:
|
||||
'始终返回数组,如果希望搜索结果为空时执行额外操作,需要用到上面的两个输入以及目标模块的触发器',
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowValueTypeEnum.kbQuote,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.datasetQuote,
|
||||
targets: []
|
||||
},
|
||||
Output_Template_Finish
|
||||
]
|
||||
};
|
||||
|
||||
export const AnswerModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.answerNode,
|
||||
id: FlowNodeTypeEnum.answerNode,
|
||||
flowType: FlowNodeTypeEnum.answerNode,
|
||||
logo: '/imgs/module/reply.png',
|
||||
name: '指定回复',
|
||||
intro: '该模块可以直接回复一段指定的内容。常用于引导、提示',
|
||||
@@ -322,9 +326,9 @@ export const AnswerModule: FlowModuleTemplateType = {
|
||||
inputs: [
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: SpecialInputKeyEnum.answerText,
|
||||
type: FlowInputItemTypeEnum.textarea,
|
||||
valueType: FlowValueTypeEnum.any,
|
||||
key: FlowNodeSpecialInputKeyEnum.answerText,
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: FlowNodeValTypeEnum.any,
|
||||
value: '',
|
||||
label: '回复的内容',
|
||||
description:
|
||||
@@ -334,7 +338,8 @@ export const AnswerModule: FlowModuleTemplateType = {
|
||||
outputs: [Output_Template_Finish]
|
||||
};
|
||||
export const ClassifyQuestionModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.classifyQuestion,
|
||||
id: FlowNodeTypeEnum.classifyQuestion,
|
||||
flowType: FlowNodeTypeEnum.classifyQuestion,
|
||||
logo: '/imgs/module/cq.png',
|
||||
name: '问题分类',
|
||||
intro: '可以判断用户问题属于哪方面问题,从而执行不同的操作。',
|
||||
@@ -345,7 +350,7 @@ export const ClassifyQuestionModule: FlowModuleTemplateType = {
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: 'model',
|
||||
type: FlowInputItemTypeEnum.selectChatModel,
|
||||
type: FlowNodeInputTypeEnum.selectChatModel,
|
||||
label: '分类模型',
|
||||
value: cqModelList?.[0]?.model,
|
||||
customData: () => cqModelList,
|
||||
@@ -354,8 +359,8 @@ export const ClassifyQuestionModule: FlowModuleTemplateType = {
|
||||
},
|
||||
{
|
||||
key: 'systemPrompt',
|
||||
type: FlowInputItemTypeEnum.textarea,
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
value: '',
|
||||
label: '背景知识',
|
||||
description:
|
||||
@@ -365,8 +370,8 @@ export const ClassifyQuestionModule: FlowModuleTemplateType = {
|
||||
Input_Template_History,
|
||||
Input_Template_UserChatInput,
|
||||
{
|
||||
key: SpecialInputKeyEnum.agents,
|
||||
type: FlowInputItemTypeEnum.custom,
|
||||
key: FlowNodeSpecialInputKeyEnum.agents,
|
||||
type: FlowNodeInputTypeEnum.custom,
|
||||
label: '',
|
||||
value: [
|
||||
{
|
||||
@@ -388,25 +393,26 @@ export const ClassifyQuestionModule: FlowModuleTemplateType = {
|
||||
{
|
||||
key: 'fasw',
|
||||
label: '',
|
||||
type: FlowOutputItemTypeEnum.hidden,
|
||||
type: FlowNodeOutputTypeEnum.hidden,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'fqsw',
|
||||
label: '',
|
||||
type: FlowOutputItemTypeEnum.hidden,
|
||||
type: FlowNodeOutputTypeEnum.hidden,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'fesw',
|
||||
label: '',
|
||||
type: FlowOutputItemTypeEnum.hidden,
|
||||
type: FlowNodeOutputTypeEnum.hidden,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
};
|
||||
export const ContextExtractModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.contentExtract,
|
||||
id: FlowNodeTypeEnum.contentExtract,
|
||||
flowType: FlowNodeTypeEnum.contentExtract,
|
||||
logo: '/imgs/module/extract.png',
|
||||
name: '文本内容提取',
|
||||
intro: '从文本中提取出指定格式的数据',
|
||||
@@ -416,8 +422,8 @@ export const ContextExtractModule: FlowModuleTemplateType = {
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: ContextExtractEnum.description,
|
||||
type: FlowInputItemTypeEnum.textarea,
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowNodeInputTypeEnum.textarea,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
value: '',
|
||||
label: '提取要求描述',
|
||||
description: '写一段提取要求,告诉 AI 需要提取哪些内容',
|
||||
@@ -427,14 +433,14 @@ export const ContextExtractModule: FlowModuleTemplateType = {
|
||||
Input_Template_History,
|
||||
{
|
||||
key: ContextExtractEnum.content,
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '需要提取的文本',
|
||||
required: true,
|
||||
valueType: FlowValueTypeEnum.string
|
||||
valueType: FlowNodeValTypeEnum.string
|
||||
},
|
||||
{
|
||||
key: ContextExtractEnum.extractKeys,
|
||||
type: FlowInputItemTypeEnum.custom,
|
||||
type: FlowNodeInputTypeEnum.custom,
|
||||
label: '目标字段',
|
||||
description: "由 '描述' 和 'key' 组成一个目标字段,可提取多个目标字段",
|
||||
value: []
|
||||
@@ -444,29 +450,30 @@ export const ContextExtractModule: FlowModuleTemplateType = {
|
||||
{
|
||||
key: ContextExtractEnum.success,
|
||||
label: '字段完全提取',
|
||||
valueType: FlowValueTypeEnum.boolean,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.boolean,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: ContextExtractEnum.failed,
|
||||
label: '提取字段缺失',
|
||||
valueType: FlowValueTypeEnum.boolean,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.boolean,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: ContextExtractEnum.fields,
|
||||
label: '完整提取结果',
|
||||
description: '一个 JSON 字符串,例如:{"name:":"YY","Time":"2023/7/2 18:00"}',
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
};
|
||||
export const HttpModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.httpRequest,
|
||||
id: FlowNodeTypeEnum.httpRequest,
|
||||
flowType: FlowNodeTypeEnum.httpRequest,
|
||||
logo: '/imgs/module/http.png',
|
||||
name: 'HTTP模块',
|
||||
intro: '可以发出一个 HTTP POST 请求,实现更为复杂的操作(联网搜索、数据库查询等)',
|
||||
@@ -477,7 +484,7 @@ export const HttpModule: FlowModuleTemplateType = {
|
||||
{
|
||||
key: HttpPropsEnum.url,
|
||||
value: '',
|
||||
type: FlowInputItemTypeEnum.input,
|
||||
type: FlowNodeInputTypeEnum.input,
|
||||
label: '请求地址',
|
||||
description: '请求目标地址',
|
||||
placeholder: 'https://api.fastgpt.run/getInventory',
|
||||
@@ -488,7 +495,8 @@ export const HttpModule: FlowModuleTemplateType = {
|
||||
outputs: [Output_Template_Finish]
|
||||
};
|
||||
export const EmptyModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.empty,
|
||||
id: FlowNodeTypeEnum.empty,
|
||||
flowType: FlowNodeTypeEnum.empty,
|
||||
logo: '/imgs/module/cq.png',
|
||||
name: '该模块已被移除',
|
||||
intro: '',
|
||||
@@ -496,10 +504,11 @@ export const EmptyModule: FlowModuleTemplateType = {
|
||||
inputs: [],
|
||||
outputs: []
|
||||
};
|
||||
export const AppModule: FlowModuleTemplateType = {
|
||||
flowType: FlowModuleTypeEnum.app,
|
||||
export const RunAppModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.runApp,
|
||||
flowType: FlowNodeTypeEnum.runApp,
|
||||
logo: '/imgs/module/app.png',
|
||||
name: '应用调用(测试版)',
|
||||
name: '应用调用',
|
||||
intro: '可以选择一个其他应用进行调用',
|
||||
description: '可以选择一个其他应用进行调用',
|
||||
showStatus: true,
|
||||
@@ -507,7 +516,7 @@ export const AppModule: FlowModuleTemplateType = {
|
||||
Input_Template_TFSwitch,
|
||||
{
|
||||
key: 'app',
|
||||
type: FlowInputItemTypeEnum.selectApp,
|
||||
type: FlowNodeInputTypeEnum.selectApp,
|
||||
label: '选择一个应用',
|
||||
description: '选择一个其他应用进行调用',
|
||||
required: true
|
||||
@@ -520,38 +529,87 @@ export const AppModule: FlowModuleTemplateType = {
|
||||
key: TaskResponseKeyEnum.history,
|
||||
label: '新的上下文',
|
||||
description: '将该应用回复内容拼接到历史记录中,作为新的上下文返回',
|
||||
valueType: FlowValueTypeEnum.chatHistory,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.chatHistory,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: TaskResponseKeyEnum.answerText,
|
||||
label: 'AI回复',
|
||||
description: '将在应用完全结束后触发',
|
||||
valueType: FlowValueTypeEnum.string,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.string,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
},
|
||||
Output_Template_Finish
|
||||
]
|
||||
};
|
||||
export const PluginInputModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.pluginInput,
|
||||
flowType: FlowNodeTypeEnum.pluginInput,
|
||||
logo: '/imgs/module/input.png',
|
||||
name: '定义插件输入',
|
||||
intro: '自定义配置外部输入,使用插件时,仅暴露自定义配置的输入',
|
||||
description: '自定义配置外部输入,使用插件时,仅暴露自定义配置的输入',
|
||||
showStatus: false,
|
||||
inputs: [],
|
||||
outputs: []
|
||||
};
|
||||
export const PluginOutputModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.pluginOutput,
|
||||
flowType: FlowNodeTypeEnum.pluginOutput,
|
||||
logo: '/imgs/module/output.png',
|
||||
name: '定义插件输出',
|
||||
intro: '自定义配置外部输出,使用插件时,仅暴露自定义配置的输出',
|
||||
description: '自定义配置外部输出,使用插件时,仅暴露自定义配置的输出',
|
||||
showStatus: false,
|
||||
inputs: [],
|
||||
outputs: []
|
||||
};
|
||||
export const PluginModule: FlowModuleTemplateType = {
|
||||
id: FlowNodeTypeEnum.pluginModule,
|
||||
flowType: FlowNodeTypeEnum.pluginModule,
|
||||
logo: '/imgs/module/custom.png',
|
||||
name: '自定义模块',
|
||||
showStatus: false,
|
||||
inputs: [],
|
||||
outputs: []
|
||||
};
|
||||
|
||||
export const ModuleTemplates = [
|
||||
{
|
||||
label: '输入模块',
|
||||
list: [UserInputModule, HistoryModule]
|
||||
},
|
||||
export const SystemModuleTemplates = [
|
||||
{
|
||||
label: '引导模块',
|
||||
list: [UserGuideModule]
|
||||
},
|
||||
{
|
||||
label: '输入模块',
|
||||
list: [UserInputModule, HistoryModule]
|
||||
},
|
||||
{
|
||||
label: '内容生成',
|
||||
list: [ChatModule, AnswerModule]
|
||||
},
|
||||
{
|
||||
label: '核心调用',
|
||||
list: [KBSearchModule, AppModule]
|
||||
list: [DatasetSearchModule, RunAppModule]
|
||||
},
|
||||
{
|
||||
label: '函数模块',
|
||||
list: [ClassifyQuestionModule, ContextExtractModule, HttpModule]
|
||||
}
|
||||
];
|
||||
export const PluginModuleTemplates = [
|
||||
{
|
||||
label: '输入输出',
|
||||
list: [PluginInputModule, PluginOutputModule, HistoryModule]
|
||||
},
|
||||
{
|
||||
label: '内容生成',
|
||||
list: [ChatModule, AnswerModule]
|
||||
},
|
||||
{
|
||||
label: '核心调用',
|
||||
list: [DatasetSearchModule, RunAppModule]
|
||||
},
|
||||
{
|
||||
label: '函数模块',
|
||||
@@ -564,13 +622,16 @@ export const ModuleTemplatesFlat = [
|
||||
UserInputModule,
|
||||
HistoryModule,
|
||||
ChatModule,
|
||||
KBSearchModule,
|
||||
DatasetSearchModule,
|
||||
AnswerModule,
|
||||
ClassifyQuestionModule,
|
||||
ContextExtractModule,
|
||||
HttpModule,
|
||||
EmptyModule,
|
||||
AppModule
|
||||
RunAppModule,
|
||||
PluginInputModule,
|
||||
PluginOutputModule,
|
||||
PluginModule
|
||||
];
|
||||
|
||||
// template
|
||||
@@ -665,7 +726,7 @@ export const appTemplates: (AppItemType & {
|
||||
{
|
||||
key: 'history',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
type: 'source',
|
||||
targets: [
|
||||
{
|
||||
@@ -757,14 +818,14 @@ export const appTemplates: (AppItemType & {
|
||||
key: 'quoteQA',
|
||||
type: 'target',
|
||||
label: '引用内容',
|
||||
valueType: 'kb_quote',
|
||||
valueType: 'datasetQuote',
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'target',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
@@ -886,7 +947,7 @@ export const appTemplates: (AppItemType & {
|
||||
{
|
||||
key: 'history',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
type: 'source',
|
||||
targets: [
|
||||
{
|
||||
@@ -1003,7 +1064,7 @@ export const appTemplates: (AppItemType & {
|
||||
description:
|
||||
'始终返回数组,如果希望搜索结果为空时执行额外操作,需要用到上面的两个输入以及目标模块的触发器',
|
||||
type: 'source',
|
||||
valueType: 'kb_quote',
|
||||
valueType: 'datasetQuote',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'chatModule',
|
||||
@@ -1094,14 +1155,14 @@ export const appTemplates: (AppItemType & {
|
||||
key: 'quoteQA',
|
||||
type: 'target',
|
||||
label: '引用内容',
|
||||
valueType: 'kb_quote',
|
||||
valueType: 'datasetQuote',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'target',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
@@ -1292,7 +1353,7 @@ export const appTemplates: (AppItemType & {
|
||||
{
|
||||
key: 'history',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
type: 'source',
|
||||
targets: [
|
||||
{
|
||||
@@ -1401,14 +1462,14 @@ export const appTemplates: (AppItemType & {
|
||||
type: 'custom',
|
||||
label: '引用内容',
|
||||
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
|
||||
valueType: 'kb_quote',
|
||||
valueType: 'datasetQuote',
|
||||
connected: false
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'target',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
@@ -1441,7 +1502,7 @@ export const appTemplates: (AppItemType & {
|
||||
key: 'history',
|
||||
label: '新的上下文',
|
||||
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
type: 'source',
|
||||
targets: []
|
||||
}
|
||||
@@ -1520,7 +1581,7 @@ export const appTemplates: (AppItemType & {
|
||||
{
|
||||
key: 'history',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
type: 'source',
|
||||
targets: [
|
||||
{
|
||||
@@ -1564,7 +1625,7 @@ export const appTemplates: (AppItemType & {
|
||||
key: 'history',
|
||||
type: 'target',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
@@ -1863,14 +1924,14 @@ export const appTemplates: (AppItemType & {
|
||||
type: 'custom',
|
||||
label: '引用内容',
|
||||
description: "对象数组格式,结构:\n [{q:'问题',a:'回答'}]",
|
||||
valueType: 'kb_quote',
|
||||
valueType: 'datasetQuote',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
key: 'history',
|
||||
type: 'target',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
connected: true
|
||||
},
|
||||
{
|
||||
@@ -1903,7 +1964,7 @@ export const appTemplates: (AppItemType & {
|
||||
key: 'history',
|
||||
label: '新的上下文',
|
||||
description: '将本次回复内容拼接上历史记录,作为新的上下文返回',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
type: 'source',
|
||||
targets: []
|
||||
}
|
||||
@@ -1938,7 +1999,7 @@ export const appTemplates: (AppItemType & {
|
||||
{
|
||||
key: 'history',
|
||||
label: '聊天记录',
|
||||
valueType: 'chat_history',
|
||||
valueType: 'chatHistory',
|
||||
type: 'source',
|
||||
targets: [
|
||||
{
|
||||
@@ -2055,7 +2116,7 @@ export const appTemplates: (AppItemType & {
|
||||
description:
|
||||
'始终返回数组,如果希望搜索结果为空时执行额外操作,需要用到上面的两个输入以及目标模块的触发器',
|
||||
type: 'source',
|
||||
valueType: 'kb_quote',
|
||||
valueType: 'datasetQuote',
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'nlfwkc',
|
||||
|
||||
@@ -1,86 +1,68 @@
|
||||
import type { BoxProps } from '@chakra-ui/react';
|
||||
import { FlowNodeTypeEnum, FlowNodeValTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
export enum FlowInputItemTypeEnum {
|
||||
systemInput = 'systemInput', // history, userChatInput, variableInput
|
||||
input = 'input', // one line input
|
||||
textarea = 'textarea',
|
||||
numberInput = 'numberInput',
|
||||
select = 'select',
|
||||
slider = 'slider',
|
||||
custom = 'custom',
|
||||
target = 'target', // data input
|
||||
switch = 'switch',
|
||||
chatInput = 'chatInput',
|
||||
selectApp = 'selectApp',
|
||||
// chat special input
|
||||
aiSettings = 'aiSettings',
|
||||
maxToken = 'maxToken',
|
||||
selectChatModel = 'selectChatModel',
|
||||
// dataset special input
|
||||
selectDataset = 'selectDataset',
|
||||
hidden = 'hidden'
|
||||
}
|
||||
|
||||
export enum FlowOutputItemTypeEnum {
|
||||
answer = 'answer',
|
||||
source = 'source',
|
||||
hidden = 'hidden'
|
||||
}
|
||||
|
||||
export enum FlowModuleTypeEnum {
|
||||
empty = 'empty',
|
||||
variable = 'variable',
|
||||
userGuide = 'userGuide',
|
||||
questionInput = 'questionInput',
|
||||
historyNode = 'historyNode',
|
||||
chatNode = 'chatNode',
|
||||
datasetSearchNode = 'datasetSearchNode',
|
||||
tfSwitchNode = 'tfSwitchNode',
|
||||
answerNode = 'answerNode',
|
||||
classifyQuestion = 'classifyQuestion',
|
||||
contentExtract = 'contentExtract',
|
||||
httpRequest = 'httpRequest',
|
||||
app = 'app'
|
||||
}
|
||||
|
||||
export enum SpecialInputKeyEnum {
|
||||
'answerText' = 'text',
|
||||
'agents' = 'agents' // cq agent key
|
||||
}
|
||||
|
||||
export enum FlowValueTypeEnum {
|
||||
'string' = 'string',
|
||||
'number' = 'number',
|
||||
'boolean' = 'boolean',
|
||||
'chatHistory' = 'chat_history',
|
||||
'kbQuote' = 'kb_quote',
|
||||
'any' = 'any'
|
||||
}
|
||||
|
||||
export const FlowValueTypeStyle: Record<`${FlowValueTypeEnum}`, BoxProps> = {
|
||||
[FlowValueTypeEnum.string]: {
|
||||
export const FlowValueTypeStyle: Record<`${FlowNodeValTypeEnum}`, BoxProps> = {
|
||||
[FlowNodeValTypeEnum.string]: {
|
||||
background: '#36ADEF'
|
||||
},
|
||||
[FlowValueTypeEnum.number]: {
|
||||
[FlowNodeValTypeEnum.number]: {
|
||||
background: '#FB7C3C'
|
||||
},
|
||||
[FlowValueTypeEnum.boolean]: {
|
||||
[FlowNodeValTypeEnum.boolean]: {
|
||||
background: '#E7D118'
|
||||
},
|
||||
[FlowValueTypeEnum.chatHistory]: {
|
||||
[FlowNodeValTypeEnum.chatHistory]: {
|
||||
background: '#00A9A6'
|
||||
},
|
||||
[FlowValueTypeEnum.kbQuote]: {
|
||||
[FlowNodeValTypeEnum.datasetQuote]: {
|
||||
background: '#A558C9'
|
||||
},
|
||||
[FlowValueTypeEnum.any]: {
|
||||
[FlowNodeValTypeEnum.any]: {
|
||||
background: '#9CA2A8'
|
||||
}
|
||||
};
|
||||
export const FlowValueTypeTip = {
|
||||
[FlowNodeValTypeEnum.string]: {
|
||||
label: 'app.module.valueType.string',
|
||||
example: ''
|
||||
},
|
||||
[FlowNodeValTypeEnum.number]: {
|
||||
label: 'app.module.valueType.number',
|
||||
example: ''
|
||||
},
|
||||
[FlowNodeValTypeEnum.boolean]: {
|
||||
label: 'app.module.valueType.boolean',
|
||||
example: ''
|
||||
},
|
||||
[FlowNodeValTypeEnum.chatHistory]: {
|
||||
label: 'app.module.valueType.chatHistory',
|
||||
example: `{
|
||||
obj: System | Human | AI;
|
||||
value: string;
|
||||
}`
|
||||
},
|
||||
[FlowNodeValTypeEnum.datasetQuote]: {
|
||||
label: 'app.module.valueType.datasetQuote',
|
||||
example: `{
|
||||
id: string;
|
||||
datasetId: string;
|
||||
collectionId: string;
|
||||
sourceName: string;
|
||||
sourceId?: string;
|
||||
q: string;
|
||||
a: string
|
||||
}`
|
||||
},
|
||||
[FlowNodeValTypeEnum.any]: {
|
||||
label: 'app.module.valueType.any',
|
||||
example: ''
|
||||
}
|
||||
};
|
||||
|
||||
export const initModuleType: Record<string, boolean> = {
|
||||
[FlowModuleTypeEnum.historyNode]: true,
|
||||
[FlowModuleTypeEnum.questionInput]: true
|
||||
[FlowNodeTypeEnum.historyNode]: true,
|
||||
[FlowNodeTypeEnum.questionInput]: true,
|
||||
[FlowNodeTypeEnum.pluginInput]: true
|
||||
};
|
||||
|
||||
export const edgeOptions = {
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
import type { FlowInputItemType } from '@/types/core/app/flow';
|
||||
import type { FlowNodeInputItemType } from '@fastgpt/global/core/module/node/type.d';
|
||||
import { SystemInputEnum } from '../app';
|
||||
import { FlowInputItemTypeEnum, FlowValueTypeEnum } from './index';
|
||||
import {
|
||||
FlowNodeInputTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
export const Input_Template_TFSwitch: FlowInputItemType = {
|
||||
export const Input_Template_TFSwitch: FlowNodeInputItemType = {
|
||||
key: SystemInputEnum.switch,
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '触发器',
|
||||
valueType: FlowValueTypeEnum.any
|
||||
valueType: FlowNodeValTypeEnum.any
|
||||
};
|
||||
|
||||
export const Input_Template_History: FlowInputItemType = {
|
||||
export const Input_Template_History: FlowNodeInputItemType = {
|
||||
key: SystemInputEnum.history,
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '聊天记录',
|
||||
valueType: FlowValueTypeEnum.chatHistory
|
||||
valueType: FlowNodeValTypeEnum.chatHistory
|
||||
};
|
||||
|
||||
export const Input_Template_UserChatInput: FlowInputItemType = {
|
||||
export const Input_Template_UserChatInput: FlowNodeInputItemType = {
|
||||
key: SystemInputEnum.userChatInput,
|
||||
type: FlowInputItemTypeEnum.target,
|
||||
type: FlowNodeInputTypeEnum.target,
|
||||
label: '用户问题',
|
||||
required: true,
|
||||
valueType: FlowValueTypeEnum.string
|
||||
valueType: FlowNodeValTypeEnum.string
|
||||
};
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import type { FlowOutputItemType } from '@/types/core/app/flow';
|
||||
import type { FlowNodeOutputItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { SystemOutputEnum } from '../app';
|
||||
import { FlowOutputItemTypeEnum, FlowValueTypeEnum } from './index';
|
||||
import {
|
||||
FlowNodeOutputTypeEnum,
|
||||
FlowNodeValTypeEnum
|
||||
} from '@fastgpt/global/core/module/node/constant';
|
||||
|
||||
export const Output_Template_Finish: FlowOutputItemType = {
|
||||
export const Output_Template_Finish: FlowNodeOutputItemType = {
|
||||
key: SystemOutputEnum.finish,
|
||||
label: '模块调用结束',
|
||||
description: '模块调用结束时触发',
|
||||
valueType: FlowValueTypeEnum.boolean,
|
||||
type: FlowOutputItemTypeEnum.source,
|
||||
valueType: FlowNodeValTypeEnum.boolean,
|
||||
type: FlowNodeOutputTypeEnum.source,
|
||||
targets: []
|
||||
};
|
||||
|
||||
@@ -25,18 +25,3 @@ export enum PromotionEnum {
|
||||
register = 'register',
|
||||
pay = 'pay'
|
||||
}
|
||||
|
||||
export enum InformTypeEnum {
|
||||
system = 'system'
|
||||
}
|
||||
|
||||
export const InformTypeMap = {
|
||||
[InformTypeEnum.system]: {
|
||||
label: '系统通知'
|
||||
}
|
||||
};
|
||||
|
||||
export enum MyModelsTypeEnum {
|
||||
my = 'my',
|
||||
collection = 'collection'
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import { countPromptTokens } from '@/global/common/tiktoken';
|
||||
export function replaceVariable(text: string, obj: Record<string, string | number>) {
|
||||
for (const key in obj) {
|
||||
const val = obj[key];
|
||||
if (typeof val !== 'string') continue;
|
||||
if (!['string', 'number'].includes(typeof val)) continue;
|
||||
|
||||
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), val);
|
||||
text = text.replace(new RegExp(`{{(${key})}}`, 'g'), String(val));
|
||||
}
|
||||
return text || '';
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { AppSchema } from '@/types/mongoSchema';
|
||||
import type { ChatItemType } from '@/types/chat';
|
||||
import { AppModuleItemType, VariableItemType } from '@/types/app';
|
||||
import { VariableItemType } from '@/types/app';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
|
||||
export type InitChatResponse = {
|
||||
chatId: string;
|
||||
appId: string;
|
||||
app: {
|
||||
userGuideModule?: AppModuleItemType;
|
||||
userGuideModule?: ModuleItemType;
|
||||
chatModels?: string[];
|
||||
name: string;
|
||||
avatar: string;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { SystemInputEnum } from '@/constants/app';
|
||||
import { FlowModuleTypeEnum } from '@/constants/flow';
|
||||
import { AppModuleItemType, VariableItemType } from '@/types/app';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { VariableItemType } from '@/types/app';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
|
||||
export const getGuideModule = (modules: AppModuleItemType[]) =>
|
||||
modules.find((item) => item.flowType === FlowModuleTypeEnum.userGuide);
|
||||
export const getGuideModule = (modules: ModuleItemType[]) =>
|
||||
modules.find((item) => item.flowType === FlowNodeTypeEnum.userGuide);
|
||||
|
||||
export const splitGuideModule = (guideModules?: AppModuleItemType) => {
|
||||
export const splitGuideModule = (guideModules?: ModuleItemType) => {
|
||||
const welcomeText: string =
|
||||
guideModules?.inputs?.find((item) => item.key === SystemInputEnum.welcomeText)?.value || '';
|
||||
|
||||
|
||||
@@ -3,41 +3,81 @@ import { PromptTemplateItem } from '@fastgpt/global/core/ai/type.d';
|
||||
export const Prompt_QuoteTemplateList: PromptTemplateItem[] = [
|
||||
{
|
||||
title: '标准模板',
|
||||
desc: '包含 q 和 a 两个变量的标准模板',
|
||||
desc: '标准提示词,用于结构不固定的知识库。',
|
||||
value: `{{q}}\n{{a}}`
|
||||
},
|
||||
{
|
||||
title: '问答模板',
|
||||
desc: '适合 QA 问答结构的知识库,或大部分核心介绍位于 a 的知识库。',
|
||||
value: `{instruction:"{{q}}",output:"{{a}}"}`
|
||||
},
|
||||
{
|
||||
title: '全部变量',
|
||||
desc: '包含 q 和 a 两个变量的标准模板',
|
||||
value: `{instruction:"{{q}}",output:"{{a}}",source:"{{source}}",sourceId:"{{sourceId}}",index:"{{index}}"}`
|
||||
title: '标准严格模板',
|
||||
desc: '在标准模板基础上,对模型的回答做更严格的要求。',
|
||||
value: `{{q}}\n{{a}}`
|
||||
},
|
||||
{
|
||||
title: '严格问答模板',
|
||||
desc: '在问答模板基础上,对模型的回答做更严格的要求。',
|
||||
value: `{question:"{{q}}",answer:"{{a}}"}`
|
||||
}
|
||||
];
|
||||
|
||||
export const Prompt_QuotePromptList: PromptTemplateItem[] = [
|
||||
{
|
||||
title: '标准模式',
|
||||
title: '标准模板',
|
||||
desc: '',
|
||||
value: `你的背景知识:
|
||||
"""
|
||||
{{quote}}
|
||||
"""
|
||||
对话要求:
|
||||
1. 背景知识是最新的,其中 instruction 是相关介绍,output 是预期回答或补充。
|
||||
2. 使用背景知识回答问题。
|
||||
3. 使用对话的风格回答我的问题,答案要和背景知识表述一致。
|
||||
1. 背景知识是最新的实时的信息,使用背景知识回答问题。
|
||||
2. 优先使用背景知识的内容回答我的问题,答案应与背景知识严格一致。
|
||||
3. 背景知识无法回答我的问题时,可以忽略背景知识,根据你的知识来自由回答。
|
||||
4. 使用对话的风格,自然的回答问题。
|
||||
我的问题是:"{{question}}"`
|
||||
},
|
||||
{
|
||||
title: '严格模式',
|
||||
title: '问答模板',
|
||||
desc: '',
|
||||
value: `你的背景知识:
|
||||
"""
|
||||
{{quote}}
|
||||
"""
|
||||
对话要求:
|
||||
1. 背景知识是最新的,其中 instruction 是相关介绍,output 是预期回答或补充。
|
||||
2. 使用背景知识回答问题。
|
||||
3. 背景知识无法满足问题时,你需要回答:我不清楚关于xxx的内容。
|
||||
1. 背景知识是最新的实时的信息,使用背景知识回答问题,其中 instruction 是相关介绍,output 是预期回答或补充。
|
||||
2. 优先使用背景知识的内容回答我的问题,答案应与背景知识严格一致。
|
||||
3. 背景知识无法回答我的问题时,可以忽略背景知识,根据你的知识来自由回答。
|
||||
4. 使用对话的风格,自然的回答问题。
|
||||
我的问题是:"{{question}}"`
|
||||
},
|
||||
{
|
||||
title: '标准严格模板',
|
||||
desc: '',
|
||||
value: `你的背景知识:
|
||||
"""
|
||||
{{quote}}
|
||||
"""
|
||||
对话要求:
|
||||
1. 背景知识是最新的实时的信息,是你的唯一信息来源,使用背景知识回答问题。
|
||||
2. 优先使用背景知识回答我的问题,答案与背景知识完全一致,无需做其他回答。
|
||||
3. 背景知识与问题无关,或背景知识无法回答本次问题时,则拒绝回答本次问题:“我不太清除xxx”。
|
||||
4. 使用对话的风格,自然的回答问题。
|
||||
我的问题是:"{{question}}"`
|
||||
},
|
||||
{
|
||||
title: '严格问答模板',
|
||||
desc: '',
|
||||
value: `你的背景知识:
|
||||
"""
|
||||
{{quote}}
|
||||
"""
|
||||
对话要求:
|
||||
1. 背景知识是最新的实时的信息,是你的唯一信息来源,使用背景知识回答问题。
|
||||
2. 在背景知识的 JSON 中,question 是相关问题,answer 是已知答案。
|
||||
3. 选择 answer 中的内容作为答案,要求答案与 answer 完全一致,无需做其他回答。
|
||||
4. answer 中的答案无法满足问题,直接回复:“我不太清除xxx”。
|
||||
我的问题是:"{{question}}"`
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { UserType } from '@/types/user';
|
||||
import type { PromotionRecordSchema } from '@/types/mongoSchema';
|
||||
import type { PromotionRecordSchema } from '@fastgpt/global/support/activity/type.d';
|
||||
export interface ResLogin {
|
||||
user: UserType;
|
||||
token: string;
|
||||
|
||||
@@ -33,6 +33,7 @@ const BillTable = () => {
|
||||
to: new Date()
|
||||
});
|
||||
const { isPc } = useSystemStore();
|
||||
const [billDetail, setBillDetail] = useState<UserBillType>();
|
||||
|
||||
const {
|
||||
data: bills,
|
||||
@@ -48,8 +49,6 @@ const BillTable = () => {
|
||||
}
|
||||
});
|
||||
|
||||
const [billDetail, setBillDetail] = useState<UserBillType>();
|
||||
|
||||
return (
|
||||
<Flex flexDirection={'column'} py={[0, 5]} h={'100%'} position={'relative'}>
|
||||
<TableContainer px={[3, 8]} position={'relative'} flex={'1 0 0'} h={0} overflowY={'auto'}>
|
||||
@@ -106,4 +105,4 @@ const BillTable = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default BillTable;
|
||||
export default React.memo(BillTable);
|
||||
|
||||
@@ -271,7 +271,7 @@ const UserInfo = () => {
|
||||
>
|
||||
<Avatar src={'/imgs/openai.png'} w={'18px'} />
|
||||
<Box ml={2} flex={1}>
|
||||
OpenAI 账号
|
||||
OpenAI/OneAPI 账号
|
||||
</Box>
|
||||
<Box
|
||||
w={'9px'}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Box, Flex, useTheme } from '@chakra-ui/react';
|
||||
import { getInforms, readInform } from '@/web/support/user/api';
|
||||
import { usePagination } from '@/web/common/hooks/usePagination';
|
||||
import { useLoading } from '@/web/common/hooks/useLoading';
|
||||
import type { informSchema } from '@/types/mongoSchema';
|
||||
import type { UserInformSchema } from '@fastgpt/global/support/user/type';
|
||||
import { formatTimeToChatTime } from '@/utils/tools';
|
||||
import { useSystemStore } from '@/web/common/system/useSystemStore';
|
||||
import MyIcon from '@/components/Icon';
|
||||
@@ -20,7 +20,7 @@ const BillTable = () => {
|
||||
Pagination,
|
||||
getData,
|
||||
pageNum
|
||||
} = usePagination<informSchema>({
|
||||
} = usePagination<UserInformSchema>({
|
||||
api: getInforms,
|
||||
pageSize: isPc ? 20 : 10
|
||||
});
|
||||
|
||||
@@ -32,8 +32,8 @@ const OpenAIAccountModal = ({
|
||||
<MyModal isOpen onClose={onClose} title={t('user.OpenAI Account Setting')}>
|
||||
<ModalBody>
|
||||
<Box fontSize={'sm'} color={'myGray.500'}>
|
||||
可以填写 OpenAI 的 key,也可以是 OneAPI 的可以。如果你填写了该内容,在线上平台使用 OpenAI
|
||||
Chat 模型不会计费(不包含知识库训练、索引生成)。请注意你的 Key 是否有访问对应模型的权限。
|
||||
可以填写 OpenAI/OneAPI 的相关秘钥。如果你填写了该内容,在线上平台使用 OpenAI Chat
|
||||
模型不会计费(不包含知识库训练、索引生成)。请注意你的 Key 是否有访问对应模型的权限。
|
||||
</Box>
|
||||
<Flex alignItems={'center'} mt={5}>
|
||||
<Box flex={'0 0 65px'}>API Key:</Box>
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
Box
|
||||
} from '@chakra-ui/react';
|
||||
import { getPayOrders, checkPayResult } from '@/web/common/bill/api';
|
||||
import { PaySchema } from '@/types/mongoSchema';
|
||||
import type { PaySchema } from '@fastgpt/global/support/wallet/type.d';
|
||||
import dayjs from 'dayjs';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { formatPrice } from '@fastgpt/global/common/bill/tools';
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { connectToDatabase, App } from '@/service/mongo';
|
||||
import { FlowInputItemTypeEnum, FlowModuleTypeEnum } from '@/constants/flow';
|
||||
import { FlowNodeInputTypeEnum, FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { SystemInputEnum } from '@/constants/app';
|
||||
|
||||
const limit = 300;
|
||||
@@ -46,19 +46,19 @@ async function initVariable(): Promise<any> {
|
||||
const modules = jsonAPP.modules;
|
||||
|
||||
// 找到 variable
|
||||
const variable = modules.find((item) => item.flowType === FlowModuleTypeEnum.variable);
|
||||
const variable = modules.find((item) => item.flowType === FlowNodeTypeEnum.variable);
|
||||
if (!variable) return await app.save();
|
||||
|
||||
// 找到 guide 模块
|
||||
const userGuideModule = modules.find(
|
||||
(item) => item.flowType === FlowModuleTypeEnum.userGuide
|
||||
(item) => item.flowType === FlowNodeTypeEnum.userGuide
|
||||
);
|
||||
if (userGuideModule) {
|
||||
userGuideModule.inputs = [
|
||||
userGuideModule.inputs[0],
|
||||
{
|
||||
key: SystemInputEnum.variables,
|
||||
type: FlowInputItemTypeEnum.systemInput,
|
||||
type: FlowNodeInputTypeEnum.systemInput,
|
||||
label: '对话框变量',
|
||||
value: variable.inputs[0]?.value
|
||||
}
|
||||
@@ -66,7 +66,7 @@ async function initVariable(): Promise<any> {
|
||||
} else {
|
||||
modules.unshift({
|
||||
moduleId: 'userGuide',
|
||||
flowType: FlowModuleTypeEnum.userGuide,
|
||||
flowType: FlowNodeTypeEnum.userGuide,
|
||||
name: '用户引导',
|
||||
position: {
|
||||
x: 447.98520778293346,
|
||||
@@ -75,12 +75,12 @@ async function initVariable(): Promise<any> {
|
||||
inputs: [
|
||||
{
|
||||
key: SystemInputEnum.welcomeText,
|
||||
type: FlowInputItemTypeEnum.input,
|
||||
type: FlowNodeInputTypeEnum.input,
|
||||
label: '开场白'
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.variables,
|
||||
type: FlowInputItemTypeEnum.systemInput,
|
||||
type: FlowNodeInputTypeEnum.systemInput,
|
||||
label: '对话框变量',
|
||||
value: variable.inputs[0]?.value
|
||||
}
|
||||
@@ -90,7 +90,7 @@ async function initVariable(): Promise<any> {
|
||||
}
|
||||
|
||||
jsonAPP.modules = jsonAPP.modules.filter(
|
||||
(item) => item.flowType !== FlowModuleTypeEnum.variable
|
||||
(item) => item.flowType !== FlowNodeTypeEnum.variable
|
||||
);
|
||||
|
||||
app.modules = JSON.parse(JSON.stringify(jsonAPP.modules));
|
||||
|
||||
@@ -4,7 +4,7 @@ import { App, connectToDatabase } from '@/service/mongo';
|
||||
import { PgClient } from '@/service/pg';
|
||||
import { connectionMongo } from '@fastgpt/service/common/mongo';
|
||||
import { PgDatasetTableName } from '@/constants/plugin';
|
||||
import { FlowModuleTypeEnum } from '@/constants/flow';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { delay } from '@/utils/tools';
|
||||
import { MongoDatasetCollection } from '@fastgpt/service/core/dataset/collection/schema';
|
||||
import { DatasetCollectionTypeEnum } from '@fastgpt/global/core/dataset/constant';
|
||||
@@ -98,7 +98,7 @@ async function initMongo(limit: number) {
|
||||
let success = 0;
|
||||
|
||||
async function initApp(limit = 100): Promise<any> {
|
||||
// 遍历所有 app,更新 app modules 里的 FlowModuleTypeEnum.kbSearchNode
|
||||
// 遍历所有 app,更新 app modules 里的 FlowNodeTypeEnum.kbSearchNode
|
||||
const apps = await App.find({ inited: false }).limit(limit);
|
||||
|
||||
if (apps.length === 0) return;
|
||||
@@ -113,7 +113,7 @@ async function initMongo(limit: number) {
|
||||
modules.forEach((module) => {
|
||||
// @ts-ignore
|
||||
if (module.flowType === 'kbSearchNode') {
|
||||
module.flowType = FlowModuleTypeEnum.datasetSearchNode;
|
||||
module.flowType = FlowNodeTypeEnum.datasetSearchNode;
|
||||
module.inputs.forEach((input) => {
|
||||
if (input.key === 'kbList') {
|
||||
input.key = 'datasets';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { Chat, App, connectToDatabase, Collection } from '@/service/mongo';
|
||||
import { Chat, App, connectToDatabase } from '@/service/mongo';
|
||||
import { MongoOutLink } from '@fastgpt/service/support/outLink/schema';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { authApp } from '@/service/utils/auth';
|
||||
@@ -29,11 +29,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
appId
|
||||
});
|
||||
|
||||
// 删除收藏列表
|
||||
await Collection.deleteMany({
|
||||
modelId: appId
|
||||
});
|
||||
|
||||
// 删除分享链接
|
||||
await MongoOutLink.deleteMany({
|
||||
appId
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Collection, App } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
|
||||
/* 模型收藏切换 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { appId } = req.query as { appId: string };
|
||||
|
||||
if (!appId) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
// 凭证校验
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
const collectionRecord = await Collection.findOne({
|
||||
userId,
|
||||
modelId: appId
|
||||
});
|
||||
|
||||
if (collectionRecord) {
|
||||
await Collection.findByIdAndRemove(collectionRecord._id);
|
||||
} else {
|
||||
await Collection.create({
|
||||
userId,
|
||||
modelId: appId
|
||||
});
|
||||
}
|
||||
|
||||
await App.findByIdAndUpdate(appId, {
|
||||
'share.collection': await Collection.countDocuments({ modelId: appId })
|
||||
});
|
||||
|
||||
jsonRes(res);
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, App } from '@/service/mongo';
|
||||
import type { PagingData } from '@/types';
|
||||
import type { ShareAppItem } from '@/types/app';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { Types } from '@fastgpt/service/common/mongo';
|
||||
|
||||
/* 获取模型列表 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const {
|
||||
searchText = '',
|
||||
pageNum = 1,
|
||||
pageSize = 20
|
||||
} = req.body as { searchText: string; pageNum: number; pageSize: number };
|
||||
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
const regex = new RegExp(searchText, 'i');
|
||||
|
||||
const where = {
|
||||
$and: [
|
||||
{ 'share.isShare': true },
|
||||
{
|
||||
$or: [{ name: { $regex: regex } }, { intro: { $regex: regex } }]
|
||||
}
|
||||
]
|
||||
};
|
||||
const pipeline = [
|
||||
{
|
||||
$match: where
|
||||
},
|
||||
{
|
||||
$lookup: {
|
||||
from: 'collections',
|
||||
let: { modelId: '$_id' },
|
||||
pipeline: [
|
||||
{
|
||||
$match: {
|
||||
$expr: {
|
||||
$and: [
|
||||
{ $eq: ['$modelId', '$$modelId'] },
|
||||
{
|
||||
$eq: ['$userId', userId ? new Types.ObjectId(userId) : new Types.ObjectId()]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
as: 'collections'
|
||||
}
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
_id: 1,
|
||||
avatar: { $ifNull: ['$avatar', '/icon/logo.svg'] },
|
||||
name: 1,
|
||||
userId: 1,
|
||||
intro: 1,
|
||||
share: 1,
|
||||
isCollection: {
|
||||
$cond: {
|
||||
if: { $gt: [{ $size: '$collections' }, 0] },
|
||||
then: true,
|
||||
else: false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
$sort: { 'share.topNum': -1, 'share.collection': -1 }
|
||||
},
|
||||
{
|
||||
$skip: (pageNum - 1) * pageSize
|
||||
},
|
||||
{
|
||||
$limit: pageSize
|
||||
}
|
||||
];
|
||||
|
||||
// 获取被分享的模型
|
||||
const [models, total] = await Promise.all([
|
||||
// @ts-ignore
|
||||
App.aggregate(pipeline),
|
||||
App.countDocuments(where)
|
||||
]);
|
||||
|
||||
jsonRes<PagingData<ShareAppItem>>(res, {
|
||||
data: {
|
||||
pageNum,
|
||||
pageSize,
|
||||
data: models,
|
||||
total
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { sseErrRes } from '@/service/response';
|
||||
import { sseResponseEventEnum } from '@/constants/chat';
|
||||
import { responseWrite } from '@fastgpt/service/common/response';
|
||||
import { AppModuleItemType } from '@/types/app';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type.d';
|
||||
import { dispatchModules } from '@/pages/api/v1/chat/completions';
|
||||
import { pushChatBill } from '@/service/common/bill/push';
|
||||
import { BillSourceEnum } from '@/constants/user';
|
||||
@@ -13,7 +13,7 @@ import { ChatItemType } from '@/types/chat';
|
||||
export type Props = {
|
||||
history: ChatItemType[];
|
||||
prompt: string;
|
||||
modules: AppModuleItemType[];
|
||||
modules: ModuleItemType[];
|
||||
variables: Record<string, any>;
|
||||
appId: string;
|
||||
appName: string;
|
||||
|
||||
24
projects/app/src/pages/api/core/plugin/create.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { createOnePlugin } from '@fastgpt/service/core/plugin/controller';
|
||||
import type { CreateOnePluginParams } from '@fastgpt/global/core/plugin/controller';
|
||||
import { defaultModules } from '@fastgpt/global/core/plugin/constants';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
const body = req.body as CreateOnePluginParams;
|
||||
|
||||
jsonRes(res, {
|
||||
data: await createOnePlugin({ userId, modules: defaultModules, ...body })
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
22
projects/app/src/pages/api/core/plugin/delete.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { deleteOnePlugin } from '@fastgpt/service/core/plugin/controller';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { id } = req.query as { id: string };
|
||||
await connectToDatabase();
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
jsonRes(res, {
|
||||
data: await deleteOnePlugin({ id, userId })
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
22
projects/app/src/pages/api/core/plugin/detail.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { getOnePluginDetail } from '@fastgpt/service/core/plugin/controller';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { id } = req.query as { id: string };
|
||||
await connectToDatabase();
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
jsonRes(res, {
|
||||
data: await getOnePluginDetail({ id, userId })
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
21
projects/app/src/pages/api/core/plugin/list.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { getUserPlugins } from '@fastgpt/service/core/plugin/controller';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
jsonRes(res, {
|
||||
data: await getUserPlugins({ userId })
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
22
projects/app/src/pages/api/core/plugin/moduleDetail.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { getPluginModuleDetail } from '@fastgpt/service/core/plugin/controller';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { id } = req.query as { id: string };
|
||||
await connectToDatabase();
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
jsonRes(res, {
|
||||
data: await getPluginModuleDetail({ id, userId })
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
21
projects/app/src/pages/api/core/plugin/templateList.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { getUserPlugins2Templates } from '@fastgpt/service/core/plugin/controller';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
jsonRes(res, {
|
||||
data: await getUserPlugins2Templates({ userId })
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
23
projects/app/src/pages/api/core/plugin/update.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { updateOnePlugin } from '@fastgpt/service/core/plugin/controller';
|
||||
import type { UpdatePluginParams } from '@fastgpt/global/core/plugin/controller';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
const body = req.body as UpdatePluginParams;
|
||||
|
||||
jsonRes(res, {
|
||||
data: await updateOnePlugin({ userId, ...body })
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ type FileType = {
|
||||
/**
|
||||
* Creates the multer uploader
|
||||
*/
|
||||
const maxSize = 50 * 1024 * 1024;
|
||||
const maxSize = 500 * 1024 * 1024;
|
||||
class UploadModel {
|
||||
uploader = multer({
|
||||
limits: {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Image } from '@/service/mongo';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { uploadMongoImg } from '@fastgpt/service/common/file/image/controller';
|
||||
|
||||
type Props = { base64Img: string };
|
||||
|
||||
@@ -11,7 +12,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
const { base64Img } = req.body as Props;
|
||||
|
||||
const data = await uploadImg({
|
||||
const data = await uploadMongoImg({
|
||||
userId,
|
||||
base64Img
|
||||
});
|
||||
@@ -24,14 +25,3 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function uploadImg({ base64Img, userId }: Props & { userId: string }) {
|
||||
const base64Data = base64Img.split(',')[1];
|
||||
|
||||
const { _id } = await Image.create({
|
||||
userId,
|
||||
binary: Buffer.from(base64Data, 'base64')
|
||||
});
|
||||
|
||||
return `/api/system/img/${_id}`;
|
||||
}
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, Image } from '@/service/mongo';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { readMongoImg } from '@fastgpt/service/common/file/image/controller';
|
||||
|
||||
// get the models available to the system
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { id } = req.query;
|
||||
const { id } = req.query as { id: string };
|
||||
|
||||
const data = await Image.findById(id);
|
||||
|
||||
if (!data) {
|
||||
throw new Error('no image');
|
||||
}
|
||||
res.setHeader('Content-Type', 'image/jpeg');
|
||||
|
||||
res.send(data.binary);
|
||||
res.send(await readMongoImg({ id }));
|
||||
} catch (error) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { Pay, connectToDatabase } from '@/service/mongo';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoPay } from '@fastgpt/service/support/wallet/pay/schema';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
await connectToDatabase();
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
const records = await Pay.find({
|
||||
const records = await MongoPay.find({
|
||||
userId,
|
||||
status: { $ne: 'CLOSED' }
|
||||
})
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { Inform, connectToDatabase } from '@/service/mongo';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoUserInform } from '@fastgpt/service/support/user/inform/schema';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
@@ -14,7 +15,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
}
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
const data = await Inform.countDocuments({
|
||||
const data = await MongoUserInform.countDocuments({
|
||||
userId,
|
||||
read: false
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { Inform, connectToDatabase } from '@/service/mongo';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { MongoUserInform } from '@fastgpt/service/support/user/inform/schema';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
@@ -15,11 +16,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
};
|
||||
|
||||
const [informs, total] = await Promise.all([
|
||||
Inform.find({ userId })
|
||||
MongoUserInform.find({ userId })
|
||||
.sort({ time: -1 }) // 按照创建时间倒序排列
|
||||
.skip((pageNum - 1) * pageSize)
|
||||
.limit(pageSize),
|
||||
Inform.countDocuments({ userId })
|
||||
MongoUserInform.countDocuments({ userId })
|
||||
]);
|
||||
|
||||
jsonRes(res, {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { Inform, connectToDatabase } from '@/service/mongo';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { MongoUserInform } from '@fastgpt/service/support/user/inform/schema';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
@@ -11,7 +12,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
const { id } = req.query as { id: string };
|
||||
|
||||
await Inform.findOneAndUpdate(
|
||||
await MongoUserInform.findOneAndUpdate(
|
||||
{
|
||||
_id: id,
|
||||
userId
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { Inform, connectToDatabase } from '@/service/mongo';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { InformTypeEnum } from '@/constants/user';
|
||||
import { startSendInform } from '@/service/events/sendInform';
|
||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||
import { MongoUserInform } from '@fastgpt/service/support/user/inform/schema';
|
||||
import { InformTypeEnum } from '@fastgpt/global/support/user/constant';
|
||||
import {
|
||||
sendInform2AllUser,
|
||||
sendInform2OneUser
|
||||
} from '@fastgpt/service/support/user/inform/controller';
|
||||
|
||||
export type Props = {
|
||||
type: `${InformTypeEnum}`;
|
||||
@@ -38,39 +42,13 @@ export async function sendInform({ type, title, content, userId }: Props) {
|
||||
|
||||
try {
|
||||
if (userId) {
|
||||
global.sendInformQueue.push(async () => {
|
||||
// skip it if have same inform within 5 minutes
|
||||
const inform = await Inform.findOne({
|
||||
type,
|
||||
title,
|
||||
content,
|
||||
userId,
|
||||
time: { $gte: new Date(Date.now() - 5 * 60 * 1000) }
|
||||
});
|
||||
|
||||
if (inform) return;
|
||||
|
||||
await Inform.create({
|
||||
type,
|
||||
title,
|
||||
content,
|
||||
userId
|
||||
});
|
||||
});
|
||||
global.sendInformQueue.push(async () => sendInform2OneUser({ type, title, content, userId }));
|
||||
startSendInform();
|
||||
return;
|
||||
}
|
||||
|
||||
// send to all user
|
||||
const users = await MongoUser.find({}, '_id');
|
||||
await Inform.insertMany(
|
||||
users.map(({ _id }) => ({
|
||||
type,
|
||||
title,
|
||||
content,
|
||||
userId: _id
|
||||
}))
|
||||
);
|
||||
sendInform2AllUser({ type, title, content });
|
||||
} catch (error) {
|
||||
console.log('send inform error', error);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, promotionRecord } from '@/service/mongo';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { MongoPromotionRecord } from '@fastgpt/service/support/activity/promotion/schema';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import mongoose from '@fastgpt/service/common/mongo';
|
||||
import { MongoUser } from '@fastgpt/service/support/user/schema';
|
||||
@@ -16,7 +17,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
});
|
||||
|
||||
// 计算累计合
|
||||
const countHistory: { totalAmount: number }[] = await promotionRecord.aggregate([
|
||||
const countHistory: { totalAmount: number }[] = await MongoPromotionRecord.aggregate([
|
||||
{
|
||||
$match: {
|
||||
userId: new mongoose.Types.ObjectId(userId),
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { connectToDatabase, promotionRecord } from '@/service/mongo';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser } from '@fastgpt/service/support/user/auth';
|
||||
import { MongoPromotionRecord } from '@fastgpt/service/support/activity/promotion/schema';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
@@ -13,13 +14,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
const data = await promotionRecord
|
||||
.find(
|
||||
{
|
||||
userId
|
||||
},
|
||||
'_id createTime type amount'
|
||||
)
|
||||
const data = await MongoPromotionRecord.find(
|
||||
{
|
||||
userId
|
||||
},
|
||||
'_id createTime type amount'
|
||||
)
|
||||
.sort({ _id: -1 })
|
||||
.skip((pageNum - 1) * pageSize)
|
||||
.limit(pageSize);
|
||||
@@ -29,7 +29,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
pageNum,
|
||||
pageSize,
|
||||
data,
|
||||
total: await promotionRecord.countDocuments({
|
||||
total: await MongoPromotionRecord.countDocuments({
|
||||
userId
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,12 +9,15 @@ import {
|
||||
dispatchHistory,
|
||||
dispatchChatInput,
|
||||
dispatchChatCompletion,
|
||||
dispatchKBSearch,
|
||||
dispatchDatasetSearch,
|
||||
dispatchAnswer,
|
||||
dispatchClassifyQuestion,
|
||||
dispatchContentExtract,
|
||||
dispatchHttpRequest,
|
||||
dispatchAppRequest
|
||||
dispatchAppRequest,
|
||||
dispatchRunPlugin,
|
||||
dispatchPluginInput,
|
||||
dispatchPluginOutput
|
||||
} from '@/service/moduleDispatch';
|
||||
import type { CreateChatCompletionRequest } from '@fastgpt/global/core/ai/type.d';
|
||||
import type { MessageItemType } from '@/types/core/chat/type';
|
||||
@@ -23,8 +26,10 @@ import { getChatHistory } from './getHistory';
|
||||
import { saveChat } from '@/service/utils/chat/saveChat';
|
||||
import { responseWrite } from '@fastgpt/service/common/response';
|
||||
import { TaskResponseKeyEnum } from '@/constants/chat';
|
||||
import { FlowModuleTypeEnum, initModuleType } from '@/constants/flow';
|
||||
import { AppModuleItemType, RunningModuleItemType } from '@/types/app';
|
||||
import { initModuleType } from '@/constants/flow';
|
||||
import { FlowNodeTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { RunningModuleItemType } from '@/types/app';
|
||||
import type { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { pushChatBill } from '@/service/common/bill/push';
|
||||
import { BillSourceEnum } from '@/constants/user';
|
||||
import { ChatHistoryItemResType } from '@/types/chat';
|
||||
@@ -305,7 +310,7 @@ export async function dispatchModules({
|
||||
detail = false
|
||||
}: {
|
||||
res: NextApiResponse;
|
||||
modules: AppModuleItemType[];
|
||||
modules: ModuleItemType[];
|
||||
user: UserModelSchema;
|
||||
params?: Record<string, any>;
|
||||
variables?: Record<string, any>;
|
||||
@@ -425,7 +430,6 @@ export async function dispatchModules({
|
||||
stream,
|
||||
detail,
|
||||
variables,
|
||||
moduleName: module.name,
|
||||
outputs: module.outputs,
|
||||
user,
|
||||
inputs: params
|
||||
@@ -433,15 +437,18 @@ export async function dispatchModules({
|
||||
|
||||
const dispatchRes: Record<string, any> = await (async () => {
|
||||
const callbackMap: Record<string, Function> = {
|
||||
[FlowModuleTypeEnum.historyNode]: dispatchHistory,
|
||||
[FlowModuleTypeEnum.questionInput]: dispatchChatInput,
|
||||
[FlowModuleTypeEnum.answerNode]: dispatchAnswer,
|
||||
[FlowModuleTypeEnum.chatNode]: dispatchChatCompletion,
|
||||
[FlowModuleTypeEnum.datasetSearchNode]: dispatchKBSearch,
|
||||
[FlowModuleTypeEnum.classifyQuestion]: dispatchClassifyQuestion,
|
||||
[FlowModuleTypeEnum.contentExtract]: dispatchContentExtract,
|
||||
[FlowModuleTypeEnum.httpRequest]: dispatchHttpRequest,
|
||||
[FlowModuleTypeEnum.app]: dispatchAppRequest
|
||||
[FlowNodeTypeEnum.historyNode]: dispatchHistory,
|
||||
[FlowNodeTypeEnum.questionInput]: dispatchChatInput,
|
||||
[FlowNodeTypeEnum.answerNode]: dispatchAnswer,
|
||||
[FlowNodeTypeEnum.chatNode]: dispatchChatCompletion,
|
||||
[FlowNodeTypeEnum.datasetSearchNode]: dispatchDatasetSearch,
|
||||
[FlowNodeTypeEnum.classifyQuestion]: dispatchClassifyQuestion,
|
||||
[FlowNodeTypeEnum.contentExtract]: dispatchContentExtract,
|
||||
[FlowNodeTypeEnum.httpRequest]: dispatchHttpRequest,
|
||||
[FlowNodeTypeEnum.runApp]: dispatchAppRequest,
|
||||
[FlowNodeTypeEnum.pluginModule]: dispatchRunPlugin,
|
||||
[FlowNodeTypeEnum.pluginInput]: dispatchPluginInput,
|
||||
[FlowNodeTypeEnum.pluginOutput]: dispatchPluginOutput
|
||||
};
|
||||
if (callbackMap[module.flowType]) {
|
||||
return callbackMap[module.flowType](props);
|
||||
@@ -449,9 +456,21 @@ export async function dispatchModules({
|
||||
return {};
|
||||
})();
|
||||
|
||||
const formatResponseData = (() => {
|
||||
if (!dispatchRes[TaskResponseKeyEnum.responseData]) return undefined;
|
||||
if (Array.isArray(dispatchRes[TaskResponseKeyEnum.responseData]))
|
||||
return dispatchRes[TaskResponseKeyEnum.responseData];
|
||||
return {
|
||||
...dispatchRes[TaskResponseKeyEnum.responseData],
|
||||
moduleName: module.name,
|
||||
moduleType: module.flowType
|
||||
};
|
||||
})();
|
||||
|
||||
return moduleOutput(module, {
|
||||
[SystemOutputEnum.finish]: true,
|
||||
...dispatchRes
|
||||
...dispatchRes,
|
||||
[TaskResponseKeyEnum.responseData]: formatResponseData
|
||||
});
|
||||
}
|
||||
|
||||
@@ -468,7 +487,7 @@ export async function dispatchModules({
|
||||
|
||||
/* init store modules to running modules */
|
||||
function loadModules(
|
||||
modules: AppModuleItemType[],
|
||||
modules: ModuleItemType[],
|
||||
variables: Record<string, any>
|
||||
): RunningModuleItemType[] {
|
||||
return modules.map((module) => {
|
||||
@@ -495,12 +514,19 @@ function loadModules(
|
||||
value: replacedVal
|
||||
};
|
||||
}),
|
||||
outputs: module.outputs.map((item) => ({
|
||||
key: item.key,
|
||||
answer: item.key === TaskResponseKeyEnum.answerText,
|
||||
value: undefined,
|
||||
targets: item.targets
|
||||
}))
|
||||
outputs: module.outputs
|
||||
.map((item) => ({
|
||||
key: item.key,
|
||||
answer: item.key === TaskResponseKeyEnum.answerText,
|
||||
value: undefined,
|
||||
targets: item.targets
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
// finish output always at last
|
||||
if (a.key === SystemOutputEnum.finish) return 1;
|
||||
if (b.key === SystemOutputEnum.finish) return -1;
|
||||
return 0;
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react';
|
||||
import { SmallCloseIcon } from '@chakra-ui/icons';
|
||||
import { FlowInputItemTypeEnum } from '@/constants/flow';
|
||||
import { FlowOutputTargetItemType } from '@/types/core/app/flow';
|
||||
import { AppModuleItemType } from '@/types/app';
|
||||
import { FlowNodeInputTypeEnum } from '@fastgpt/global/core/module/node/constant';
|
||||
import { FlowNodeOutputTargetItemType } from '@fastgpt/global/core/module/node/type';
|
||||
import { ModuleItemType } from '@fastgpt/global/core/module/type';
|
||||
import { useRequest } from '@/web/common/hooks/useRequest';
|
||||
import type { AppSchema } from '@/types/mongoSchema';
|
||||
import { useUserStore } from '@/web/support/user/useUserStore';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useCopyData } from '@/web/common/hooks/useCopyData';
|
||||
import { AppTypeEnum, SystemOutputEnum } from '@/constants/app';
|
||||
import { AppTypeEnum } from '@/constants/app';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
import MyIcon from '@/components/Icon';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
import ChatTest, { type ChatTestComponentRef } from './ChatTest';
|
||||
import { useFlowStore } from './Provider';
|
||||
import ChatTest, { type ChatTestComponentRef } from '@/components/core/module/Flow/ChatTest';
|
||||
import { flowNode2Modules, useFlowProviderStore } from '@/components/core/module/Flow/FlowProvider';
|
||||
|
||||
const ImportSettings = dynamic(() => import('./ImportSettings'));
|
||||
const ImportSettings = dynamic(() => import('@/components/core/module/Flow/ImportSettings'));
|
||||
|
||||
type Props = { app: AppSchema; onCloseSettings: () => void };
|
||||
type Props = { app: AppSchema; onClose: () => void };
|
||||
|
||||
const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
app,
|
||||
ChatTestRef,
|
||||
testModules,
|
||||
setTestModules,
|
||||
onCloseSettings
|
||||
onClose
|
||||
}: Props & {
|
||||
ChatTestRef: React.RefObject<ChatTestComponentRef>;
|
||||
testModules?: AppModuleItemType[];
|
||||
setTestModules: React.Dispatch<AppModuleItemType[] | undefined>;
|
||||
testModules?: ModuleItemType[];
|
||||
setTestModules: React.Dispatch<ModuleItemType[] | undefined>;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
@@ -38,54 +38,11 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
const { isOpen: isOpenImport, onOpen: onOpenImport, onClose: onCloseImport } = useDisclosure();
|
||||
const { updateAppDetail } = useUserStore();
|
||||
|
||||
const { nodes, edges, onFixView } = useFlowStore();
|
||||
|
||||
const flow2AppModules = useCallback(() => {
|
||||
const modules: AppModuleItemType[] = nodes.map((item) => ({
|
||||
moduleId: item.data.moduleId,
|
||||
name: item.data.name,
|
||||
flowType: item.data.flowType,
|
||||
showStatus: item.data.showStatus,
|
||||
position: item.position,
|
||||
inputs: item.data.inputs.map((item) => ({
|
||||
...item,
|
||||
connected: item.connected ?? item.type !== FlowInputItemTypeEnum.target
|
||||
})),
|
||||
outputs: item.data.outputs.map((item) => ({
|
||||
...item,
|
||||
targets: [] as FlowOutputTargetItemType[]
|
||||
}))
|
||||
}));
|
||||
|
||||
// update inputs and outputs
|
||||
modules.forEach((module) => {
|
||||
module.inputs.forEach((input) => {
|
||||
input.connected =
|
||||
input.connected ||
|
||||
!!edges.find(
|
||||
(edge) => edge.target === module.moduleId && edge.targetHandle === input.key
|
||||
);
|
||||
});
|
||||
module.outputs.forEach((output) => {
|
||||
output.targets = edges
|
||||
.filter(
|
||||
(edge) =>
|
||||
edge.source === module.moduleId &&
|
||||
edge.sourceHandle === output.key &&
|
||||
edge.targetHandle
|
||||
)
|
||||
.map((edge) => ({
|
||||
moduleId: edge.target,
|
||||
key: edge.targetHandle || ''
|
||||
}));
|
||||
});
|
||||
});
|
||||
return modules;
|
||||
}, [edges, nodes]);
|
||||
const { nodes, edges, onFixView } = useFlowProviderStore();
|
||||
|
||||
const { mutate: onclickSave, isLoading } = useRequest({
|
||||
mutationFn: () => {
|
||||
const modules = flow2AppModules();
|
||||
const modules = flowNode2Modules({ nodes, edges });
|
||||
// check required connect
|
||||
for (let i = 0; i < modules.length; i++) {
|
||||
const item = modules[i];
|
||||
@@ -127,7 +84,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
variant={'base'}
|
||||
aria-label={''}
|
||||
onClick={() => {
|
||||
onCloseSettings();
|
||||
onClose();
|
||||
onFixView();
|
||||
}}
|
||||
/>
|
||||
@@ -155,7 +112,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
aria-label={'save'}
|
||||
onClick={() =>
|
||||
copyData(
|
||||
JSON.stringify(flow2AppModules(), null, 2),
|
||||
JSON.stringify(flowNode2Modules({ nodes, edges }), null, 2),
|
||||
t('app.Export Config Successful')
|
||||
)
|
||||
}
|
||||
@@ -181,7 +138,7 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
|
||||
aria-label={'save'}
|
||||
variant={'base'}
|
||||
onClick={() => {
|
||||
setTestModules(flow2AppModules());
|
||||
setTestModules(flowNode2Modules({ nodes, edges }));
|
||||
}}
|
||||
/>
|
||||
</MyTooltip>
|
||||
@@ -206,7 +163,7 @@ const Header = (props: Props) => {
|
||||
const { app } = props;
|
||||
const ChatTestRef = useRef<ChatTestComponentRef>(null);
|
||||
|
||||
const [testModules, setTestModules] = useState<AppModuleItemType[]>();
|
||||
const [testModules, setTestModules] = useState<ModuleItemType[]>();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeCard from '../modules/NodeCard';
|
||||
import { FlowModuleItemType } from '@/types/core/app/flow';
|
||||
import Divider from '../modules/Divider';
|
||||
import Container from '../modules/Container';
|
||||
import RenderInput from '../render/RenderInput';
|
||||
import RenderOutput from '../render/RenderOutput';
|
||||
|
||||
const NodeHistory = ({ data }: NodeProps<FlowModuleItemType>) => {
|
||||
const { inputs, outputs, moduleId } = data;
|
||||
return (
|
||||
<NodeCard minW={'300px'} {...data}>
|
||||
<Divider text="Input" />
|
||||
<Container>
|
||||
<RenderInput moduleId={moduleId} flowInputList={inputs} />
|
||||
</Container>
|
||||
<Divider text="Output" />
|
||||
<Container>
|
||||
<RenderOutput moduleId={moduleId} flowOutputList={outputs} />
|
||||
</Container>
|
||||
</NodeCard>
|
||||
);
|
||||
};
|
||||
export default React.memo(NodeHistory);
|
||||