Ai histories (#1376)

* perf: workflow node ui

* i18n

* rename controller

* fix: zindex

* fix: leave page callback

* revert button
This commit is contained in:
Archer
2024-05-07 13:32:01 +08:00
committed by GitHub
parent eef609a063
commit 9e192c6d11
13 changed files with 116 additions and 111 deletions

View File

@@ -6,10 +6,10 @@
"i18n-ally.localesPaths": [ "i18n-ally.localesPaths": [
"projects/app/i18n", "projects/app/i18n",
], ],
"i18n-ally.enabledParsers": ["json"], "i18n-ally.enabledParsers": ["json", "yaml", "js", "ts"],
"i18n-ally.keystyle": "nested", "i18n-ally.keystyle": "nested",
"i18n-ally.sortKeys": true, "i18n-ally.sortKeys": true,
"i18n-ally.keepFulfilled": true, "i18n-ally.keepFulfilled": false,
"i18n-ally.sourceLanguage": "zh", // 根据此语言文件翻译其他语言文件的变量和内容 "i18n-ally.sourceLanguage": "zh", // 根据此语言文件翻译其他语言文件的变量和内容
"i18n-ally.displayLanguage": "zh", // 显示语言 "i18n-ally.displayLanguage": "zh" // 显示语言
} }

View File

@@ -1,6 +1,6 @@
import { FlowNodeTemplateTypeEnum, WorkflowIOValueTypeEnum } from '../../constants'; import { FlowNodeTemplateTypeEnum, WorkflowIOValueTypeEnum } from '../../constants';
import { getHandleConfig } from '../utils'; import { getHandleConfig } from '../utils';
import { FlowNodeTypeEnum } from '../../node/constant'; import { FlowNodeOutputTypeEnum, FlowNodeTypeEnum } from '../../node/constant';
import { VariableItemType } from '../../../app/type'; import { VariableItemType } from '../../../app/type';
import { FlowNodeTemplateType } from '../../type'; import { FlowNodeTemplateType } from '../../type';
@@ -25,6 +25,7 @@ export const getGlobalVariableNode = ({
id: item.key, id: item.key,
key: item.key, key: item.key,
valueType: WorkflowIOValueTypeEnum.string, valueType: WorkflowIOValueTypeEnum.string,
type: FlowNodeOutputTypeEnum.static,
label: item.label label: item.label
})) }))
}; };

View File

