* fix: full text search match query

* perf: mongo schema import, Avoid duplicate import

* feat: mongo log store

* doc

* fix: sandbox outputs

* perf: desc color

* fix: node init

* perf code

* perf: chat header
This commit is contained in:
Archer
2024-07-10 15:52:39 +08:00
committed by GitHub
parent f548e24e7d
commit e2ae571d15
21 changed files with 184 additions and 137 deletions

View File

@@ -13,9 +13,9 @@ weight: 818
### 2. 修改镜像
- fastgpt 镜像 tag 修改成 v4.8.6-alpha
- fastgpt-sandbox 镜像 tag 修改成 v4.8.6-alpha
- 商业版镜像 tag 修改成 v4.8.6-alpha
- fastgpt 镜像 tag 修改成 v4.8.6-alpha2
- fastgpt-sandbox 镜像 tag 修改成 v4.8.6-alpha2
- 商业版镜像 tag 修改成 v4.8.6-alpha2
### 3. 执行初始化
@@ -39,6 +39,7 @@ curl --location --request POST 'https://{{host}}/api/admin/initv486' \
4. 新增 - 移动文本加工和自定义反馈到基础节点中
5. 优化 - Read file 默认选中从节点,实现 MongoDB 读写分离,减轻主节点压力
6. 优化 - 知识库导入接口,返回值对齐
7. 修复 - 工作流中团队插件加载异常
8. 修复 - 知识库集合目录导航失效
9. 修复 - 通过 API 调用 chat 接口,传递 System 异常
7. 优化 - Mongo model 重复加载
8. 修复 - 工作流中团队插件加载异常
9. 修复 - 知识库集合目录导航失效
10. 修复 - 通过 API 调用 chat 接口,传递 System 异常

View File

@@ -8,5 +8,10 @@ export const Output_Template_AddOutput: FlowNodeOutputItemType = {
key: NodeOutputKeyEnum.addOutputParam,
type: FlowNodeOutputTypeEnum.dynamic,
valueType: WorkflowIOValueTypeEnum.dynamic,
label: ''
label: '',
customFieldConfig: {
selectValueTypeList: Object.values(WorkflowIOValueTypeEnum),
showDescription: false,
showDefaultValue: false
}
};

View File

