4.8.8 test fix (#2149)

* perf: code comment

* feat: system plugin input guide

* perf: variable avatar

* feat: feishu webhook

* perf: select tool config tip

* perf: rename variable

* fix: per inherit error

* perf: docker-compose oneapi version and i18n

* perf: ui tip bug

* fix: ts

* perf: pg log

* perf: editor color

* perf: update init
This commit is contained in:
Archer
2024-07-26 10:23:44 +08:00
committed by GitHub
parent 2d016b7462
commit cd554f573e
59 changed files with 1648 additions and 506 deletions

View File

@@ -7,7 +7,7 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build-fastgpt-images: preview-fastgpt-images:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout - name: Checkout

View File

@@ -16,19 +16,35 @@ weight: 816
- fastgpt 镜像 tag 修改成 v4.8.8-alpha - fastgpt 镜像 tag 修改成 v4.8.8-alpha
- 商业版镜像 tag 修改成 v4.8.8-alpha - 商业版镜像 tag 修改成 v4.8.8-alpha
### 3. 执行初始化
从任意终端,发起 1 个 HTTP 请求。其中 {{rootkey}} 替换成环境变量里的 `rootkey`{{host}} 替换成**FastGPT 域名**。
```bash
curl --location --request POST 'https://{{host}}/api/admin/initv48 8' \
--header 'rootkey: {{rootkey}}' \
--header 'Content-Type: application/json'
```
会初始化知识库的继承权限
------- -------
## V4.8.8 更新说明 ## V4.8.8 更新说明
1. 新增 - 重构系统插件的结构。允许向开源社区 PR 系统插件,具体可见: [如何向 FastGPT 社区提交系统插件](https://fael3z0zfze.feishu.cn/wiki/ERZnw9R26iRRG0kXZRec6WL9nwh)。 1. 新增 - 重构系统插件的结构。允许向开源社区 PR 系统插件,具体可见: [如何向 FastGPT 社区提交系统插件](https://fael3z0zfze.feishu.cn/wiki/ERZnw9R26iRRG0kXZRec6WL9nwh)。
2. 新增 - DuckDuckGo 系统插件。 2. 新增 - DuckDuckGo 系统插件。
3. 新增 - 修改变量填写方式。提示词输入框以以及工作流中所有 Textarea 输入框,支持输入 / 唤起变量选择,可直接选择所有上游输出值,无需动态引入 3. 新增 - 飞书 webhook 系统插件
4. 优化 - 移动端快速切换应用交互 4. 新增 - 修改变量填写方式。提示词输入框以以及工作流中所有 Textarea 输入框,支持输入 / 唤起变量选择,可直接选择所有上游输出值,无需动态引入
5. 优化 - 节点图标 5. 商业版新增 - 知识库权限继承
6. 优化 - 对话框引用增加额外复制案件,便于复制。增加引用内容折叠 6. 优化 - 移动端快速切换应用交互
7. 优化 - 对话框底部增加复制,简便复制交互,无需滚动到消息开头 7. 优化 - 节点图标
8. 优化 - OpenAI sdk 升级,并自定义了 whisper 模型接口(未仔细查看 sdk 实现,但 sdk 中 whisper 接口,似乎无法适配一般 fastapi 接口) 8. 优化 - 对话框引用增加额外复制案件,便于复制。增加引用内容折叠。
9. 修复 - Permission 表声明问题 9. 优化 - 对话框底部增加复制,简便复制交互,无需滚动到消息开头
10. 修复 - 并行执行节点,运行时间未正确记录。 10. 优化 - OpenAI sdk 升级,并自定义了 whisper 模型接口(未仔细查看 sdk 实现,但 sdk 中 whisper 接口,似乎无法适配一般 fastapi 接口)
11. 修复 - 简易模式,首次进入,无法正确获取知识库配置 11. 修复 - Permission 表声明问题
12. 修复 - Log debug level 配置无效 12. 修复 - 并行执行节点,运行时间未正确记录
13. 修复 - 运行详情未正确展示嵌套节点信息。
14. 修复 - 简易模式,首次进入,无法正确获取知识库配置。
15. 修复 - Log debug level 配置无效。
16. 修复 - 插件独立运行时,会将插件输入的值进行变量替换,可能导致后续节点变量异常。

View File

@@ -179,7 +179,7 @@ services:
- ./mysql:/var/lib/mysql - ./mysql:/var/lib/mysql
oneapi: oneapi:
container_name: oneapi container_name: oneapi
image: ghcr.io/songquanpeng/one-api:latest image: ghcr.io/songquanpeng/one-api:0.6.7
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/one-api:v0.6.6 # 阿里云 # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/one-api:v0.6.6 # 阿里云
ports: ports:
- 3001:3000 - 3001:3000

View File

@@ -136,7 +136,7 @@ services:
- ./mysql:/var/lib/mysql - ./mysql:/var/lib/mysql
oneapi: oneapi:
container_name: oneapi container_name: oneapi
image: ghcr.io/songquanpeng/one-api:latest image: ghcr.io/songquanpeng/one-api:0.6.7
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/one-api:v0.6.6 # 阿里云 # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/one-api:v0.6.6 # 阿里云
ports: ports:
- 3001:3000 - 3001:3000

View File

@@ -117,7 +117,7 @@ services:
- ./mysql:/var/lib/mysql - ./mysql:/var/lib/mysql
oneapi: oneapi:
container_name: oneapi container_name: oneapi
image: ghcr.io/songquanpeng/one-api:latest image: ghcr.io/songquanpeng/one-api:0.6.7
# image: registry.cn-hangzhou.aliyuncs.com/fastgpt/one-api:v0.6.6 # 阿里云 # image: registry.cn-hangzhou.aliyuncs.com/fastgpt/one-api:v0.6.6 # 阿里云
ports: ports:
- 3001:3000 - 3001:3000

View File

@@ -6,27 +6,29 @@ import { getAppChatConfig } from '../workflow/utils';
import { StoreNodeItemType } from '../workflow/type/node'; import { StoreNodeItemType } from '../workflow/type/node';
import { DatasetSearchModeEnum } from '../dataset/constants'; import { DatasetSearchModeEnum } from '../dataset/constants';
export const getDefaultAppForm = (): AppSimpleEditFormType => ({ export const getDefaultAppForm = (): AppSimpleEditFormType => {
aiSettings: { return {
model: 'gpt-4o-mini', aiSettings: {
systemPrompt: '', model: 'gpt-4o-mini',
temperature: 0, systemPrompt: '',
isResponseAnswerText: true, temperature: 0,
maxHistories: 6, isResponseAnswerText: true,
maxToken: 4000 maxHistories: 6,
}, maxToken: 4000
dataset: { },
datasets: [], dataset: {
similarity: 0.4, datasets: [],
limit: 1500, similarity: 0.4,
searchMode: DatasetSearchModeEnum.embedding, limit: 1500,
usingReRank: false, searchMode: DatasetSearchModeEnum.embedding,
datasetSearchUsingExtensionQuery: false, usingReRank: false,
datasetSearchExtensionBg: '' datasetSearchUsingExtensionQuery: true,
}, datasetSearchExtensionBg: ''
selectedTools: [], },
chatConfig: {} selectedTools: [],
}); chatConfig: {}
};
};
/* format app nodes to edit form */ /* format app nodes to edit form */
export const appWorkflow2Form = ({ export const appWorkflow2Form = ({

View File

@@ -6,6 +6,7 @@ export enum FlowNodeTemplateTypeEnum {
search = 'search', search = 'search',
multimodal = 'multimodal', multimodal = 'multimodal',
communication = 'communication',
other = 'other', other = 'other',
teamApp = 'teamApp' teamApp = 'teamApp'

View File

@@ -35,6 +35,7 @@ export type WorkflowTemplateType = {
avatar: string; avatar: string;
intro?: string; intro?: string;
author?: string; author?: string;
inputExplanationUrl?: string;
version: string; version: string;
showStatus?: boolean; showStatus?: boolean;

View File

@@ -31,6 +31,7 @@ export type FlowNodeCommonType = {
avatar?: string; avatar?: string;
name: string; name: string;
intro?: string; // template list intro intro?: string; // template list intro
inputExplanationUrl?: string;
showStatus?: boolean; // chatting response step status showStatus?: boolean; // chatting response step status
version: string; version: string;

View File

@@ -229,7 +229,7 @@ export const updatePluginInputByVariables = (
); );
}; };
export const filterPluginInputVariables = ( export const removePluginInputVariables = (
variables: Record<string, any>, variables: Record<string, any>,
nodes: RuntimeNodeItemType[] nodes: RuntimeNodeItemType[]
) => { ) => {
@@ -268,6 +268,7 @@ export function replaceVariableLabel({
}; };
}); });
// Upstream node outputs
const nodeVariables = nodes const nodeVariables = nodes
.map((node) => { .map((node) => {
return node.outputs.map((output) => { return node.outputs.map((output) => {
@@ -280,6 +281,7 @@ export function replaceVariableLabel({
}) })
.flat(); .flat();
// Get runningNode inputs(Will be replaced with reference)
const customInputs = runningNode.inputs.flatMap((item) => { const customInputs = runningNode.inputs.flatMap((item) => {
if (Array.isArray(item.value)) { if (Array.isArray(item.value)) {
return [ return [
@@ -299,6 +301,7 @@ export function replaceVariableLabel({
const allVariables = [...globalVariables, ...nodeVariables, ...customInputs]; const allVariables = [...globalVariables, ...nodeVariables, ...customInputs];
// Replace {{$xxx.xxx$}} to value
for (const key in allVariables) { for (const key in allVariables) {
const val = allVariables[key]; const val = allVariables[key];
const regex = new RegExp(`\\{\\{\\$(${val.nodeId}\\.${val.id})\\$\\}\\}`, 'g'); const regex = new RegExp(`\\{\\{\\$(${val.nodeId}\\.${val.id})\\$\\}\\}`, 'g');

View File

@@ -7,7 +7,14 @@ import { cloneDeep } from 'lodash';
import { WorkerNameEnum, runWorker } from '@fastgpt/service/worker/utils'; import { WorkerNameEnum, runWorker } from '@fastgpt/service/worker/utils';
// Run in main thread // Run in main thread
const staticPluginList = ['getTime', 'fetchUrl', 'Doc2X', 'Doc2X/URLPDF2text', 'Doc2X/URLImg2text']; const staticPluginList = [
'getTime',
'fetchUrl',
'Doc2X',
'Doc2X/URLPDF2text',
'Doc2X/URLImg2text',
'feishu'
];
// Run in worker thread (Have npm packages) // Run in worker thread (Have npm packages)
const packagePluginList = [ const packagePluginList = [
'mathExprVal', 'mathExprVal',

View File

@@ -1,9 +1,10 @@
{ {
"author": "", "author": "Menghuan1918",
"version": "488", "version": "488",
"name": "Doc2X 图像(URL)识别", "name": "Doc2X 图像(URL)识别",
"avatar": "plugins/doc2x", "avatar": "plugins/doc2x",
"intro": "将传入的图片(URL)发送至Doc2X进行解析返回带LaTeX公式的markdown格式的文本", "intro": "将传入的图片(URL)发送至Doc2X进行解析返回带LaTeX公式的markdown格式的文本",
"inputExplanationUrl": "https://fael3z0zfze.feishu.cn/wiki/Rkc5witXWiJoi5kORd2cofh6nDg?fromScene=spaceOverview",
"showStatus": true, "showStatus": true,
"weight": 10, "weight": 10,

View File

@@ -1,9 +1,10 @@
{ {
"author": "", "author": "Menghuan1918",
"version": "488", "version": "488",
"name": "Doc2X PDF文件(URL)识别", "name": "Doc2X PDF文件(URL)识别",
"avatar": "plugins/doc2x", "avatar": "plugins/doc2x",
"intro": "将传入的PDF文件(URL)发送至Doc2X进行解析返回带LaTeX公式的markdown格式的文本", "intro": "将传入的PDF文件(URL)发送至Doc2X进行解析返回带LaTeX公式的markdown格式的文本",
"inputExplanationUrl": "https://fael3z0zfze.feishu.cn/wiki/Rkc5witXWiJoi5kORd2cofh6nDg?fromScene=spaceOverview",
"showStatus": true, "showStatus": true,
"weight": 10, "weight": 10,

View File

@@ -1,5 +1,5 @@
{ {
"author": "", "author": "Menghuan1918",
"version": "488", "version": "488",
"name": "Doc2X服务", "name": "Doc2X服务",
"avatar": "plugins/doc2x", "avatar": "plugins/doc2x",

View File

@@ -5,7 +5,7 @@
"avatar": "core/workflow/template/duckduckgo", "avatar": "core/workflow/template/duckduckgo",
"intro": "DuckDuckGo 服务,包含网络搜索、图片搜索、新闻搜索等。", "intro": "DuckDuckGo 服务,包含网络搜索、图片搜索、新闻搜索等。",
"showStatus": false, "showStatus": false,
"weight": 10, "weight": 100,
"isTool": true, "isTool": true,
"templateType": "tools", "templateType": "tools",

View File

@@ -0,0 +1,443 @@
{
"author": "",
"version": "488",
"name": "飞书机器人 webhook",
"avatar": "/imgs/app/templates/feishu.svg",
"intro": "向飞书机器人发起 webhook 请求。",
"inputExplanationUrl": "https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot#f62e72d5",
"showStatus": false,
"weight": 10,
"isTool": true,
"templateType": "communication",
"workflow": {
"nodes": [
{
"nodeId": "pluginInput",
"name": "自定义插件输入",
"intro": "可以配置插件需要哪些输入,利用这些输入来运行插件",
"avatar": "core/workflow/template/workflowStart",
"flowNodeType": "pluginInput",
"showStatus": false,
"position": {
"x": 156.37657136084977,
"y": 90.73380846709256
},
"version": "481",
"inputs": [
{
"renderTypeList": ["reference"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "content",
"label": "content",
"description": "需要发送的消息",
"required": true,
"toolDescription": "需要发送的消息"
},
{
"renderTypeList": ["input"],
"selectedTypeIndex": 0,
"valueType": "string",
"canEdit": true,
"key": "hook_url",
"label": "hook_url",
"description": "飞书机器人地址",
"required": true,
"defaultValue": ""
}
],
"outputs": [
{
"id": "query",
"valueType": "string",
"key": "content",
"label": "content",
"type": "hidden"
},
{
"id": "hook_url",
"valueType": "string",
"key": "hook_url",
"label": "hook_url",
"type": "hidden"
}
]
},
{
"nodeId": "pluginOutput",
"name": "自定义插件输出",
"intro": "自定义配置外部输出,使用插件时,仅暴露自定义配置的输出",
"avatar": "core/workflow/template/pluginOutput",
"flowNodeType": "pluginOutput",
"showStatus": false,
"position": {
"x": 2110.7223589692912,
"y": 120.17602722162474
},
"version": "481",
"inputs": [
{
"renderTypeList": ["reference"],
"valueType": "object",
"canEdit": true,
"key": "result",
"label": "result",
"description": "",
"value": ["vzreK6vHrPvZ", "httpRawResponse"]
}
],
"outputs": []
},
{
"nodeId": "vzreK6vHrPvZ",
"name": "HTTP 请求",
"intro": "可以发出一个 HTTP 请求,实现更为复杂的操作(联网搜索、数据库查询等)",
"avatar": "core/workflow/template/httpRequest",
"flowNodeType": "httpRequest468",
"showStatus": true,
"position": {
"x": 1363.4233257919495,
"y": -182.3490463845037
},
"version": "481",
"inputs": [
{
"key": "system_addInputParam",
"renderTypeList": ["addInputParam"],
"valueType": "dynamic",
"label": "",
"required": false,
"description": "core.module.input.description.HTTP Dynamic Input",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
}
},
{
"key": "system_httpMethod",
"renderTypeList": ["custom"],
"valueType": "string",
"label": "",
"value": "POST",
"required": true
},
{
"key": "system_httpReqUrl",
"renderTypeList": ["hidden"],
"valueType": "string",
"label": "",
"description": "core.module.input.description.Http Request Url",
"placeholder": "https://api.ai.com/getInventory",
"required": false,
"value": "{{url}}"
},
{
"key": "system_httpHeader",
"renderTypeList": ["custom"],
"valueType": "any",
"value": [],
"label": "",
"description": "core.module.input.description.Http Request Header",
"placeholder": "core.module.input.description.Http Request Header",
"required": false
},
{
"key": "system_httpParams",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": [],
"label": "",
"required": false
},
{
"key": "system_httpJsonBody",
"renderTypeList": ["hidden"],
"valueType": "any",
"value": "{{content}}",
"label": "",
"required": false
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "url",
"label": "url",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "hook_url"]
},
{
"renderTypeList": ["reference"],
"valueType": "object",
"canEdit": true,
"key": "content",
"label": "content",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["qcJpBBVtXsGd", "system_rawResponse"]
}
],
"outputs": [
{
"id": "error",
"key": "error",
"label": "请求错误",
"description": "HTTP请求错误信息成功时返回空",
"valueType": "object",
"type": "static"
},
{
"id": "httpRawResponse",
"key": "httpRawResponse",
"label": "原始响应",
"required": true,
"description": "HTTP请求的原始响应。只能接受字符串或JSON类型响应数据。",
"valueType": "any",
"type": "static"
},
{
"id": "system_addOutputParam",
"key": "system_addOutputParam",
"type": "dynamic",
"valueType": "dynamic",
"label": "",
"customFieldConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": false
}
}
]
},
{
"nodeId": "qcJpBBVtXsGd",
"name": "代码运行",
"intro": "执行一段简单的脚本代码,通常用于进行复杂的数据处理。",
"avatar": "core/workflow/template/codeRun",
"flowNodeType": "code",
"showStatus": true,
"position": {
"x": 805.8169457909617,
"y": -159.52218926716316
},
"version": "482",
"inputs": [
{
"key": "system_addInputParam",
"renderTypeList": ["addInputParam"],
"valueType": "dynamic",
"label": "",
"required": false,
"description": "这些变量会作为代码的运行的输入参数",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
}
},
{
"key": "codeType",
"renderTypeList": ["hidden"],
"label": "",
"value": "js"
},
{
"key": "code",
"renderTypeList": ["custom"],
"label": "",
"value": "function main({data1}){\n try{\n const parseData = JSON.parse(data1)\n if(typeof parseData === 'object') {\n return parseData\n }\n return {\n \"msg_type\": \"text\",\n content: {\n \"text\": data1\n }\n }\n } catch(err) {\n return {\n \"msg_type\": \"text\",\n content: {\n \"text\": data1\n }\n }\n }\n}"
},
{
"renderTypeList": ["reference"],
"valueType": "string",
"canEdit": true,
"key": "data1",
"label": "data1",
"customInputConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": true
},
"required": true,
"value": ["pluginInput", "query"]
}
],
"outputs": [
{
"id": "system_rawResponse",
"key": "system_rawResponse",
"label": "完整响应数据",
"valueType": "object",
"type": "static"
},
{
"id": "error",
"key": "error",
"label": "运行错误",
"description": "代码运行错误信息,成功时返回空",
"valueType": "object",
"type": "static"
},
{
"id": "system_addOutputParam",
"key": "system_addOutputParam",
"type": "dynamic",
"valueType": "dynamic",
"label": "",
"customFieldConfig": {
"selectValueTypeList": [
"string",
"number",
"boolean",
"object",
"arrayString",
"arrayNumber",
"arrayBoolean",
"arrayObject",
"any",
"chatHistory",
"datasetQuote",
"dynamic",
"selectApp",
"selectDataset"
],
"showDescription": false,
"showDefaultValue": false
},
"description": "将代码中 return 的对象作为输出,传递给后续的节点。变量名需要对应 return 的 key"
},
{
"id": "qLUQfhG0ILRX",
"type": "dynamic",
"key": "content",
"valueType": "object",
"label": "content"
}
]
}
],
"edges": [
{
"source": "vzreK6vHrPvZ",
"target": "pluginOutput",
"sourceHandle": "vzreK6vHrPvZ-source-right",
"targetHandle": "pluginOutput-target-left"
},
{
"source": "pluginInput",
"target": "qcJpBBVtXsGd",
"sourceHandle": "pluginInput-source-right",
"targetHandle": "qcJpBBVtXsGd-target-left"
},
{
"source": "qcJpBBVtXsGd",
"target": "vzreK6vHrPvZ",
"sourceHandle": "qcJpBBVtXsGd-source-right",
"targetHandle": "vzreK6vHrPvZ-target-left"
}
]
}
}

View File

@@ -47,8 +47,6 @@ const addCommonMiddleware = (schema: mongoose.Schema) => {
if (duration > 1000) { if (duration > 1000) {
addLog.warn(`Slow operation ${duration}ms`, warnLogData); addLog.warn(`Slow operation ${duration}ms`, warnLogData);
} else if (duration > 3000) {
addLog.error(`Slow operation ${duration}ms`, warnLogData);
} }
} }
next(); next();

View File

@@ -1,3 +1,4 @@
import { delay } from '@fastgpt/global/common/system/utils';
import { addLog } from '../system/log'; import { addLog } from '../system/log';
import { connectionMongo } from './index'; import { connectionMongo } from './index';
import type { Mongoose } from 'mongoose'; import type { Mongoose } from 'mongoose';
@@ -17,9 +18,11 @@ export async function connectMongo(): Promise<Mongoose> {
try { try {
connectionMongo.set('strictQuery', true); connectionMongo.set('strictQuery', true);
connectionMongo.connection.on('error', (error) => { connectionMongo.connection.on('error', async (error) => {
console.log('mongo error', error); console.log('mongo error', error);
connectionMongo.disconnect(); await connectionMongo.disconnect();
await delay(1000);
connectMongo();
}); });
connectionMongo.connection.on('disconnected', () => { connectionMongo.connection.on('disconnected', () => {
console.log('mongo disconnected'); console.log('mongo disconnected');
@@ -44,8 +47,10 @@ export async function connectMongo(): Promise<Mongoose> {
console.log('mongo connected'); console.log('mongo connected');
} catch (error) { } catch (error) {
connectionMongo.disconnect();
addLog.error('mongo connect error', error); addLog.error('mongo connect error', error);
await connectionMongo.disconnect();
await delay(1000);
connectMongo();
} }
return connectionMongo; return connectionMongo;

View File

@@ -10,6 +10,7 @@ import {
InsertVectorControllerProps InsertVectorControllerProps
} from '../controller.d'; } from '../controller.d';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { addLog } from '../../system/log';
export class PgVectorCtrl { export class PgVectorCtrl {
constructor() {} constructor() {}
@@ -38,9 +39,9 @@ export class PgVectorCtrl {
`CREATE INDEX CONCURRENTLY IF NOT EXISTS create_time_index ON ${DatasetVectorTableName} USING btree(createtime);` `CREATE INDEX CONCURRENTLY IF NOT EXISTS create_time_index ON ${DatasetVectorTableName} USING btree(createtime);`
); );
console.log('init pg successful'); addLog.info('init pg successful');
} catch (error) { } catch (error) {
console.log('init pg error', error); addLog.error('init pg error', error);
} }
}; };
insert = async (props: InsertVectorControllerProps): Promise<{ insertId: string }> => { insert = async (props: InsertVectorControllerProps): Promise<{ insertId: string }> => {

View File

@@ -82,6 +82,7 @@ export async function getPluginPreviewNode({ id }: { id: string }): Promise<Flow
avatar: plugin.avatar, avatar: plugin.avatar,
name: plugin.name, name: plugin.name,
intro: plugin.intro, intro: plugin.intro,
inputExplanationUrl: plugin.inputExplanationUrl,
showStatus: plugin.showStatus, showStatus: plugin.showStatus,
isTool: plugin.isTool, isTool: plugin.isTool,
version: plugin.version, version: plugin.version,

View File

@@ -262,13 +262,14 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
/* Inject data into module input */ /* Inject data into module input */
function getNodeRunParams(node: RuntimeNodeItemType) { function getNodeRunParams(node: RuntimeNodeItemType) {
if (node.flowNodeType === FlowNodeTypeEnum.pluginInput) { if (node.flowNodeType === FlowNodeTypeEnum.pluginInput) {
// Format plugin input to object
return node.inputs.reduce<Record<string, any>>((acc, item) => { return node.inputs.reduce<Record<string, any>>((acc, item) => {
acc[item.key] = valueTypeFormat(item.value, item.valueType); acc[item.key] = valueTypeFormat(item.value, item.valueType);
return acc; return acc;
}, {}); }, {});
} }
// common nodes // Dynamic input need to store a key.
const dynamicInput = node.inputs.find( const dynamicInput = node.inputs.find(
(item) => item.renderTypeList[0] === FlowNodeInputTypeEnum.addInputParam (item) => item.renderTypeList[0] === FlowNodeInputTypeEnum.addInputParam
); );
@@ -281,14 +282,14 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
node.inputs.forEach((input) => { node.inputs.forEach((input) => {
if (input.key === dynamicInput?.key) return; if (input.key === dynamicInput?.key) return;
// replace {{}} variables // replace {{xx}} variables
let value = replaceVariable(input.value, variables); let value = replaceVariable(input.value, variables);
// replace {{$$}} variables // replace {{$xx.xx$}} variables
value = replaceVariableLabel({ value = replaceVariableLabel({
text: value, text: value,
nodes: runtimeNodes, nodes: runtimeNodes,
variables: variables, variables,
runningNode: node runningNode: node
}); });
@@ -299,12 +300,11 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
variables variables
}); });
// concat dynamic inputs // Dynamic input is stored in the dynamic key
if (input.canEdit && dynamicInput && params[dynamicInput.key]) { if (input.canEdit && dynamicInput && params[dynamicInput.key]) {
params[dynamicInput.key][input.key] = valueTypeFormat(value, input.valueType); params[dynamicInput.key][input.key] = valueTypeFormat(value, input.valueType);
} }
// Not dynamic input
params[input.key] = valueTypeFormat(value, input.valueType); params[input.key] = valueTypeFormat(value, input.valueType);
}); });

View File

@@ -106,6 +106,7 @@ export const dispatchHttp468Request = async (props: HttpRequestProps): Promise<H
acc[key] = valueTypeFormat(value, WorkflowIOValueTypeEnum.string); acc[key] = valueTypeFormat(value, WorkflowIOValueTypeEnum.string);
return acc; return acc;
}, {}); }, {});
const requestBody = await (() => { const requestBody = await (() => {
if (!httpJsonBody) return {}; if (!httpJsonBody) return {};
try { try {

View File

@@ -89,7 +89,13 @@ export default function Editor({
}, [value, variables, variableLabels]); }, [value, variables, variableLabels]);
return ( return (
<Box position={'relative'} width={'full'} h={`${height}px`} cursor={'text'}> <Box
position={'relative'}
width={'full'}
h={`${height}px`}
cursor={'text'}
color={'myGray.700'}
>
<LexicalComposer initialConfig={initialConfig} key={key}> <LexicalComposer initialConfig={initialConfig} key={key}>
<PlainTextPlugin <PlainTextPlugin
contentEditable={ contentEditable={

View File

@@ -1,36 +1,40 @@
import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants'; import { FlowNodeTemplateTypeEnum } from '@fastgpt/global/core/workflow/constants';
import { NodeTemplateListType } from '@fastgpt/global/core/workflow/type/node'; import { i18nT } from '../../i18n/utils';
import { TFunction } from 'next-i18next';
export const workflowNodeTemplateList = (t: TFunction): NodeTemplateListType => [ export const workflowNodeTemplateList = [
{ {
type: FlowNodeTemplateTypeEnum.systemInput, type: FlowNodeTemplateTypeEnum.systemInput,
label: t('common:core.module.template.System input module'), label: i18nT('common:core.module.template.System input module'),
list: [] list: []
}, },
{ {
type: FlowNodeTemplateTypeEnum.ai, type: FlowNodeTemplateTypeEnum.ai,
label: t('common:core.module.template.AI function'), label: i18nT('common:core.module.template.AI function'),
list: []
},
{
type: FlowNodeTemplateTypeEnum.tools,
label: t('common:core.module.template.Tool module'),
list: [] list: []
}, },
{ {
type: FlowNodeTemplateTypeEnum.search, type: FlowNodeTemplateTypeEnum.search,
label: t('core.workflow.template.Search'), label: i18nT('common:core.workflow.template.Search'),
list: [] list: []
}, },
{ {
type: FlowNodeTemplateTypeEnum.multimodal, type: FlowNodeTemplateTypeEnum.multimodal,
label: t('core.workflow.template.Multimodal'), label: i18nT('common:core.workflow.template.Multimodal'),
list: []
},
{
type: FlowNodeTemplateTypeEnum.tools,
label: i18nT('common:core.module.template.Tool module'),
list: []
},
{
type: FlowNodeTemplateTypeEnum.communication,
label: i18nT('app:workflow.template.communication'),
list: [] list: []
}, },
{ {
type: FlowNodeTemplateTypeEnum.other, type: FlowNodeTemplateTypeEnum.other,
label: t('common:common.Other'), label: i18nT('common:common.Other'),
list: [] list: []
}, },
{ {

View File

@@ -1,6 +1,7 @@
{ {
"Run": "Run", "Run": "Run",
"ai_settings": "AI Settings", "ai_settings": "AI Settings",
"all_apps": "All Apps",
"app": { "app": {
"modules": { "modules": {
"click to update": "click to update", "click to update": "click to update",
@@ -52,6 +53,7 @@
"template": { "template": {
"simple_robot": "Simple Robot" "simple_robot": "Simple Robot"
}, },
"tool_input_param_tip": "Configure related information before the plugin runs properly",
"transition_to_workflow": "Transition to workflow", "transition_to_workflow": "Transition to workflow",
"transition_to_workflow_create_new_placeholder": "Create a new application instead of modifying the current one", "transition_to_workflow_create_new_placeholder": "Create a new application instead of modifying the current one",
"transition_to_workflow_create_new_tip": "After converting to workflow, it will not be able to convert back to simple mode, please confirm!", "transition_to_workflow_create_new_tip": "After converting to workflow, it will not be able to convert back to simple mode, please confirm!",
@@ -71,5 +73,11 @@
}, },
"version": { "version": {
"Revert success": "Revert success" "Revert success": "Revert success"
},
"workflow": {
"Input guide": "Input guide",
"template": {
"communication": "Communication"
}
} }
} }

View File

@@ -1,6 +1,5 @@
{ {
"App": "App", "App": "App",
"all_apps": "All Apps",
"click_to_resume": "Resume", "click_to_resume": "Resume",
"code_editor": "Code edit", "code_editor": "Code edit",
"Export": "Export", "Export": "Export",

View File

@@ -1,6 +1,7 @@
{ {
"Run": "运行", "Run": "运行",
"ai_settings": "AI 配置", "ai_settings": "AI 配置",
"all_apps": "全部应用",
"app": { "app": {
"modules": { "modules": {
"click to update": "点击更新", "click to update": "点击更新",
@@ -52,6 +53,7 @@
"template": { "template": {
"simple_robot": "简易机器人" "simple_robot": "简易机器人"
}, },
"tool_input_param_tip": "该插件正常运行需要配置相关信息",
"transition_to_workflow": "转成工作流", "transition_to_workflow": "转成工作流",
"transition_to_workflow_create_new_placeholder": "创建一个新的应用,而不是修改当前应用", "transition_to_workflow_create_new_placeholder": "创建一个新的应用,而不是修改当前应用",
"transition_to_workflow_create_new_tip": "转化成工作流后,将无法转化回简易模式,请确认!", "transition_to_workflow_create_new_tip": "转化成工作流后,将无法转化回简易模式,请确认!",
@@ -71,5 +73,11 @@
}, },
"version": { "version": {
"Revert success": "回滚成功" "Revert success": "回滚成功"
},
"workflow": {
"Input guide": "填写说明",
"template": {
"communication": "通信"
}
} }
} }

View File

@@ -1,6 +1,5 @@
{ {
"App": "应用", "App": "应用",
"all_apps": "全部应用",
"click_to_resume": "点击恢复", "click_to_resume": "点击恢复",
"code_editor": "代码编辑", "code_editor": "代码编辑",
"Export": "导出", "Export": "导出",

1159
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -52,13 +52,14 @@
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-hook-form": "7.43.1", "react-hook-form": "7.43.1",
"react-i18next": "14.1.2", "react-i18next": "14.1.2",
"react-markdown": "^8.0.7", "react-markdown": "^9.0.1",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.5.0",
"reactflow": "^11.7.4", "reactflow": "^11.7.4",
"rehype-katex": "^6.0.2", "rehype-external-links": "^3.0.0",
"remark-breaks": "^3.0.3", "rehype-katex": "^7.0.0",
"remark-gfm": "^3.0.1", "remark-breaks": "^4.0.0",
"remark-math": "^5.1.1", "remark-gfm": "^4.0.0",
"remark-math": "^6.0.0",
"request-ip": "^3.3.0", "request-ip": "^3.3.0",
"sass": "^1.58.3", "sass": "^1.58.3",
"use-context-selector": "^1.4.4", "use-context-selector": "^1.4.4",

View File

@@ -36,4 +36,4 @@ const MdImage = ({ src }: { src?: string }) => {
); );
}; };
export default React.memo(MdImage); export default MdImage;

View File

@@ -1,10 +1,11 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import 'katex/dist/katex.min.css'; import 'katex/dist/katex.min.css';
import RemarkMath from 'remark-math'; import RemarkMath from 'remark-math'; // Math syntax
import RemarkBreaks from 'remark-breaks'; import RemarkBreaks from 'remark-breaks'; // Line break
import RehypeKatex from 'rehype-katex'; import RehypeKatex from 'rehype-katex'; // Math render
import RemarkGfm from 'remark-gfm'; import RemarkGfm from 'remark-gfm'; // Special markdown syntax
import RehypeExternalLinks from 'rehype-external-links';
import styles from './index.module.scss'; import styles from './index.module.scss';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
@@ -15,6 +16,7 @@ import { useTranslation } from 'next-i18next';
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus'; import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
import MyIcon from '@fastgpt/web/components/common/Icon'; import MyIcon from '@fastgpt/web/components/common/Icon';
import { MARKDOWN_QUOTE_SIGN } from '@fastgpt/global/core/chat/constants'; import { MARKDOWN_QUOTE_SIGN } from '@fastgpt/global/core/chat/constants';
import { CodeClassNameEnum } from './utils';
const CodeLight = dynamic(() => import('./CodeLight'), { ssr: false }); const CodeLight = dynamic(() => import('./CodeLight'), { ssr: false });
const MermaidCodeBlock = dynamic(() => import('./img/MermaidCodeBlock'), { ssr: false }); const MermaidCodeBlock = dynamic(() => import('./img/MermaidCodeBlock'), { ssr: false });
@@ -24,15 +26,6 @@ const EChartsCodeBlock = dynamic(() => import('./img/EChartsCodeBlock'), { ssr:
const ChatGuide = dynamic(() => import('./chat/Guide'), { ssr: false }); const ChatGuide = dynamic(() => import('./chat/Guide'), { ssr: false });
const QuestionGuide = dynamic(() => import('./chat/QuestionGuide'), { ssr: false }); const QuestionGuide = dynamic(() => import('./chat/QuestionGuide'), { ssr: false });
export enum CodeClassName {
guide = 'guide',
questionGuide = 'questionGuide',
mermaid = 'mermaid',
echarts = 'echarts',
quote = 'quote',
files = 'files'
}
const Markdown = ({ const Markdown = ({
source = '', source = '',
showAnimation = false showAnimation = false
@@ -51,10 +44,13 @@ const Markdown = ({
[] []
); );
const formatSource = source const formatSource = useMemo(() => {
// .replace(/\\n/g, '\n') const formatSource = source
.replace(/(http[s]?:\/\/[^\s。]+)([。,])/g, '$1 $2') .replace(/(http[s]?:\/\/[^\s。]+)([。,])/g, '$1 $2') // Follow the link with a space
.replace(/\n*(\[QUOTE SIGN\]\(.*\))/g, '$1'); .replace(/\n*(\[QUOTE SIGN\]\(.*\))/g, '$1');
return formatSource;
}, [source]);
return ( return (
<ReactMarkdown <ReactMarkdown
@@ -62,9 +58,8 @@ const Markdown = ({
${showAnimation ? `${formatSource ? styles.waitingAnimation : styles.animation}` : ''} ${showAnimation ? `${formatSource ? styles.waitingAnimation : styles.animation}` : ''}
`} `}
remarkPlugins={[RemarkMath, [RemarkGfm, { singleTilde: false }], RemarkBreaks]} remarkPlugins={[RemarkMath, [RemarkGfm, { singleTilde: false }], RemarkBreaks]}
rehypePlugins={[RehypeKatex]} rehypePlugins={[RehypeKatex, [RehypeExternalLinks, { target: '_blank' }]]}
components={components} components={components}
linkTarget={'_blank'}
> >
{formatSource} {formatSource}
</ReactMarkdown> </ReactMarkdown>
@@ -73,6 +68,7 @@ const Markdown = ({
export default React.memo(Markdown); export default React.memo(Markdown);
/* Custom dom */
const Code = React.memo(function Code(e: any) { const Code = React.memo(function Code(e: any) {
const { inline, className, children } = e; const { inline, className, children } = e;
@@ -82,17 +78,16 @@ const Code = React.memo(function Code(e: any) {
const strChildren = String(children); const strChildren = String(children);
const Component = useMemo(() => { const Component = useMemo(() => {
if (codeType === CodeClassName.mermaid) { if (codeType === CodeClassNameEnum.mermaid) {
return <MermaidCodeBlock code={strChildren} />; return <MermaidCodeBlock code={strChildren} />;
} }
if (codeType === CodeClassNameEnum.guide) {
if (codeType === CodeClassName.guide) {
return <ChatGuide text={strChildren} />; return <ChatGuide text={strChildren} />;
} }
if (codeType === CodeClassName.questionGuide) { if (codeType === CodeClassNameEnum.questionGuide) {
return <QuestionGuide text={strChildren} />; return <QuestionGuide text={strChildren} />;
} }
if (codeType === CodeClassName.echarts) { if (codeType === CodeClassNameEnum.echarts) {
return <EChartsCodeBlock code={strChildren} />; return <EChartsCodeBlock code={strChildren} />;
} }
@@ -105,7 +100,6 @@ const Code = React.memo(function Code(e: any) {
return Component; return Component;
}); });
const Image = React.memo(function Image({ src }: { src?: string }) { const Image = React.memo(function Image({ src }: { src?: string }) {
return <MdImage src={src} />; return <MdImage src={src} />;
}); });

View File

@@ -0,0 +1,77 @@
export enum CodeClassNameEnum {
guide = 'guide',
questionGuide = 'questionGuide',
mermaid = 'mermaid',
echarts = 'echarts',
quote = 'quote',
files = 'files',
latex = 'latex'
}
function htmlTableToLatex(html: string) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const table = doc.querySelector('table');
if (!table) return '';
let latex = '\\begin{tabular}{';
// 获取列数
const columns = table.querySelectorAll('tr:first-child th, tr:first-child td').length;
latex += '|' + 'c|'.repeat(columns) + '}\n\\hline\n';
// 创建一个二维数组来跟踪单元格合并情况
const cellTracker = Array.from({ length: table.rows.length }, () => Array(columns).fill(false));
// 遍历行
table.querySelectorAll('tr').forEach((row, rowIndex) => {
const cells = row.querySelectorAll('th, td');
let cellTexts: string[] = [];
let colIndex = 0;
cells.forEach((cell) => {
// 跳过已经被合并的单元格
while (cellTracker[rowIndex][colIndex]) {
colIndex++;
}
// @ts-ignore
const rowspan = parseInt(cell.getAttribute('rowspan') || 1, 10);
// @ts-ignore
const colspan = parseInt(cell.getAttribute('colspan') || 1, 10);
// 添加单元格内容
let cellText = cell.textContent?.trim() || '';
if (colspan > 1) {
cellText = `\\multicolumn{${colspan}}{|c|}{${cellText}}`;
}
if (rowspan > 1) {
cellText = `\\multirow{${rowspan}}{*}{${cellText}}`;
}
cellTexts.push(cellText);
// 标记合并的单元格
for (let i = 0; i < rowspan; i++) {
for (let j = 0; j < colspan; j++) {
cellTracker[rowIndex + i][colIndex + j] = true;
}
}
colIndex += colspan;
});
latex += cellTexts.join(' & ') + ' \\\\\n\\hline\n';
});
latex += '\\end{tabular}';
return `\`\`\`${CodeClassNameEnum.latex}
${latex}
\`\`\``;
}
export function convertHtmlTablesToLatex(input: string) {
const tableRegex = /<table[\s\S]*?<\/table>/gi;
return input.replace(tableRegex, (match) => htmlTableToLatex(match));
}

View File

@@ -16,6 +16,7 @@ import MyModal from '@fastgpt/web/components/common/MyModal';
type Props = TextareaProps & { type Props = TextareaProps & {
title?: string; title?: string;
iconSrc?: string;
// variables: string[]; // variables: string[];
}; };
@@ -24,7 +25,11 @@ const MyTextarea = React.forwardRef<HTMLTextAreaElement, Props>(function MyTexta
const TextareaRef = useRef<HTMLTextAreaElement>(null); const TextareaRef = useRef<HTMLTextAreaElement>(null);
const { t } = useTranslation(); const { t } = useTranslation();
const { title = t('common:core.app.edit.Prompt Editor'), ...childProps } = props; const {
title = t('common:core.app.edit.Prompt Editor'),
iconSrc = 'modal/edit',
...childProps
} = props;
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
@@ -32,7 +37,7 @@ const MyTextarea = React.forwardRef<HTMLTextAreaElement, Props>(function MyTexta
<> <>
<Editor textareaRef={TextareaRef} {...childProps} onOpenModal={onOpen} /> <Editor textareaRef={TextareaRef} {...childProps} onOpenModal={onOpen} />
{isOpen && ( {isOpen && (
<MyModal iconSrc="/imgs/modal/edit.svg" title={title} isOpen onClose={onClose}> <MyModal iconSrc={iconSrc} title={title} isOpen onClose={onClose}>
<ModalBody> <ModalBody>
<Editor <Editor
textareaRef={ModalTextareaRef} textareaRef={ModalTextareaRef}

View File

@@ -96,9 +96,7 @@ const VariableEdit = ({
<Box> <Box>
<Flex alignItems={'center'}> <Flex alignItems={'center'}>
<MyIcon name={'core/app/simpleMode/variable'} w={'20px'} /> <MyIcon name={'core/app/simpleMode/variable'} w={'20px'} />
<FormLabel ml={2} fontWeight={'medium'}> <FormLabel ml={2}>{t('common:core.module.Variable')}</FormLabel>
{t('common:core.module.Variable')}
</FormLabel>
<ChatFunctionTip type={'variable'} /> <ChatFunctionTip type={'variable'} />
<Box flex={1} /> <Box flex={1} />
<Button <Button

View File

@@ -17,6 +17,8 @@ const WelcomeTextConfig = (props: TextareaProps) => {
<ChatFunctionTip type={'welcome'} /> <ChatFunctionTip type={'welcome'} />
</Flex> </Flex>
<MyTextarea <MyTextarea
iconSrc={'core/app/simpleMode/chat'}
title={t('common:core.app.Welcome Text')}
mt={2} mt={2}
rows={6} rows={6}
fontSize={'sm'} fontSize={'sm'}

View File

@@ -1,4 +1,5 @@
import Markdown, { CodeClassName } from '@/components/Markdown'; import Markdown from '@/components/Markdown';
import { CodeClassNameEnum } from '@/components/Markdown/utils';
import { import {
Accordion, Accordion,
AccordionButton, AccordionButton,
@@ -41,7 +42,7 @@ const AIResponseBox = ({ value, index, chat, isLastChild, isChatting, questionGu
index === chat.value.length - 1 index === chat.value.length - 1
) { ) {
source = `${source} source = `${source}
\`\`\`${CodeClassName.questionGuide} \`\`\`${CodeClassNameEnum.questionGuide}
${JSON.stringify(questionGuides)}`; ${JSON.stringify(questionGuides)}`;
} }

View File

@@ -14,6 +14,7 @@ const SearchParamsTip = ({
limit = 1500, limit = 1500,
responseEmptyText, responseEmptyText,
usingReRank = false, usingReRank = false,
datasetSearchUsingExtensionQuery,
queryExtensionModel queryExtensionModel
}: { }: {
searchMode: `${DatasetSearchModeEnum}`; searchMode: `${DatasetSearchModeEnum}`;
@@ -21,6 +22,7 @@ const SearchParamsTip = ({
limit?: number; limit?: number;
responseEmptyText?: string; responseEmptyText?: string;
usingReRank?: boolean; usingReRank?: boolean;
datasetSearchUsingExtensionQuery?: boolean;
queryExtensionModel?: string; queryExtensionModel?: string;
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -32,11 +34,11 @@ const SearchParamsTip = ({
const extensionModelName = useMemo( const extensionModelName = useMemo(
() => () =>
queryExtensionModel datasetSearchUsingExtensionQuery
? llmModelList.find((item) => item.model === queryExtensionModel)?.name ?? ? llmModelList.find((item) => item.model === queryExtensionModel)?.name ??
llmModelList[0]?.name llmModelList[0]?.name
: undefined, : undefined,
[llmModelList, queryExtensionModel] [datasetSearchUsingExtensionQuery, llmModelList, queryExtensionModel]
); );
return ( return (

View File

@@ -52,7 +52,7 @@ const ConfigPerModal = ({
> >
<ModalBody> <ModalBody>
<HStack> <HStack>
<Avatar src={avatar} w={'1.75rem'} /> <Avatar src={avatar} w={'1.75rem'} borderRadius={'md'} />
<Box>{name}</Box> <Box>{name}</Box>
</HStack> </HStack>
{!isInheritPermission && ( {!isInheritPermission && (

View File

@@ -0,0 +1,46 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { connectToDatabase } from '@/service/mongo';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { MongoDataset } from '@fastgpt/service/core/dataset/schema';
import { DatasetDefaultPermissionVal } from '@fastgpt/global/support/permission/dataset/constant';
/* pg 中的数据搬到 mongo dataset.datas 中,并做映射 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
await authCert({ req, authRoot: true });
await MongoDataset.updateMany(
{
inheritPermission: { $exists: false }
},
{
$set: {
inheritPermission: true
}
}
);
await MongoDataset.updateMany(
{
defaultPermission: { $exists: false }
},
{
$set: {
defaultPermission: DatasetDefaultPermissionVal
}
}
);
jsonRes(res, {
message: 'success'
});
} catch (error) {
console.log(error);
jsonRes(res, {
code: 500,
error
});
}
}

View File

@@ -89,7 +89,7 @@ async function handler(req: ApiRequestProps<AppUpdateParams, { appId: string }>)
defaultPermission: updatedDefaultPermission defaultPermission: updatedDefaultPermission
}), }),
// Not root, update default permission // Not root, update default permission
...(isDefaultPermissionChanged && { inheritPermission: false }), ...(app.parentId && isDefaultPermissionChanged && { inheritPermission: false }),
...(teamTags && { teamTags }), ...(teamTags && { teamTags }),
...(formatNodes && { ...(formatNodes && {
modules: formatNodes modules: formatNodes

View File

@@ -15,7 +15,7 @@ import { removeEmptyUserInput } from '@fastgpt/global/core/chat/utils';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { import {
filterPluginInputVariables, removePluginInputVariables,
updatePluginInputByVariables updatePluginInputByVariables
} from '@fastgpt/global/core/workflow/utils'; } from '@fastgpt/global/core/workflow/utils';
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
@@ -66,7 +66,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
// Plugin need to replace inputs // Plugin need to replace inputs
if (isPlugin) { if (isPlugin) {
nodes = updatePluginInputByVariables(nodes, variables); nodes = updatePluginInputByVariables(nodes, variables);
variables = filterPluginInputVariables(variables, nodes); variables = removePluginInputVariables(variables, nodes);
} else { } else {
if (!userInput) { if (!userInput) {
throw new Error('Params Error'); throw new Error('Params Error');

View File

@@ -24,7 +24,7 @@ async function handler(req: NextApiRequest) {
searchMode, searchMode,
usingReRank, usingReRank,
datasetSearchUsingExtensionQuery = false, datasetSearchUsingExtensionQuery = true,
datasetSearchExtensionModel, datasetSearchExtensionModel,
datasetSearchExtensionBg = '' datasetSearchExtensionBg = ''
} = req.body as SearchTestProps; } = req.body as SearchTestProps;

View File

@@ -78,7 +78,7 @@ async function handler(
defaultPermission: updatedDefaultPermission defaultPermission: updatedDefaultPermission
}), }),
// update the defaultPermission // update the defaultPermission
...(isDefaultPermissionChanged && { inheritPermission: false }) ...(dataset.parentId && isDefaultPermissionChanged && { inheritPermission: false })
}, },
{ session } { session }
); );

View File

@@ -55,7 +55,7 @@ import { getAppLatestVersion } from '@fastgpt/service/core/app/controller';
import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant'; import { ReadPermissionVal } from '@fastgpt/global/support/permission/constant';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { import {
filterPluginInputVariables, removePluginInputVariables,
updatePluginInputByVariables updatePluginInputByVariables
} from '@fastgpt/global/core/workflow/utils'; } from '@fastgpt/global/core/workflow/utils';
import { getNanoid } from '@fastgpt/global/common/string/tools'; import { getNanoid } from '@fastgpt/global/common/string/tools';
@@ -238,7 +238,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
) )
: storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)); : storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes));
const runtimeVariables = filterPluginInputVariables( const runtimeVariables = removePluginInputVariables(
variables, variables,
storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes)) storeNodes2RuntimeNodes(nodes, getDefaultEntryNodeIds(nodes))
); );

View File

@@ -113,8 +113,8 @@ const EditForm = ({
...item, ...item,
parent: { parent: {
id: 'VARIABLE_NODE_ID', id: 'VARIABLE_NODE_ID',
label: '全局变量', label: t('common:core.module.Variable'),
avatar: '/imgs/workflow/variable.png' avatar: 'core/workflow/template/variable'
} }
})), })),
[appForm.chatConfig.variables, t] [appForm.chatConfig.variables, t]
@@ -230,6 +230,7 @@ const EditForm = ({
similarity={appForm.dataset.similarity} similarity={appForm.dataset.similarity}
limit={appForm.dataset.limit} limit={appForm.dataset.limit}
usingReRank={appForm.dataset.usingReRank} usingReRank={appForm.dataset.usingReRank}
datasetSearchUsingExtensionQuery={appForm.dataset.datasetSearchUsingExtensionQuery}
queryExtensionModel={appForm.dataset.datasetSearchExtensionModel} queryExtensionModel={appForm.dataset.datasetSearchExtensionModel}
/> />
</Box> </Box>

View File

@@ -92,7 +92,12 @@ const Header = ({
)} )}
<Flex pt={[2, 3]} alignItems={'flex-start'} position={'relative'}> <Flex pt={[2, 3]} alignItems={'flex-start'} position={'relative'}>
<Box flex={'1'}> <Box flex={'1'}>
<FolderPath paths={paths} hoverStyle={{ color: 'primary.600' }} onClick={onclickRoute} /> <FolderPath
rootName={t('app:all_apps')}
paths={paths}
hoverStyle={{ color: 'primary.600' }}
onClick={onclickRoute}
/>
</Box> </Box>
{isPc && ( {isPc && (
<Box position={'absolute'} left={'50%'} transform={'translateX(-50%)'}> <Box position={'absolute'} left={'50%'} transform={'translateX(-50%)'}>

View File

@@ -6,6 +6,7 @@ import {
Box, Box,
Button, Button,
Flex, Flex,
HStack,
Input, Input,
InputGroup, InputGroup,
InputLeftElement, InputLeftElement,
@@ -287,6 +288,8 @@ const RenderList = React.memo(function RenderList({
</MyTooltip> </MyTooltip>
); );
})} })}
{/* Plugin input config */}
{!!configTool && ( {!!configTool && (
<MyModal <MyModal
isOpen isOpen
@@ -295,6 +298,19 @@ const RenderList = React.memo(function RenderList({
overflow={'auto'} overflow={'auto'}
> >
<ModalBody> <ModalBody>
<HStack mb={4} spacing={1} fontSize={'sm'}>
<MyIcon name={'common/info'} w={'1.25rem'} />
<Box flex={1}>{t('app:tool_input_param_tip')}</Box>
{configTool.inputExplanationUrl && (
<Box
cursor={'pointer'}
color={'primary.500'}
onClick={() => window.open(configTool.inputExplanationUrl, '_blank')}
>
{t('app:workflow.Input guide')}
</Box>
)}
</HStack>
{configTool.inputs {configTool.inputs
.filter((item) => !item.toolDescription) .filter((item) => !item.toolDescription)
.map((input) => { .map((input) => {

View File

@@ -334,7 +334,7 @@ const RenderList = React.memo(function RenderList({
const { computedNewNodeName } = useWorkflowUtils(); const { computedNewNodeName } = useWorkflowUtils();
const formatTemplates = useMemo<NodeTemplateListType>(() => { const formatTemplates = useMemo<NodeTemplateListType>(() => {
const copy: NodeTemplateListType = cloneDeep(workflowNodeTemplateList(t)); const copy: NodeTemplateListType = cloneDeep(workflowNodeTemplateList);
templates.forEach((item) => { templates.forEach((item) => {
const index = copy.findIndex((template) => template.type === item.templateType); const index = copy.findIndex((template) => template.type === item.templateType);
if (index === -1) return; if (index === -1) return;

View File

@@ -1,12 +1,30 @@
import React from 'react'; import React from 'react';
import { Box, Flex, FlexProps } from '@chakra-ui/react'; import { Box, StackProps, HStack } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next';
const IOTitle = ({
text,
inputExplanationUrl,
...props
}: { text?: 'Input' | 'Output' | string; inputExplanationUrl?: string } & StackProps) => {
const { t } = useTranslation();
const IOTitle = ({ text, ...props }: { text?: 'Input' | 'Output' | string } & FlexProps) => {
return ( return (
<Flex fontSize={'md'} alignItems={'center'} fontWeight={'medium'} mb={3} {...props}> <HStack fontSize={'md'} alignItems={'center'} fontWeight={'medium'} mb={3} {...props}>
<Box w={'3px'} h={'14px'} borderRadius={'13px'} bg={'primary.600'} mr={1.5} /> <Box w={'3px'} h={'14px'} borderRadius={'13px'} bg={'primary.600'} />
{text} <Box>{text}</Box>
</Flex> <Box flex={1} />
{inputExplanationUrl && (
<Box
cursor={'pointer'}
color={'primary.500'}
onClick={() => window.open(inputExplanationUrl, '_blank')}
>
{t('app:workflow.Input guide')}
</Box>
)}
</HStack>
); );
}; };

View File

@@ -37,7 +37,10 @@ const NodeSimple = ({
{filterHiddenInputs.length > 0 && ( {filterHiddenInputs.length > 0 && (
<> <>
<Container> <Container>
<IOTitle text={t('common:common.Input')} /> <IOTitle
text={t('common:common.Input')}
inputExplanationUrl={data.inputExplanationUrl}
/>
<RenderInput nodeId={nodeId} flowInputList={commonInputs} /> <RenderInput nodeId={nodeId} flowInputList={commonInputs} />
</Container> </Container>
</> </>

View File

@@ -80,27 +80,36 @@ const NodeCard = (props: Props) => {
const node = nodeList.find((node) => node.nodeId === nodeId); const node = nodeList.find((node) => node.nodeId === nodeId);
const { openConfirm: onOpenConfirmSync, ConfirmModal: ConfirmSyncModal } = useConfirm({ const { openConfirm: onOpenConfirmSync, ConfirmModal: ConfirmSyncModal } = useConfirm({
content: appT('module.Confirm Sync') content: t('app:module.Confirm Sync')
}); });
const { data: newNodeVersion, runAsync: getNodeVersion } = useRequest2( const { data: nodeTemplate, runAsync: getNodeLatestTemplate } = useRequest2(
async () => { async () => {
if (node?.flowNodeType === FlowNodeTypeEnum.pluginModule) { if (node?.flowNodeType === FlowNodeTypeEnum.pluginModule) {
if (!node?.pluginId) return; if (!node?.pluginId) return;
const template = await getPreviewPluginNode({ appId: node.pluginId }); const template = await getPreviewPluginNode({ appId: node.pluginId });
return template.version;
// Focus update plugin latest inputExplanationUrl
onChangeNode({
nodeId,
type: 'attr',
key: 'inputExplanationUrl',
value: template.inputExplanationUrl
});
return template;
} else { } else {
const template = moduleTemplatesFlat.find( const template = moduleTemplatesFlat.find(
(item) => item.flowNodeType === node?.flowNodeType (item) => item.flowNodeType === node?.flowNodeType
); );
return template?.version; return template;
} }
}, },
{ {
manual: false manual: false
} }
); );
const hasNewVersion = newNodeVersion && newNodeVersion !== node?.version; const hasNewVersion = nodeTemplate && nodeTemplate.version !== node?.version;
const { runAsync: onClickSyncVersion } = useRequest2( const { runAsync: onClickSyncVersion } = useRequest2(
async () => { async () => {
@@ -119,10 +128,10 @@ const NodeCard = (props: Props) => {
node: getLatestNodeTemplate(node, template) node: getLatestNodeTemplate(node, template)
}); });
} }
await getNodeVersion(); await getNodeLatestTemplate();
}, },
{ {
refreshDeps: [node, nodeId, onResetNode, getNodeVersion] refreshDeps: [node, nodeId, onResetNode, getNodeLatestTemplate]
} }
); );

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import type { RenderInputProps } from '../type'; import type { RenderInputProps } from '../type';
import { Box, Button, Flex, useDisclosure } from '@chakra-ui/react'; import { Flex, useDisclosure } from '@chakra-ui/react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants'; import { DatasetSearchModeEnum } from '@fastgpt/global/core/dataset/constants';
import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant'; import { FlowNodeTypeEnum } from '@fastgpt/global/core/workflow/node/constant';
@@ -54,7 +54,8 @@ const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => {
if (data[input.key] !== undefined) { if (data[input.key] !== undefined) {
setData((state) => ({ setData((state) => ({
...state, ...state,
[input.key]: input.value // @ts-ignore
[input.key]: input.value || state[input.key]
})); }));
} }
}); });
@@ -82,6 +83,7 @@ const SelectDatasetParam = ({ inputs = [], nodeId }: RenderInputProps) => {
similarity={data.similarity} similarity={data.similarity}
limit={data.limit} limit={data.limit}
usingReRank={data.usingReRank} usingReRank={data.usingReRank}
datasetSearchUsingExtensionQuery={data.datasetSearchUsingExtensionQuery}
queryExtensionModel={data.datasetSearchExtensionModel} queryExtensionModel={data.datasetSearchExtensionModel}
/> />
</> </>

View File

@@ -10,7 +10,7 @@ const TextInput = ({ item, nodeId }: RenderInputProps) => {
const Render = useMemo(() => { const Render = useMemo(() => {
return ( return (
<Input <Input
placeholder={item.placeholder} placeholder={item.placeholder ?? item.description}
defaultValue={item.value} defaultValue={item.value}
bg={'white'} bg={'white'}
px={3} px={3}

View File

@@ -14,7 +14,6 @@ import { AppUpdateParams } from '@/global/core/app/api';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { useI18n } from '@/web/context/I18n'; import { useI18n } from '@/web/context/I18n';
import { AppTypeEnum } from '@fastgpt/global/core/app/constants'; import { AppTypeEnum } from '@fastgpt/global/core/app/constants';
import { useThrottleEffect } from 'ahooks';
const MoveModal = dynamic(() => import('@/components/common/folder/MoveModal')); const MoveModal = dynamic(() => import('@/components/common/folder/MoveModal'));
type AppListContextType = { type AppListContextType = {

View File

@@ -131,10 +131,10 @@ const MobileDrawer = ({
}} }}
list={[ list={[
...(isTeamChat ...(isTeamChat
? [{ label: t('common:all_apps'), value: TabEnum.recently }] ? [{ label: t('app:all_apps'), value: TabEnum.recently }]
: [ : [
{ label: t('common:core.chat.Recent use'), value: TabEnum.recently }, { label: t('common:core.chat.Recent use'), value: TabEnum.recently },
{ label: t('common:all_apps'), value: TabEnum.app } { label: t('app:all_apps'), value: TabEnum.app }
]) ])
]} ]}
value={currentTab} value={currentTab}

View File

@@ -71,7 +71,7 @@ const Test = ({ datasetId }: { datasetId: string }) => {
usingReRank: false, usingReRank: false,
limit: 5000, limit: 5000,
similarity: 0, similarity: 0,
datasetSearchUsingExtensionQuery: false, datasetSearchUsingExtensionQuery: true,
datasetSearchExtensionModel: llmModelList[0].model, datasetSearchExtensionModel: llmModelList[0].model,
datasetSearchExtensionBg: '' datasetSearchExtensionBg: ''
} }
@@ -432,6 +432,7 @@ const TestResults = React.memo(function TestResults({
similarity={datasetTestItem.similarity} similarity={datasetTestItem.similarity}
limit={datasetTestItem.limit} limit={datasetTestItem.limit}
usingReRank={datasetTestItem.usingReRank} usingReRank={datasetTestItem.usingReRank}
datasetSearchUsingExtensionQuery={!!datasetTestItem.queryExtensionModel}
queryExtensionModel={datasetTestItem.queryExtensionModel} queryExtensionModel={datasetTestItem.queryExtensionModel}
/> />
</Box> </Box>

View File

@@ -68,7 +68,11 @@ function DatasetContextProvider({ children }: { children: React.ReactNode }) {
const { parentId = null } = router.query as { parentId?: string | null }; const { parentId = null } = router.query as { parentId?: string | null };
const { data: myDatasets = [], runAsync: loadMyDatasets } = useRequest2( const {
data: myDatasets = [],
runAsync: loadMyDatasets,
loading: isFetchingDatasets
} = useRequest2(
() => () =>
getDatasets({ getDatasets({
parentId parentId
@@ -83,7 +87,7 @@ function DatasetContextProvider({ children }: { children: React.ReactNode }) {
() => (parentId ? getDatasetById(parentId) : Promise.resolve(undefined)), () => (parentId ? getDatasetById(parentId) : Promise.resolve(undefined)),
{ {
manual: false, manual: false,
refreshDeps: [parentId, myDatasets] refreshDeps: [parentId]
} }
); );
@@ -95,16 +99,8 @@ function DatasetContextProvider({ children }: { children: React.ReactNode }) {
} }
); );
const { runAsync: refetchDatasets, loading: isFetchingDatasets } = useRequest2(
() => loadMyDatasets(),
{
manual: false,
refreshDeps: [parentId]
}
);
const { runAsync: onUpdateDataset } = useRequest2(putDatasetById, { const { runAsync: onUpdateDataset } = useRequest2(putDatasetById, {
onSuccess: () => Promise.all([refetchDatasets(), refetchPaths(), loadMyDatasets()]) onSuccess: () => Promise.all([refetchFolderDetail(), refetchPaths(), loadMyDatasets()])
}); });
const onMoveDataset = useCallback( const onMoveDataset = useCallback(
@@ -138,7 +134,6 @@ function DatasetContextProvider({ children }: { children: React.ReactNode }) {
}); });
const contextValue = { const contextValue = {
refetchDatasets,
isFetchingDatasets, isFetchingDatasets,
setMoveDatasetId, setMoveDatasetId,
paths, paths,

View File

@@ -40,8 +40,8 @@ export const getGlobalVariableNode = ({
flowNodeType: FlowNodeTypeEnum.emptyNode, flowNodeType: FlowNodeTypeEnum.emptyNode,
sourceHandle: getHandleConfig(false, false, false, false), sourceHandle: getHandleConfig(false, false, false, false),
targetHandle: getHandleConfig(false, false, false, false), targetHandle: getHandleConfig(false, false, false, false),
avatar: '/imgs/workflow/variable.png', avatar: 'core/workflow/template/variable',
name: '全局变量', name: t('common:core.module.Variable'),
intro: '', intro: '',
unique: true, unique: true,
forbidDelete: true, forbidDelete: true,