@@ -27,7 +27,7 @@ export const ToolModule: FlowNodeTemplateType = {
sourceHandle: getHandleConfig(true, true, false, true), sourceHandle: getHandleConfig(true, true, false, true),
targetHandle: getHandleConfig(true, true, false, true), targetHandle: getHandleConfig(true, true, false, true),
avatar: '/imgs/workflow/tool.svg', avatar: '/imgs/workflow/tool.svg',
name: '工具调用实验', name: '工具调用(实验)',
intro: '通过AI模型自动选择一个或多个功能块进行调用也可以对插件进行调用。', intro: '通过AI模型自动选择一个或多个功能块进行调用也可以对插件进行调用。',
showStatus: true, showStatus: true,
inputs: [ inputs: [

View File

@@ -47,7 +47,7 @@ export const filterToolNodeIdByEdges = ({
export const getHistories = (history?: ChatItemType[] | number, histories: ChatItemType[] = []) => { export const getHistories = (history?: ChatItemType[] | number, histories: ChatItemType[] = []) => {
if (!history) return []; if (!history) return [];
if (typeof history === 'number') return histories.slice(-history); if (typeof history === 'number') return histories.slice(-(history * 2));
if (Array.isArray(history)) return history; if (Array.isArray(history)) return history;
return []; return [];

View File

@@ -8,7 +8,7 @@ export const useBeforeunload = (props?: { callback?: () => any; tip?: string })
useEffect(() => { useEffect(() => {
const listen = const listen =
process.env.NODE_ENV !== 'production' process.env.NODE_ENV === 'production'
? (e: any) => { ? (e: any) => {
e.preventDefault(); e.preventDefault();
e.returnValue = tip; e.returnValue = tip;

View File

@@ -92,9 +92,9 @@ export const useEditTextarea = ({
closeBtnText?: string; closeBtnText?: string;
}) => ( }) => (
<MyModal isOpen={isOpen} onClose={onClose} iconSrc={iconSrc} title={title} maxW={'500px'}> <MyModal isOpen={isOpen} onClose={onClose} iconSrc={iconSrc} title={title} maxW={'500px'}>
<ModalBody> <ModalBody pt={tip ? '3 !important' : '5 !important'}>
{!!tip && ( {!!tip && (
<Box mb={2} color={'myGray.500'} fontSize={'sm'}> <Box mb={3} color={'myGray.500'} fontSize={'sm'}>
{tip} {tip}
</Box> </Box>
)} )}

View File

@@ -1,13 +1,11 @@
{ {
"App": "App", "App": "App",
"Create New": "Create New",
"Export": "Export", "Export": "Export",
"Folder": "Folder", "Folder": "Folder",
"Move": "Move", "Move": "Move",
"Name": "Name", "Name": "Name",
"Rename": "Rename", "Rename": "Rename",
"Running": "Running", "Running": "Running",
"Select value is empty": "Selected value is empty",
"UnKnow": "Unknown", "UnKnow": "Unknown",
"Warning": "Warning", "Warning": "Warning",
"app": { "app": {
@@ -646,7 +644,8 @@
"success": "Sync started" "success": "Sync started"
} }
}, },
"training": {} "training": {
}
}, },
"data": { "data": {
"Auxiliary Data": "Auxiliary Data", "Auxiliary Data": "Auxiliary Data",
@@ -1167,7 +1166,7 @@
}, },
"publish": { "publish": {
"OnRevert version": "Click back to that version", "OnRevert version": "Click back to that version",
"OnRevert version confirm": "Are you sure to roll back the version?", "OnRevert version confirm": "Sure you want to roll back to this version? The configuration for the version in editing is saved for you and a new release is created for the rollback version.",
"histories": "Publiish histories" "histories": "Publiish histories"
}, },
"tool": { "tool": {
@@ -1204,7 +1203,6 @@
"Select Dataset": "Select this knowledge base", "Select Dataset": "Select this knowledge base",
"Select Dataset Tips": "Only knowledge bases of the same index model can be selected", "Select Dataset Tips": "Only knowledge bases of the same index model can be selected",
"Select Folder": "Enter folder", "Select Folder": "Enter folder",
"System Data Queue": "Queue Length",
"Training Name": "Data Training", "Training Name": "Data Training",
"Upload Time": "Upload Time", "Upload Time": "Upload Time",
"collections": { "collections": {

View File

@@ -1,13 +1,11 @@
{ {
"App": "应用", "App": "应用",
"Create New": "",
"Export": "导出", "Export": "导出",
"Folder": "文件夹", "Folder": "文件夹",
"Move": "移动", "Move": "移动",
"Name": "名称", "Name": "名称",
"Rename": "重命名", "Rename": "重命名",
"Running": "运行中", "Running": "运行中",
"Select value is empty": "选择的内容为空",
"UnKnow": "未知", "UnKnow": "未知",
"Warning": "提示", "Warning": "提示",
"app": { "app": {
@@ -646,7 +644,8 @@
"success": "开始同步" "success": "开始同步"
} }
}, },
"training": {} "training": {
}
}, },
"data": { "data": {
"Auxiliary Data": "辅助数据", "Auxiliary Data": "辅助数据",
@@ -1167,7 +1166,7 @@
}, },
"publish": { "publish": {
"OnRevert version": "点击回退到该版本", "OnRevert version": "点击回退到该版本",
"OnRevert version confirm": "确认回退该版本?", "OnRevert version confirm": "确认回退该版本?会为您保存编辑中版本的配置,并为回退版本创建一个新的发布版本。",
"histories": "发布记录" "histories": "发布记录"
}, },
"tool": { "tool": {
@@ -1204,7 +1203,6 @@
"Select Dataset": "选择该知识库", "Select Dataset": "选择该知识库",
"Select Dataset Tips": "仅能选择同一个索引模型的知识库", "Select Dataset Tips": "仅能选择同一个索引模型的知识库",
"Select Folder": "进入文件夹", "Select Folder": "进入文件夹",
"System Data Queue": "排队长度",
"Training Name": "数据训练", "Training Name": "数据训练",
"Upload Time": "上传时间", "Upload Time": "上传时间",
"collections": { "collections": {

View File

@@ -99,7 +99,7 @@ const ChatTest = (
return ( return (
<> <>
<Flex <Flex
zIndex={3} zIndex={101}
flexDirection={'column'} flexDirection={'column'}
position={'absolute'} position={'absolute'}
top={5} top={5}

View File

@@ -90,7 +90,7 @@ const NodeDatasetConcat = ({ data, selected }: NodeProps<FlowNodeItemType>) => {
</Box> </Box>
<Box flex={'1 0 0'} /> <Box flex={'1 0 0'} />
<Button <Button
variant={'transparentBase'} variant={'whitePrimary'}
leftIcon={<SmallAddIcon />} leftIcon={<SmallAddIcon />}
iconSpacing={1} iconSpacing={1}
size={'sm'} size={'sm'}

View File

@@ -38,6 +38,8 @@ type Props = FlowNodeItemType & {
const NodeCard = (props: Props) => { const NodeCard = (props: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { toast } = useToast();
const { const {
children, children,
avatar = LOGO_ICON, avatar = LOGO_ICON,
@@ -59,6 +61,13 @@ const NodeCard = (props: Props) => {
const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList); const nodeList = useContextSelector(WorkflowContext, (v) => v.nodeList);
const setHoverNodeId = useContextSelector(WorkflowContext, (v) => v.setHoverNodeId); const setHoverNodeId = useContextSelector(WorkflowContext, (v) => v.setHoverNodeId);
const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError); const onUpdateNodeError = useContextSelector(WorkflowContext, (v) => v.onUpdateNodeError);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
// custom title edit
const { onOpenModal: onOpenCustomTitleModal, EditModal: EditTitleModal } = useEditTitle({
title: t('common.Custom Title'),
placeholder: t('app.module.Custom Title Tip') || ''
});
const showToolHandle = useMemo( const showToolHandle = useMemo(
() => isTool && !!nodeList.find((item) => item?.flowNodeType === FlowNodeTypeEnum.tools), () => isTool && !!nodeList.find((item) => item?.flowNodeType === FlowNodeTypeEnum.tools),
@@ -81,13 +90,42 @@ const NodeCard = (props: Props) => {
<Box ml={3} fontSize={'lg'} fontWeight={'medium'}> <Box ml={3} fontSize={'lg'} fontWeight={'medium'}>
{t(name)} {t(name)}
</Box> </Box>
{!menuForbid?.rename && (
<MyIcon
className="controller-rename"
display={'none'}
name={'edit'}
w={'14px'}
cursor={'pointer'}
ml={1}
color={'myGray.500'}
_hover={{ color: 'primary.600' }}
onClick={() => {
onOpenCustomTitleModal({
defaultVal: name,
onSuccess: (e) => {
if (!e) {
return toast({
title: t('app.modules.Title is required'),
status: 'warning'
});
}
onChangeNode({
nodeId,
type: 'attr',
key: 'name',
value: e
});
}
});
}}
/>
)}
</Flex> </Flex>
<MenuRender <MenuRender
name={name}
nodeId={nodeId} nodeId={nodeId}
pluginId={pluginId} pluginId={pluginId}
flowNodeType={flowNodeType} flowNodeType={flowNodeType}
inputs={inputs}
menuForbid={menuForbid} menuForbid={menuForbid}
/> />
<NodeIntro nodeId={nodeId} intro={intro} /> <NodeIntro nodeId={nodeId} intro={intro} />
@@ -101,11 +139,13 @@ const NodeCard = (props: Props) => {
avatar, avatar,
t, t,
name, name,
menuForbid,
pluginId, pluginId,
flowNodeType, flowNodeType,
inputs, intro,
menuForbid, onOpenCustomTitleModal,
intro onChangeNode,
toast
]); ]);
return ( return (
@@ -123,6 +163,9 @@ const NodeCard = (props: Props) => {
}, },
'& .controller-debug': { '& .controller-debug': {
display: 'block' display: 'block'
},
'& .controller-rename': {
display: 'block'
} }
}} }}
onMouseEnter={() => setHoverNodeId(nodeId)} onMouseEnter={() => setHoverNodeId(nodeId)}
@@ -140,6 +183,8 @@ const NodeCard = (props: Props) => {
{children} {children}
<ConnectionSourceHandle nodeId={nodeId} /> <ConnectionSourceHandle nodeId={nodeId} />
<ConnectionTargetHandle nodeId={nodeId} /> <ConnectionTargetHandle nodeId={nodeId} />
<EditTitleModal maxLength={20} />
</Box> </Box>
); );
}; };
@@ -147,18 +192,14 @@ const NodeCard = (props: Props) => {
export default React.memo(NodeCard); export default React.memo(NodeCard);
const MenuRender = React.memo(function MenuRender({ const MenuRender = React.memo(function MenuRender({
name,
nodeId, nodeId,
pluginId, pluginId,
flowNodeType, flowNodeType,
inputs,
menuForbid menuForbid
}: { }: {
name: string;
nodeId: string; nodeId: string;
pluginId?: string; pluginId?: string;
flowNodeType: Props['flowNodeType']; flowNodeType: Props['flowNodeType'];
inputs: Props['inputs'];
menuForbid?: Props['menuForbid']; menuForbid?: Props['menuForbid'];
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -169,11 +210,7 @@ const MenuRender = React.memo(function MenuRender({
const { openConfirm: onOpenConfirmSync, ConfirmModal: ConfirmSyncModal } = useConfirm({ const { openConfirm: onOpenConfirmSync, ConfirmModal: ConfirmSyncModal } = useConfirm({
content: t('module.Confirm Sync Plugin') content: t('module.Confirm Sync Plugin')
}); });
// custom title edit
const { onOpenModal: onOpenCustomTitleModal, EditModal: EditTitleModal } = useEditTitle({
title: t('common.Custom Title'),
placeholder: t('app.module.Custom Title Tip') || ''
});
const { openConfirm: onOpenConfirmDeleteNode, ConfirmModal: ConfirmDeleteModal } = useConfirm({ const { openConfirm: onOpenConfirmDeleteNode, ConfirmModal: ConfirmDeleteModal } = useConfirm({
content: t('core.module.Confirm Delete Node'), content: t('core.module.Confirm Delete Node'),
type: 'delete' type: 'delete'
@@ -182,7 +219,6 @@ const MenuRender = React.memo(function MenuRender({
const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes); const setNodes = useContextSelector(WorkflowContext, (v) => v.setNodes);
const onResetNode = useContextSelector(WorkflowContext, (v) => v.onResetNode); const onResetNode = useContextSelector(WorkflowContext, (v) => v.onResetNode);
const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges); const setEdges = useContextSelector(WorkflowContext, (v) => v.setEdges);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const onCopyNode = useCallback( const onCopyNode = useCallback(
(nodeId: string) => { (nodeId: string) => {
@@ -236,6 +272,16 @@ const MenuRender = React.memo(function MenuRender({
onClick: () => openDebugNode({ entryNodeId: nodeId }) onClick: () => openDebugNode({ entryNodeId: nodeId })
} }
]), ]),
...(menuForbid?.copy
? []
: [
{
icon: 'copy',
label: t('common.Copy'),
variant: 'whiteBase',
onClick: () => onCopyNode(nodeId)
}
]),
...(flowNodeType === FlowNodeTypeEnum.pluginModule ...(flowNodeType === FlowNodeTypeEnum.pluginModule
? [ ? [
{ {
@@ -264,43 +310,7 @@ const MenuRender = React.memo(function MenuRender({
} }
] ]
: []), : []),
...(menuForbid?.rename
? []
: [
{
icon: 'edit',
label: t('common.Rename'),
variant: 'whiteBase',
onClick: () =>
onOpenCustomTitleModal({
defaultVal: name,
onSuccess: (e) => {
if (!e) {
return toast({
title: t('app.modules.Title is required'),
status: 'warning'
});
}
onChangeNode({
nodeId,
type: 'attr',
key: 'name',
value: e
});
}
})
}
]),
...(menuForbid?.copy
? []
: [
{
icon: 'copy',
label: t('common.Copy'),
variant: 'whiteBase',
onClick: () => onCopyNode(nodeId)
}
]),
...(menuForbid?.delete ...(menuForbid?.delete
? [] ? []
: [ : [
@@ -342,7 +352,6 @@ const MenuRender = React.memo(function MenuRender({
</Box> </Box>
))} ))}
</Box> </Box>
<EditTitleModal maxLength={20} />
<ConfirmSyncModal /> <ConfirmSyncModal />
<ConfirmDeleteModal /> <ConfirmDeleteModal />
<DebugInputModal /> <DebugInputModal />
@@ -352,20 +361,15 @@ const MenuRender = React.memo(function MenuRender({
ConfirmDeleteModal, ConfirmDeleteModal,
ConfirmSyncModal, ConfirmSyncModal,
DebugInputModal, DebugInputModal,
EditTitleModal,
flowNodeType, flowNodeType,
menuForbid?.copy, menuForbid?.copy,
menuForbid?.debug, menuForbid?.debug,
menuForbid?.delete, menuForbid?.delete,
menuForbid?.rename,
name,
nodeId, nodeId,
onChangeNode,
onCopyNode, onCopyNode,
onDelNode, onDelNode,
onOpenConfirmDeleteNode, onOpenConfirmDeleteNode,
onOpenConfirmSync, onOpenConfirmSync,
onOpenCustomTitleModal,
onResetNode, onResetNode,
openDebugNode, openDebugNode,
pluginId, pluginId,
@@ -388,7 +392,7 @@ const NodeIntro = React.memo(function NodeIntro({
const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs); const splitToolInputs = useContextSelector(WorkflowContext, (ctx) => ctx.splitToolInputs);
const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode); const onChangeNode = useContextSelector(WorkflowContext, (v) => v.onChangeNode);
const moduleIsTool = useMemo(() => { const NodeIsTool = useMemo(() => {
const { isTool } = splitToolInputs([], nodeId); const { isTool } = splitToolInputs([], nodeId);
return isTool; return isTool;
}, [nodeId, splitToolInputs]); }, [nodeId, splitToolInputs]);
@@ -407,7 +411,7 @@ const NodeIntro = React.memo(function NodeIntro({
<Box fontSize={'xs'} color={'myGray.600'} flex={'1 0 0'}> <Box fontSize={'xs'} color={'myGray.600'} flex={'1 0 0'}>
{t(intro)} {t(intro)}
</Box> </Box>
{moduleIsTool && ( {NodeIsTool && (
<Button <Button
size={'xs'} size={'xs'}
variant={'whiteBase'} variant={'whiteBase'}
@@ -432,7 +436,7 @@ const NodeIntro = React.memo(function NodeIntro({
<EditIntroModal maxLength={500} /> <EditIntroModal maxLength={500} />
</> </>
); );
}, [EditIntroModal, intro, moduleIsTool, nodeId, onChangeNode, onOpenIntroModal, t]); }, [EditIntroModal, intro, NodeIsTool, nodeId, onChangeNode, onOpenIntroModal, t]);
return Render; return Render;
}); });

View File

@@ -413,7 +413,7 @@ const WorkflowContextProvider = ({
}); });
/* If the module is connected by a tool, the tool input and the normal input are separated */ /* If the module is connected by a tool, the tool input and the normal input are separated */
const splitToolInputs = useMemoizedFn((inputs: FlowNodeInputItemType[], nodeId: string) => { const splitToolInputs = (inputs: FlowNodeInputItemType[], nodeId: string) => {
const isTool = !!edges.find( const isTool = !!edges.find(
(edge) => edge.targetHandle === NodeOutputKeyEnum.selectedTools && edge.target === nodeId (edge) => edge.targetHandle === NodeOutputKeyEnum.selectedTools && edge.target === nodeId
); );
@@ -426,7 +426,7 @@ const WorkflowContextProvider = ({
return !item.toolDescription; return !item.toolDescription;
}) })
}; };
}); };
const initData = useMemoizedFn( const initData = useMemoizedFn(
async (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => { async (e: { nodes: StoreNodeItemType[]; edges: StoreEdgeItemType[] }) => {

View File

@@ -232,6 +232,8 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
<Box flex={1} /> <Box flex={1} />
{!isShowVersionHistories && (
<>
<MyMenu <MyMenu
Button={ Button={
<IconButton <IconButton
@@ -265,6 +267,8 @@ const RenderHeaderContainer = React.memo(function RenderHeaderContainer({
variant={'whitePrimary'} variant={'whitePrimary'}
onClick={() => setIsShowVersionHistories(true)} onClick={() => setIsShowVersionHistories(true)}
/> />
</>
)}
<Button <Button
size={'sm'} size={'sm'}