@@ -82,12 +82,7 @@ export const HttpNode468: FlowNodeTemplateType = {
],
outputs: [
{
...Output_Template_AddOutput,
customFieldConfig: {
selectValueTypeList: Object.values(WorkflowIOValueTypeEnum),
showDescription: false,
showDefaultValue: true
}
...Output_Template_AddOutput
},
{
id: NodeOutputKeyEnum.error,

View File

@@ -36,6 +36,32 @@ export const CodeNode: FlowNodeTemplateType = {
showDefaultValue: true
}
},
{
renderTypeList: [FlowNodeInputTypeEnum.reference],
valueType: WorkflowIOValueTypeEnum.string,
canEdit: true,
key: 'data1',
label: 'data1',
customInputConfig: {
selectValueTypeList: Object.values(WorkflowIOValueTypeEnum),
showDescription: false,
showDefaultValue: true
},
required: true
},
{
renderTypeList: [FlowNodeInputTypeEnum.reference],
valueType: WorkflowIOValueTypeEnum.string,
canEdit: true,
key: 'data2',
label: 'data2',
customInputConfig: {
selectValueTypeList: Object.values(WorkflowIOValueTypeEnum),
showDescription: false,
showDefaultValue: true
},
required: true
},
{
key: NodeInputKeyEnum.codeType,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
@@ -52,7 +78,7 @@ export const CodeNode: FlowNodeTemplateType = {
outputs: [
{
...Output_Template_AddOutput,
description: '将代码中 return 的对象作为输出,传递给后续的节点'
description: '将代码中 return 的对象作为输出,传递给后续的节点。变量名需要对应 return 的 key'
},
{
id: NodeOutputKeyEnum.rawResponse,

View File

@@ -1,6 +1,9 @@
import { addLog } from '../../common/system/log';
import mongoose, { Model } from 'mongoose';
export default mongoose;
export * from 'mongoose';
export const connectionMongo = (() => {
if (!global.mongodb) {
global.mongodb = mongoose;
@@ -9,9 +12,6 @@ export const connectionMongo = (() => {
return global.mongodb;
})();
export default mongoose;
export * from 'mongoose';
const addCommonMiddleware = (schema: mongoose.Schema) => {
const operations = [
/^find/,

View File

@@ -1,8 +1,8 @@
import dayjs from 'dayjs';
import chalk from 'chalk';
import { LogLevelEnum } from './log/constant';
// import { MongoLog } from './log/schema';
import connectionMongo from '../mongo/index';
import { connectionMongo } from '../mongo/index';
import { getMongoLog } from './log/schema';
const logMap = {
[LogLevelEnum.debug]: {
@@ -52,14 +52,14 @@ export const addLog = {
level === LogLevelEnum.error && console.error(obj);
// store
// if (level >= STORE_LOG_LEVEL && connectionMongo.connection.readyState === 1) {
// // store log
// MongoLog.create({
// text: msg,
// level,
// metadata: obj
// });
// }
if (level >= STORE_LOG_LEVEL && connectionMongo.connection.readyState === 1) {
// store log
getMongoLog().create({
text: msg,
level,
metadata: obj
});
}
},
debug(msg: string, obj?: Record<string, any>) {
this.log(LogLevelEnum.debug, msg, obj);

View File

@@ -4,24 +4,26 @@ import { LogLevelEnum } from './constant';
export const LogCollectionName = 'system_logs';
const SystemLogSchema = new Schema({
text: {
type: String,
required: true
},
level: {
type: String,
required: true,
enum: Object.values(LogLevelEnum)
},
time: {
type: Date,
default: () => new Date()
},
metadata: Object
});
export const getMongoLog = () => {
const SystemLogSchema = new Schema({
text: {
type: String,
required: true
},
level: {
type: String,
required: true,
enum: Object.values(LogLevelEnum)
},
time: {
type: Date,
default: () => new Date()
},
metadata: Object
});
SystemLogSchema.index({ time: 1 }, { expires: '15d' });
SystemLogSchema.index({ level: 1 });
SystemLogSchema.index({ time: 1 }, { expires: '15d' });
SystemLogSchema.index({ level: 1 });
export const MongoLog = getMongoModel<SystemLogType>(LogCollectionName, SystemLogSchema);
return getMongoModel<SystemLogType>(LogCollectionName, SystemLogSchema);
};

View File

@@ -57,7 +57,7 @@
},
"module": {
"Combine Modules": "Combine Modules",
"Confirm Sync": "Using the latest template will overwrite the existing one and may result in the loss of some previous configuration information. Please confirm.",
"Confirm Sync": "The template will be updated to the latest template configuration. Fields that do not exist in the template will be deleted (including all custom fields). You are advised to make a copy of the node and then update the original node version.",
"Custom Title Tip": "This title will be displayed during the conversation",
"My Modules": "My Modules",
"No Modules": "No plugins yet~",

View File

@@ -1068,7 +1068,7 @@
"Debug": "Debug",
"Debug Node": "Debug mode",
"Failed": "Execution failed",
"Not intro": "This node has no introduction~\\",
"Not intro": "This node has no introduction~",
"Run from here": "Run from here",
"Run result": "Run result",
"Running": "Running",

View File

@@ -16,7 +16,7 @@
"Tool input": "Tool",
"code": {
"Reset template": "Reset template",
"Reset template confirm": "Are you sure to restore the code template? Be careful to save the current code."
"Reset template confirm": "Are you sure to restore the code template? All input and output to template values will be reset, please be careful to save the current code."
},
"ifelse": {
"Input value": "Input",

View File

@@ -56,7 +56,7 @@
},
"module": {
"Combine Modules": "组合模块",
"Confirm Sync": "将会使用最新模板进行覆盖,可能会丢失一些旧的配置信息,请确认",
"Confirm Sync": "将会更新至最新模板配置,不存在模板中的字段将会被删除(包括所有自定义字段),建议您先复制一份节点,再更新原来节点的版本。",
"Custom Title Tip": "该标题名字会展示在对话过程中",
"My Modules": "",
"No Modules": "没找到插件",

View File

@@ -1077,7 +1077,7 @@
"Debug": "调试",
"Debug Node": "Debug 模式",
"Failed": "运行失败",
"Not intro": "这个节点没有介绍~\\",
"Not intro": "这个节点没有介绍~",
"Run from here": "从这里开始运行",
"Run result": "",
"Running": "运行中",

View File

@@ -16,7 +16,7 @@
"Tool input": "工具参数",
"code": {
"Reset template": "还原模板",
"Reset template confirm": "确认还原代码模板?请注意保存当前代码。"
"Reset template confirm": "确认还原代码模板?将会重置所有输入和输出至模板值,请注意保存当前代码。"
},
"ifelse": {
"Input value": "输入值",

View File

@@ -155,7 +155,7 @@ const SelectOneResource = ({
return loading ? (
<Loading fixed={false} />
) : (
<Box maxH={maxH} overflow={'auto'}>
<Box maxH={maxH} h={'100%'} overflow={'auto'}>
<Render list={concatRoot} />
</Box>
);

View File

@@ -63,8 +63,8 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
const avatar = getValues('avatar');
// submit config
const { mutate: saveSubmitSuccess, isLoading: btnLoading } = useRequest({
mutationFn: async (data: AppSchema) => {
const { runAsync: saveSubmitSuccess, loading: btnLoading } = useRequest2(
async (data: AppSchema) => {
await updateAppDetail({
name: data.name,
avatar: data.avatar,
@@ -72,16 +72,17 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
defaultPermission: data.defaultPermission
});
},
onSuccess() {
onClose();
toast({
title: t('common.Update Success'),
status: 'success'
});
reloadApp();
},
errorToast: t('common.Update Failed')
});
{
onSuccess() {
toast({
title: t('common.Update Success'),
status: 'success'
});
reloadApp();
},
errorToast: t('common.Update Failed')
}
);
const saveSubmitError = useCallback(() => {
// deep search message
@@ -101,8 +102,8 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
}, [errors, t, toast]);
const saveUpdateModel = useCallback(
() => handleSubmit((data) => saveSubmitSuccess(data), saveSubmitError)(),
[handleSubmit, saveSubmitError, saveSubmitSuccess]
() => handleSubmit((data) => saveSubmitSuccess(data).then(onClose), saveSubmitError)(),
[handleSubmit, onClose, saveSubmitError, saveSubmitSuccess]
);
const onSelectFile = useCallback(
@@ -210,7 +211,7 @@ const InfoModal = ({ onClose }: { onClose: () => void }) => {
isInheritPermission={appDetail.inheritPermission}
onChange={(v) => {
setValue('defaultPermission', v);
handleSubmit((data) => saveSubmitSuccess(data), saveSubmitError)();
return handleSubmit((data) => saveSubmitSuccess(data), saveSubmitError)();
}}
hasParent={!!appDetail.parentId}
/>

View File

@@ -396,7 +396,9 @@ const RenderList = React.memo(function RenderList({
{t(template.name)}
</Box>
</Flex>
<Box mt={2}>{t(template.intro || 'core.workflow.Not intro')}</Box>
<Box mt={2} color={'myGray.500'}>
{t(template.intro) || t('core.workflow.Not intro')}
</Box>
</Box>
}
>

View File

@@ -16,7 +16,8 @@ import CodeEditor from '@fastgpt/web/components/common/Textarea/CodeEditor';
import { Box, Flex } from '@chakra-ui/react';
import { useI18n } from '@/web/context/I18n';
import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { JS_TEMPLATE } from '@fastgpt/global/core/workflow/template/system/sandbox/constants';
import { getLatestNodeTemplate } from '@/web/core/workflow/utils';
import { CodeNode } from '@fastgpt/global/core/workflow/template/system/sandbox';
const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { t } = useTranslation();
@@ -24,6 +25,8 @@ const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
const { nodeId, inputs, outputs } = data;
const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const onResetNode = useContextSelector(WorkflowContext, (v) => v.onResetNode);
const { isTool, commonInputs } = splitToolInputs(inputs, nodeId);
const { ConfirmModal, openConfirm } = useConfirm({
content: workflowT('code.Reset template confirm')
@@ -41,14 +44,9 @@ const NodeCode = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
color={'primary.500'}
fontSize={'xs'}
onClick={openConfirm(() => {
onChangeNode({
nodeId,
type: 'updateInput',
key: item.key,
value: {
...item,
value: JS_TEMPLATE
}
onResetNode({
id: nodeId,
node: getLatestNodeTemplate(data, CodeNode)
});
})}
>

View File

@@ -37,6 +37,7 @@ export const defaultInput: FlowNodeInputItemType = {
renderTypeList: [FlowNodeInputTypeEnum.reference], // Can only choose one here
selectedTypeIndex: 0,
valueType: WorkflowIOValueTypeEnum.string,
canEdit: true,
key: '',
label: ''
};

View File

@@ -16,7 +16,7 @@ import { useDebug } from '../../hooks/useDebug';
import { ResponseBox } from '@/components/ChatBox/components/WholeResponseModal';
import EmptyTip from '@fastgpt/web/components/common/EmptyTip';
import { getPreviewPluginNode } from '@/web/core/app/api/plugin';
import { storeNode2FlowNode, updateFlowNodeVersion } from '@/web/core/workflow/utils';
import { storeNode2FlowNode, getLatestNodeTemplate } from '@/web/core/workflow/utils';
import { getNanoid } from '@fastgpt/global/common/string/tools';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../../context';
@@ -24,8 +24,6 @@ import { useI18n } from '@/web/context/I18n';
import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/constants';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import MyTooltip from '@fastgpt/web/components/common/MyTooltip';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useMount } from 'ahooks';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { useWorkflowUtils } from '../../hooks/useUtils';
@@ -56,13 +54,11 @@ const NodeCard = (props: Props) => {
minW = '300px',
maxW = '600px',
nodeId,
flowNodeType,
selected,
menuForbid,
isTool = false,
isError = false,
debugResult,
pluginId
debugResult
} = props;
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
@@ -106,11 +102,11 @@ const NodeCard = (props: Props) => {
);
const hasNewVersion = newNodeVersion && newNodeVersion !== node?.version;
const template = moduleTemplatesFlat.find((item) => item.flowNodeType === node?.flowNodeType);
const onClickSyncVersion = useCallback(async () => {
try {
const { runAsync: onClickSyncVersion } = useRequest2(
async () => {
const template = moduleTemplatesFlat.find((item) => item.flowNodeType === node?.flowNodeType);
if (!node || !template) return;
if (node?.flowNodeType === FlowNodeTypeEnum.pluginModule) {
if (!node.pluginId) return;
onResetNode({
@@ -120,14 +116,15 @@ const NodeCard = (props: Props) => {
} else {
onResetNode({
id: nodeId,
node: updateFlowNodeVersion(node, template)
node: getLatestNodeTemplate(node, template)
});
}
await getNodeVersion();
} catch (error) {
console.error('Error fetching plugin module:', error);
},
{
refreshDeps: [node, nodeId, onResetNode, getNodeVersion]
}
}, [getNodeVersion, node, nodeId, onResetNode, template]);
);
/* Node header */
const Header = useMemo(() => {
@@ -197,12 +194,7 @@ const NodeCard = (props: Props) => {
</MyTooltip>
)}
</Flex>
<MenuRender
nodeId={nodeId}
pluginId={pluginId}
flowNodeType={flowNodeType}
menuForbid={menuForbid}
/>
<MenuRender nodeId={nodeId} menuForbid={menuForbid} />
<NodeIntro nodeId={nodeId} intro={intro} />
</Box>
<ConfirmSyncModal />
@@ -219,8 +211,6 @@ const NodeCard = (props: Props) => {
appT,
onOpenConfirmSync,
onClickSyncVersion,
pluginId,
flowNodeType,
intro,
ConfirmSyncModal,
onOpenCustomTitleModal,
@@ -274,13 +264,9 @@ export default React.memo(NodeCard);
const MenuRender = React.memo(function MenuRender({
nodeId,
pluginId,
flowNodeType,
menuForbid
}: {
nodeId: string;
pluginId?: string;
flowNodeType: Props['flowNodeType'];
menuForbid?: Props['menuForbid'];
}) {
const { t } = useTranslation();

View File

@@ -164,8 +164,12 @@ const ChatHistorySlider = ({
px: 1
}}
list={[
{ label: t('core.chat.Recent use'), value: TabEnum.recently },
...(!isTeamChat ? [{ label: t('App'), value: TabEnum.app }] : []),
...(isTeamChat
? [{ label: t('App'), value: TabEnum.recently }]
: [
{ label: t('core.chat.Recent use'), value: TabEnum.recently },
{ label: t('App'), value: TabEnum.app }
]),
{ label: t('core.chat.History'), value: TabEnum.history }
]}
value={currentTab}
@@ -185,8 +189,8 @@ const ChatHistorySlider = ({
>
{t('core.chat.New Chat')}
</Button>
{(isPc || !showApps) && (
{/* Clear */}
{isPc && (
<IconButton
ml={3}
h={'100%'}

View File

@@ -8,6 +8,7 @@ import { moduleTemplatesFlat } from '@fastgpt/global/core/workflow/template/cons
import {
EDGE_TYPE,
FlowNodeInputTypeEnum,
FlowNodeOutputTypeEnum,
FlowNodeTypeEnum,
defaultNodeVersion
} from '@fastgpt/global/core/workflow/node/constant';
@@ -34,6 +35,7 @@ import { IfElseListItemType } from '@fastgpt/global/core/workflow/template/syste
import { VariableConditionEnum } from '@fastgpt/global/core/workflow/template/system/ifElse/constant';
import { AppChatConfigType } from '@fastgpt/global/core/app/type';
import { cloneDeep, isEqual } from 'lodash';
import { getInputComponentProps } from '@fastgpt/global/core/workflow/node/io/utils';
export const nodeTemplate2FlowNode = ({
template,
@@ -70,13 +72,24 @@ export const storeNode2FlowNode = ({
moduleTemplatesFlat.find((template) => template.flowNodeType === storeNode.flowNodeType) ||
EmptyNode;
const templateInputs = template.inputs.filter((input) => !input.canEdit);
const templateOutputs = template.outputs.filter(
(output) => output.type !== FlowNodeOutputTypeEnum.dynamic
);
const dynamicInput = template.inputs.find(
(input) => input.renderTypeList[0] === FlowNodeInputTypeEnum.addInputParam
);
// replace item data
const nodeItem: FlowNodeItemType = {
...template,
...storeNode,
version: storeNode.version ?? template.version ?? defaultNodeVersion,
inputs: template.inputs
/*
Inputs and outputs, New fields are added, not reduced
*/
inputs: templateInputs
.map<FlowNodeInputItemType>((templateInput) => {
const storeInput =
storeNode.inputs.find((item) => item.key === templateInput.key) || templateInput;
@@ -91,27 +104,35 @@ export const storeNode2FlowNode = ({
};
})
.concat(
/*
1. Plugin input
2. Old version adapt: Dynamic input will be added to the node inputs.
*/
storeNode.inputs.filter((item) => !template.inputs.find((input) => input.key === item.key))
/* Concat dynamic inputs */
storeNode.inputs
.filter((item) => !templateInputs.find((input) => input.key === item.key))
.map((item) => {
if (!dynamicInput) return item;
return {
...item,
...getInputComponentProps(dynamicInput)
};
})
),
outputs: template.outputs
outputs: templateOutputs
.map<FlowNodeOutputItemType>((templateOutput) => {
const storeOutput =
template.outputs.find((item) => item.key === templateOutput.key) || templateOutput;
return {
...storeOutput,
...templateOutput,
id: storeOutput.id ?? templateOutput.id,
label: storeOutput.label ?? templateOutput.label,
value: storeOutput.value ?? templateOutput.value
};
})
.concat(
storeNode.outputs.filter(
(item) => !template.outputs.find((output) => output.key === item.key)
(item) => !templateOutputs.find((output) => output.key === item.key)
)
)
};
@@ -365,37 +386,42 @@ export const getWorkflowGlobalVariables = ({
export type CombinedItemType = Partial<FlowNodeInputItemType> & Partial<FlowNodeOutputItemType>;
export const updateFlowNodeVersion = (
/* Reset node to latest version */
export const getLatestNodeTemplate = (
node: FlowNodeItemType,
template: FlowNodeTemplateType
): FlowNodeItemType => {
function updateArrayBasedOnTemplate<T extends FlowNodeInputItemType | FlowNodeOutputItemType>(
nodeArray: T[],
templateArray: T[]
): T[] {
return templateArray.map((templateItem) => {
const nodeItem = nodeArray.find((item) => item.key === templateItem.key);
if (nodeItem) {
return { ...templateItem, ...nodeItem } as T;
}
return { ...templateItem };
});
}
const updatedNode: FlowNodeItemType = {
...node,
...template,
inputs: template.inputs.map((templateItem) => {
const nodeItem = node.inputs.find((item) => item.key === templateItem.key);
if (nodeItem) {
return {
...templateItem,
value: nodeItem.value,
selectedTypeIndex: nodeItem.selectedTypeIndex,
valueType: nodeItem.valueType
};
}
return { ...templateItem };
}),
outputs: template.outputs.map((templateItem) => {
const nodeItem = node.outputs.find((item) => item.key === templateItem.key);
if (nodeItem) {
return {
...templateItem,
id: nodeItem.id,
value: nodeItem.value,
valueType: nodeItem.valueType
};
}
return { ...templateItem };
}),
name: node.name,
intro: node.intro
};
if (node.inputs && template.inputs) {
updatedNode.inputs = updateArrayBasedOnTemplate(node.inputs, template.inputs);
}
if (node.outputs && template.outputs) {
updatedNode.outputs = updateArrayBasedOnTemplate(node.outputs, template.outputs);
}
return updatedNode;
};