mirror of
https://gitee.com/ByteDance/flowgram.ai.git
synced 2025-07-07 17:43:29 +08:00
feat(runtime): flowgram workflow node.js runtime (#304)
* feat(runtime): init nodejs runtime * feat(runtime): init folder struct * feat(runtime): interface & test * feat(runtime): basic api & schema interfaces * feat(runtime): init runtime model framework * feat(runtime): create document & node entities * feat(runtime): runtime engine basic execute logic * feat(runtime): node add variable data * refactor(runtime): split to sub domains * test(runtime): document module test * feat(runtime): variable store * feat(runtime): workflow runtime executor * chore(demo): reset initial data * feat(runtime): workflow runtime branch logic * feat(runtime): workflow runtime access to ai model * feat(runtime): workflow runtime data all add to context * feat(runtime): workflow runtime invoke record snaphots * feat(runtime): workflow runtime status * feat(runtime): main api request processing chain * chore(demo): reset initial data * refactor(runtime): types move to interface package * feat(runtime): router access api defines & interfaces * feat(runtime): standardize api register & gen api docs * feat(runtime): create snapshot before node execute * fix(sub-canvas): tips cannot close * chore(demo): reset initial data * feat(demo): make node schema runnable * feat(demo): access test run * feat(runtime): runtime core can run in both browser & server env * fix(runtime): condition value empty issue * feat(runtime): beautify structure data view * feat(demo): test run sidesheet * chore(demo): test run sidesheet button fixed * feat(demo): running node show flowing line * chore(demo): hide node result overflow * chore(demo): reset initial data * feat(runtime): workflow runtime support loop node * fix(container): sub canvas height issue * feat(demo): test run multiple result render * test(runtime): enbale test coverage * refactor(runtime): interface folders structure * refactor(runtime): core folders structure * refactor(runtime): core export apis & access to router * feat(demo): runtime plugin * feat(runtime): server add try-catch protection * fix(runtime): node process reset end time * chore: format json * chore: rush update * refactor(demo): running service move to runtime-plugin as built-in runtime service * fix(runtime): build error * test(runtime): disable nodejs test * fix(demo): test run result key indent width
This commit is contained in:
parent
1668d9f26e
commit
aab4183d65
13
.vscode/settings.json
vendored
13
.vscode/settings.json
vendored
@ -69,7 +69,10 @@
|
|||||||
},
|
},
|
||||||
"search.useIgnoreFiles": true,
|
"search.useIgnoreFiles": true,
|
||||||
//
|
//
|
||||||
"editor.rulers": [80, 120],
|
"editor.rulers": [
|
||||||
|
80,
|
||||||
|
120
|
||||||
|
],
|
||||||
"files.eol": "\n",
|
"files.eol": "\n",
|
||||||
"files.trimTrailingWhitespace": true,
|
"files.trimTrailingWhitespace": true,
|
||||||
"files.insertFinalNewline": true,
|
"files.insertFinalNewline": true,
|
||||||
@ -97,7 +100,6 @@
|
|||||||
"scss.validate": false,
|
"scss.validate": false,
|
||||||
"less.validate": false,
|
"less.validate": false,
|
||||||
"emmet.triggerExpansionOnTab": true,
|
"emmet.triggerExpansionOnTab": true,
|
||||||
|
|
||||||
"[yaml]": {
|
"[yaml]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
@ -108,10 +110,10 @@
|
|||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[json]": {
|
"[json]": {
|
||||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
"editor.defaultFormatter": "vscode.json-language-features"
|
||||||
},
|
},
|
||||||
"[jsonc]": {
|
"[jsonc]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "vscode.json-language-features"
|
||||||
},
|
},
|
||||||
"[less]": {
|
"[less]": {
|
||||||
"editor.defaultFormatter": "vscode.css-language-features"
|
"editor.defaultFormatter": "vscode.css-language-features"
|
||||||
@ -125,9 +127,6 @@
|
|||||||
"[typescriptreact]": {
|
"[typescriptreact]": {
|
||||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||||
},
|
},
|
||||||
"[scss]": {
|
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
||||||
},
|
|
||||||
"[ignore]": {
|
"[ignore]": {
|
||||||
"editor.defaultFormatter": "foxundermoon.shell-format"
|
"editor.defaultFormatter": "foxundermoon.shell-format"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -30,6 +30,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@douyinfe/semi-icons": "^2.80.0",
|
"@douyinfe/semi-icons": "^2.80.0",
|
||||||
"@douyinfe/semi-ui": "^2.80.0",
|
"@douyinfe/semi-ui": "^2.80.0",
|
||||||
|
"@flowgram.ai/runtime-interface": "workspace:*",
|
||||||
"@flowgram.ai/free-layout-editor": "workspace:*",
|
"@flowgram.ai/free-layout-editor": "workspace:*",
|
||||||
"@flowgram.ai/free-snap-plugin": "workspace:*",
|
"@flowgram.ai/free-snap-plugin": "workspace:*",
|
||||||
"@flowgram.ai/free-lines-plugin": "workspace:*",
|
"@flowgram.ai/free-lines-plugin": "workspace:*",
|
||||||
@ -38,6 +39,7 @@
|
|||||||
"@flowgram.ai/free-container-plugin": "workspace:*",
|
"@flowgram.ai/free-container-plugin": "workspace:*",
|
||||||
"@flowgram.ai/free-group-plugin": "workspace:*",
|
"@flowgram.ai/free-group-plugin": "workspace:*",
|
||||||
"@flowgram.ai/form-materials": "workspace:*",
|
"@flowgram.ai/form-materials": "workspace:*",
|
||||||
|
"@flowgram.ai/runtime-js": "workspace:*",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"nanoid": "^4.0.2",
|
"nanoid": "^4.0.2",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useCallback } from 'react';
|
|||||||
import { FlowNodeEntity, useNodeRender } from '@flowgram.ai/free-layout-editor';
|
import { FlowNodeEntity, useNodeRender } from '@flowgram.ai/free-layout-editor';
|
||||||
import { ConfigProvider } from '@douyinfe/semi-ui';
|
import { ConfigProvider } from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
|
import { NodeStatusBar } from '../testrun/node-status-bar';
|
||||||
import { NodeRenderContext } from '../../context';
|
import { NodeRenderContext } from '../../context';
|
||||||
import { ErrorIcon } from './styles';
|
import { ErrorIcon } from './styles';
|
||||||
import { NodeWrapper } from './node-wrapper';
|
import { NodeWrapper } from './node-wrapper';
|
||||||
@ -32,6 +33,7 @@ export const BaseNode = ({ node }: { node: FlowNodeEntity }) => {
|
|||||||
{form?.state.invalid && <ErrorIcon />}
|
{form?.state.invalid && <ErrorIcon />}
|
||||||
{form?.render()}
|
{form?.render()}
|
||||||
</NodeWrapper>
|
</NodeWrapper>
|
||||||
|
<NodeStatusBar />
|
||||||
</NodeRenderContext.Provider>
|
</NodeRenderContext.Provider>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
.node-status-group {
|
||||||
|
padding: 6px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
font-size: 15px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
import { FC, useState } from 'react';
|
||||||
|
|
||||||
|
import { IconSmallTriangleDown } from '@douyinfe/semi-icons';
|
||||||
|
|
||||||
|
import { DataStructureViewer } from '../viewer';
|
||||||
|
|
||||||
|
import './index.css';
|
||||||
|
import { Tag } from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
|
interface NodeStatusGroupProps {
|
||||||
|
title: string;
|
||||||
|
data: unknown;
|
||||||
|
optional?: boolean;
|
||||||
|
disableCollapse?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isObjectHasContent = (obj: any = {}): boolean => Object.keys(obj).length > 0;
|
||||||
|
|
||||||
|
export const NodeStatusGroup: FC<NodeStatusGroupProps> = ({
|
||||||
|
title,
|
||||||
|
data,
|
||||||
|
optional = false,
|
||||||
|
disableCollapse = false,
|
||||||
|
}) => {
|
||||||
|
const hasContent = isObjectHasContent(data);
|
||||||
|
const [isExpanded, setIsExpanded] = useState(true);
|
||||||
|
|
||||||
|
if (optional && !hasContent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="node-status-group" onClick={() => hasContent && setIsExpanded(!isExpanded)}>
|
||||||
|
{!disableCollapse && (
|
||||||
|
<IconSmallTriangleDown
|
||||||
|
style={{
|
||||||
|
transform: isExpanded ? 'rotate(0deg)' : 'rotate(-90deg)',
|
||||||
|
transition: 'transform 0.2s',
|
||||||
|
cursor: 'pointer',
|
||||||
|
marginRight: '4px',
|
||||||
|
opacity: hasContent ? 1 : 0,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span>{title}:</span>
|
||||||
|
{!hasContent && (
|
||||||
|
<Tag
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
marginLeft: 4,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
null
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{hasContent && isExpanded ? <DataStructureViewer data={data} /> : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import { IconChevronDown } from '@douyinfe/semi-icons';
|
||||||
|
|
||||||
|
import { useNodeRenderContext } from '../../../../hooks';
|
||||||
|
import { NodeStatusHeaderContentStyle, NodeStatusHeaderStyle } from './style';
|
||||||
|
|
||||||
|
interface NodeStatusBarProps {
|
||||||
|
header?: React.ReactNode;
|
||||||
|
defaultShowDetail?: boolean;
|
||||||
|
extraBtns?: React.ReactNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NodeStatusHeader: React.FC<React.PropsWithChildren<NodeStatusBarProps>> = ({
|
||||||
|
header,
|
||||||
|
defaultShowDetail,
|
||||||
|
children,
|
||||||
|
extraBtns = [],
|
||||||
|
}) => {
|
||||||
|
const [showDetail, setShowDetail] = useState(defaultShowDetail);
|
||||||
|
const { selectNode } = useNodeRenderContext();
|
||||||
|
|
||||||
|
const handleToggleShowDetail = (e: React.MouseEvent) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
selectNode(e);
|
||||||
|
setShowDetail(!showDetail);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NodeStatusHeaderStyle
|
||||||
|
// 必须要禁止 down 冒泡,防止判定圈选和 node hover(不支持多边形)
|
||||||
|
onMouseDown={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<NodeStatusHeaderContentStyle
|
||||||
|
className={showDetail ? 'status-header-opened' : ''}
|
||||||
|
// 必须要禁止 down 冒泡,防止判定圈选和 node hover(不支持多边形)
|
||||||
|
onMouseDown={(e) => e.stopPropagation()}
|
||||||
|
// 其他事件统一走点击事件,且也需要阻止冒泡
|
||||||
|
onClick={handleToggleShowDetail}
|
||||||
|
>
|
||||||
|
<div className="status-title">
|
||||||
|
{header}
|
||||||
|
{extraBtns.length > 0 ? extraBtns : null}
|
||||||
|
</div>
|
||||||
|
<div className="status-btns">
|
||||||
|
<IconChevronDown className={showDetail ? 'is-show-detail' : ''} />
|
||||||
|
</div>
|
||||||
|
</NodeStatusHeaderContentStyle>
|
||||||
|
{showDetail ? children : null}
|
||||||
|
</NodeStatusHeaderStyle>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const NodeStatusHeaderStyle = styled.div`
|
||||||
|
border: 1px solid rgba(68, 83, 130, 0.25);
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: #fff;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 8px);
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const NodeStatusHeaderContentStyle = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 6px;
|
||||||
|
|
||||||
|
&-opened {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-title {
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 8px;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
:global {
|
||||||
|
.coz-tag {
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
.semi-tag-content {
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.semi-tag-suffix-icon > div {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.status-btns {
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
column-gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-show-detail {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
interface Props {
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IconSuccessFill = ({ className, style }: Props) => (
|
||||||
|
<svg
|
||||||
|
className={className}
|
||||||
|
style={style}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
>
|
||||||
|
<g clipPath="url(#icon-workflow-run-success_svg__a)">
|
||||||
|
<path
|
||||||
|
fill="#3EC254"
|
||||||
|
d="M.833 10A9.166 9.166 0 0 0 10 19.168a9.166 9.166 0 0 0 9.167-9.166A9.166 9.166 0 0 0 10 .834a9.166 9.166 0 0 0-9.167 9.167"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
fill="#fff"
|
||||||
|
d="M6.077 9.755a.833.833 0 0 0 0 1.179l2.357 2.357a.833.833 0 0 0 1.179 0l4.714-4.714a.833.833 0 1 0-1.178-1.179l-4.125 4.125-1.768-1.768a.833.833 0 0 0-1.179 0"
|
||||||
|
></path>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="icon-workflow-run-success_svg__a">
|
||||||
|
<path fill="#fff" d="M0 0h20v20H0z"></path>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
interface Props {
|
||||||
|
className?: string;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IconWarningFill = ({ className, style }: Props) => (
|
||||||
|
<svg
|
||||||
|
className={className}
|
||||||
|
style={style}
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M23 12C23 18.0751 18.0751 23 12 23C5.92487 23 1 18.0751 1 12C1 5.92487 5.92487 1 12 1C18.0751 1 23 5.92487 23 12ZM11 8C11 7.44772 11.4477 7 12 7C12.5523 7 13 7.44772 13 8V13C13 13.5523 12.5523 14 12 14C11.4477 14 11 13.5523 11 13V8ZM11 16C11 15.4477 11.4477 15 12 15C12.5523 15 13 15.4477 13 16C13 16.5523 12.5523 17 12 17C11.4477 17 11 16.5523 11 16Z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { NodeReport } from '@flowgram.ai/runtime-interface';
|
||||||
|
import { useCurrentEntity, useService } from '@flowgram.ai/free-layout-editor';
|
||||||
|
|
||||||
|
import { WorkflowRuntimeService } from '../../../plugins/runtime-plugin/runtime-service';
|
||||||
|
import { NodeStatusRender } from './render';
|
||||||
|
|
||||||
|
const useNodeReport = () => {
|
||||||
|
const node = useCurrentEntity();
|
||||||
|
const [report, setReport] = useState<NodeReport>();
|
||||||
|
|
||||||
|
const runtimeService = useService(WorkflowRuntimeService);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const reportDisposer = runtimeService.onNodeReportChange((nodeReport) => {
|
||||||
|
if (nodeReport.id !== node.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setReport(nodeReport);
|
||||||
|
});
|
||||||
|
const resetDisposer = runtimeService.onReset(() => {
|
||||||
|
setReport(undefined);
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
reportDisposer.dispose();
|
||||||
|
resetDisposer.dispose();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return report;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NodeStatusBar = () => {
|
||||||
|
const report = useNodeReport();
|
||||||
|
|
||||||
|
if (!report) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <NodeStatusRender report={report} />;
|
||||||
|
};
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
.node-status-succeed {
|
||||||
|
background-color: rgba(105, 209, 140, 0.3);
|
||||||
|
color: rgba(0, 178, 60, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-status-processing {
|
||||||
|
background-color: rgba(153, 187, 255, 0.3);
|
||||||
|
color: rgba(61, 121, 242, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-status-failed {
|
||||||
|
background-color: rgba(255, 163, 171, 0.3);
|
||||||
|
color: rgba(229, 50, 65, 1);
|
||||||
|
}
|
||||||
@ -0,0 +1,233 @@
|
|||||||
|
import { FC, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { NodeReport, WorkflowStatus } from '@flowgram.ai/runtime-interface';
|
||||||
|
import { Tag, Button, Select } from '@douyinfe/semi-ui';
|
||||||
|
import { IconSpin } from '@douyinfe/semi-icons';
|
||||||
|
|
||||||
|
import { IconWarningFill } from '../icon/warning';
|
||||||
|
import { IconSuccessFill } from '../icon/success';
|
||||||
|
import { NodeStatusHeader } from '../header';
|
||||||
|
import './index.css';
|
||||||
|
import { NodeStatusGroup } from '../group';
|
||||||
|
|
||||||
|
interface NodeStatusRenderProps {
|
||||||
|
report: NodeReport;
|
||||||
|
}
|
||||||
|
|
||||||
|
const msToSeconds = (ms: number): string => (ms / 1000).toFixed(2) + 's';
|
||||||
|
const displayCount = 6;
|
||||||
|
|
||||||
|
export const NodeStatusRender: FC<NodeStatusRenderProps> = ({ report }) => {
|
||||||
|
const { status: nodeStatus } = report;
|
||||||
|
const [currentSnapshotIndex, setCurrentSnapshotIndex] = useState(0);
|
||||||
|
|
||||||
|
const snapshots = report.snapshots || [];
|
||||||
|
const currentSnapshot = snapshots[currentSnapshotIndex] || snapshots[0];
|
||||||
|
|
||||||
|
// 节点 5 个状态
|
||||||
|
const isNodePending = nodeStatus === WorkflowStatus.Pending;
|
||||||
|
const isNodeProcessing = nodeStatus === WorkflowStatus.Processing;
|
||||||
|
const isNodeFailed = nodeStatus === WorkflowStatus.Failed;
|
||||||
|
const isNodeSucceed = nodeStatus === WorkflowStatus.Succeeded;
|
||||||
|
const isNodeCanceled = nodeStatus === WorkflowStatus.Canceled;
|
||||||
|
|
||||||
|
const tagColor = useMemo(() => {
|
||||||
|
if (isNodeSucceed) {
|
||||||
|
return 'node-status-succeed';
|
||||||
|
}
|
||||||
|
if (isNodeFailed) {
|
||||||
|
return 'node-status-failed';
|
||||||
|
}
|
||||||
|
if (isNodeProcessing) {
|
||||||
|
return 'node-status-processing';
|
||||||
|
}
|
||||||
|
}, [isNodeSucceed, isNodeFailed, isNodeProcessing]);
|
||||||
|
|
||||||
|
const renderIcon = () => {
|
||||||
|
if (isNodeProcessing) {
|
||||||
|
return (
|
||||||
|
<IconSpin
|
||||||
|
spin
|
||||||
|
style={{
|
||||||
|
color: 'rgba(77,83,232,1',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (isNodeSucceed) {
|
||||||
|
return <IconSuccessFill />;
|
||||||
|
}
|
||||||
|
return <IconWarningFill className={tagColor} />;
|
||||||
|
};
|
||||||
|
const renderDesc = () => {
|
||||||
|
const getDesc = () => {
|
||||||
|
if (isNodeProcessing) {
|
||||||
|
return 'Running';
|
||||||
|
} else if (isNodePending) {
|
||||||
|
return 'Run terminated';
|
||||||
|
} else if (isNodeSucceed) {
|
||||||
|
return 'Succeed';
|
||||||
|
} else if (isNodeFailed) {
|
||||||
|
return 'Failed';
|
||||||
|
} else if (isNodeCanceled) {
|
||||||
|
return 'Canceled';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const desc = getDesc();
|
||||||
|
|
||||||
|
return desc ? <p style={{ margin: 0 }}>{desc}</p> : null;
|
||||||
|
};
|
||||||
|
const renderCost = () => (
|
||||||
|
<Tag size="small" className={tagColor}>
|
||||||
|
{msToSeconds(report.timeCost)}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderSnapshotNavigation = () => {
|
||||||
|
if (snapshots.length <= 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = (
|
||||||
|
<p
|
||||||
|
style={{
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#333',
|
||||||
|
fontSize: '15px',
|
||||||
|
marginLeft: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Total: {snapshots.length}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (snapshots.length <= displayCount) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{count}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
margin: '12px',
|
||||||
|
display: 'flex',
|
||||||
|
gap: '8px',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{snapshots.map((_, index) => (
|
||||||
|
<Button
|
||||||
|
key={index}
|
||||||
|
size="small"
|
||||||
|
type={currentSnapshotIndex === index ? 'primary' : 'tertiary'}
|
||||||
|
onClick={() => setCurrentSnapshotIndex(index)}
|
||||||
|
style={{
|
||||||
|
minWidth: '32px',
|
||||||
|
height: '32px',
|
||||||
|
padding: '0',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor:
|
||||||
|
currentSnapshotIndex === index ? '#4d53e8' : 'rgba(29, 28, 35, 0.08)',
|
||||||
|
fontWeight: currentSnapshotIndex === index ? '800' : '500',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{index + 1}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 超过5个时,前5个显示为按钮,剩余的放在下拉选择中
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{count}
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
margin: '12px',
|
||||||
|
display: 'flex',
|
||||||
|
gap: '8px',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{snapshots.slice(0, displayCount).map((_, index) => (
|
||||||
|
<Button
|
||||||
|
key={index}
|
||||||
|
size="small"
|
||||||
|
type="tertiary"
|
||||||
|
onClick={() => setCurrentSnapshotIndex(index)}
|
||||||
|
style={{
|
||||||
|
minWidth: '32px',
|
||||||
|
height: '32px',
|
||||||
|
padding: '0',
|
||||||
|
borderRadius: '4px',
|
||||||
|
fontSize: '12px',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: currentSnapshotIndex === index ? '#4d53e8' : 'rgba(29, 28, 35, 0.08)',
|
||||||
|
fontWeight: currentSnapshotIndex === index ? '800' : '500',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{index + 1}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
<Select
|
||||||
|
value={currentSnapshotIndex >= displayCount ? currentSnapshotIndex : undefined}
|
||||||
|
onChange={(value) => setCurrentSnapshotIndex(value as number)}
|
||||||
|
style={{
|
||||||
|
width: '100px',
|
||||||
|
height: '32px',
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor:
|
||||||
|
currentSnapshotIndex >= displayCount ? '#4d53e8' : 'rgba(29, 28, 35, 0.08)',
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
placeholder="Select"
|
||||||
|
>
|
||||||
|
{snapshots.slice(displayCount).map((_, index) => {
|
||||||
|
const actualIndex = index + displayCount;
|
||||||
|
return (
|
||||||
|
<Select.Option key={actualIndex} value={actualIndex}>
|
||||||
|
{actualIndex + 1}
|
||||||
|
</Select.Option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!report) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NodeStatusHeader
|
||||||
|
header={
|
||||||
|
<>
|
||||||
|
{renderIcon()}
|
||||||
|
{renderDesc()}
|
||||||
|
{renderCost()}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
padding: '0px 2px 10px 2px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{renderSnapshotNavigation()}
|
||||||
|
<NodeStatusGroup title="Inputs" data={currentSnapshot?.inputs} />
|
||||||
|
<NodeStatusGroup title="Outputs" data={currentSnapshot?.outputs} />
|
||||||
|
<NodeStatusGroup title="Branch" data={currentSnapshot?.branch} optional />
|
||||||
|
<NodeStatusGroup title="Data" data={currentSnapshot?.data} optional />
|
||||||
|
</div>
|
||||||
|
</NodeStatusHeader>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,137 @@
|
|||||||
|
.node-status-data-structure-viewer {
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #333;
|
||||||
|
background: #fafafa;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px 12px 12px 0;
|
||||||
|
margin: 12px;
|
||||||
|
border: 1px solid #e1e4e8;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node {
|
||||||
|
margin: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 4px;
|
||||||
|
min-height: 20px;
|
||||||
|
padding: 2px 0;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: background-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-header:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #666;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-button:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-button.expanded {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-button.collapsed {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-placeholder {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: inline-block;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-label {
|
||||||
|
color: #0969da;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: auto;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-label:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-value {
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primitive-value-quote {
|
||||||
|
color: #8f8f8f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primitive-value {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: all;
|
||||||
|
padding: 1px 3px;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: background-color 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primitive-value:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.primitive-value.string {
|
||||||
|
color: #032f62;
|
||||||
|
background-color: rgba(3, 47, 98, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.primitive-value.number {
|
||||||
|
color: #005cc5;
|
||||||
|
background-color: rgba(0, 92, 197, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.primitive-value.boolean {
|
||||||
|
color: #e36209;
|
||||||
|
background-color: rgba(227, 98, 9, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.primitive-value.null,
|
||||||
|
.primitive-value.undefined {
|
||||||
|
color: #6a737d;
|
||||||
|
font-style: italic;
|
||||||
|
background-color: rgba(106, 115, 125, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-children {
|
||||||
|
margin-left: 8px;
|
||||||
|
padding-left: 8px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-node-children::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 1px;
|
||||||
|
background: #e1e4e8;
|
||||||
|
}
|
||||||
@ -0,0 +1,154 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import './index.css';
|
||||||
|
import { Toast } from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
|
interface DataStructureViewerProps {
|
||||||
|
data: any;
|
||||||
|
level?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TreeNodeProps {
|
||||||
|
label: string;
|
||||||
|
value: any;
|
||||||
|
level: number;
|
||||||
|
isLast?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TreeNode: React.FC<TreeNodeProps> = ({ label, value, level, isLast = false }) => {
|
||||||
|
const [isExpanded, setIsExpanded] = useState(true);
|
||||||
|
|
||||||
|
const handleCopy = (text: string) => {
|
||||||
|
navigator.clipboard.writeText(text);
|
||||||
|
Toast.success('Copied');
|
||||||
|
};
|
||||||
|
|
||||||
|
const isExpandable = (val: any) =>
|
||||||
|
val !== null &&
|
||||||
|
typeof val === 'object' &&
|
||||||
|
((Array.isArray(val) && val.length > 0) ||
|
||||||
|
(!Array.isArray(val) && Object.keys(val).length > 0));
|
||||||
|
|
||||||
|
const renderPrimitiveValue = (val: any) => {
|
||||||
|
if (val === null) return <span className="primitive-value null">null</span>;
|
||||||
|
if (val === undefined) return <span className="primitive-value undefined">undefined</span>;
|
||||||
|
|
||||||
|
switch (typeof val) {
|
||||||
|
case 'string':
|
||||||
|
return (
|
||||||
|
<span className="string">
|
||||||
|
<span className="primitive-value-quote">{'"'}</span>
|
||||||
|
<span className="primitive-value" onDoubleClick={() => handleCopy(val)}>
|
||||||
|
{val}
|
||||||
|
</span>
|
||||||
|
<span className="primitive-value-quote">{'"'}</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
case 'number':
|
||||||
|
return (
|
||||||
|
<span className="primitive-value number" onDoubleClick={() => handleCopy(String(val))}>
|
||||||
|
{val}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
case 'boolean':
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className="primitive-value boolean"
|
||||||
|
onDoubleClick={() => handleCopy(val.toString())}
|
||||||
|
>
|
||||||
|
{val.toString()}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<span className="primitive-value" onDoubleClick={() => handleCopy(String(val))}>
|
||||||
|
{String(val)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderChildren = () => {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map((item, index) => (
|
||||||
|
<TreeNode
|
||||||
|
key={index}
|
||||||
|
label={`${index + 1}.`}
|
||||||
|
value={item}
|
||||||
|
level={level + 1}
|
||||||
|
isLast={index === value.length - 1}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
const entries = Object.entries(value);
|
||||||
|
return entries.map(([key, val], index) => (
|
||||||
|
<TreeNode
|
||||||
|
key={key}
|
||||||
|
label={`${key}:`}
|
||||||
|
value={val}
|
||||||
|
level={level + 1}
|
||||||
|
isLast={index === entries.length - 1}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="tree-node">
|
||||||
|
<div className="tree-node-header">
|
||||||
|
{isExpandable(value) ? (
|
||||||
|
<button
|
||||||
|
className={`expand-button ${isExpanded ? 'expanded' : 'collapsed'}`}
|
||||||
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
|
>
|
||||||
|
▶
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<span className="expand-placeholder"></span>
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
className="node-label"
|
||||||
|
onClick={() =>
|
||||||
|
handleCopy(
|
||||||
|
JSON.stringify({
|
||||||
|
[label]: value,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
{!isExpandable(value) && <span className="node-value">{renderPrimitiveValue(value)}</span>}
|
||||||
|
</div>
|
||||||
|
{isExpandable(value) && isExpanded && (
|
||||||
|
<div className="tree-node-children">{renderChildren()}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DataStructureViewer: React.FC<DataStructureViewerProps> = ({ data, level = 0 }) => {
|
||||||
|
if (data === null || data === undefined || typeof data !== 'object') {
|
||||||
|
return (
|
||||||
|
<div className="node-status-data-structure-viewer">
|
||||||
|
<TreeNode label="value" value={data} level={0} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries = Object.entries(data);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="node-status-data-structure-viewer">
|
||||||
|
{entries.map(([key, value], index) => (
|
||||||
|
<TreeNode
|
||||||
|
key={key}
|
||||||
|
label={key}
|
||||||
|
value={value}
|
||||||
|
level={0}
|
||||||
|
isLast={index === entries.length - 1}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
|
|
||||||
|
import { useClientContext, getNodeForm, FlowNodeEntity } from '@flowgram.ai/free-layout-editor';
|
||||||
|
import { Button, Badge, SideSheet } from '@douyinfe/semi-ui';
|
||||||
|
import { IconPlay } from '@douyinfe/semi-icons';
|
||||||
|
|
||||||
|
import { TestRunSideSheet } from '../testrun-sidesheet';
|
||||||
|
|
||||||
|
export function TestRunButton(props: { disabled: boolean }) {
|
||||||
|
const [errorCount, setErrorCount] = useState(0);
|
||||||
|
const clientContext = useClientContext();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
|
const updateValidateData = useCallback(() => {
|
||||||
|
const allForms = clientContext.document.getAllNodes().map((node) => getNodeForm(node));
|
||||||
|
const count = allForms.filter((form) => form?.state.invalid).length;
|
||||||
|
setErrorCount(count);
|
||||||
|
}, [clientContext]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate all node and Save
|
||||||
|
*/
|
||||||
|
const onTestRun = useCallback(async () => {
|
||||||
|
const allForms = clientContext.document.getAllNodes().map((node) => getNodeForm(node));
|
||||||
|
await Promise.all(allForms.map(async (form) => form?.validate()));
|
||||||
|
console.log('>>>>> save data: ', clientContext.document.toJSON());
|
||||||
|
setVisible(true);
|
||||||
|
}, [clientContext]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen single node validate
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
const listenSingleNodeValidate = (node: FlowNodeEntity) => {
|
||||||
|
const form = getNodeForm(node);
|
||||||
|
if (form) {
|
||||||
|
const formValidateDispose = form.onValidate(() => updateValidateData());
|
||||||
|
node.onDispose(() => formValidateDispose.dispose());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
clientContext.document.getAllNodes().map((node) => listenSingleNodeValidate(node));
|
||||||
|
const dispose = clientContext.document.onNodeCreate(({ node }) =>
|
||||||
|
listenSingleNodeValidate(node)
|
||||||
|
);
|
||||||
|
return () => dispose.dispose();
|
||||||
|
}, [clientContext]);
|
||||||
|
|
||||||
|
const button =
|
||||||
|
errorCount === 0 ? (
|
||||||
|
<Button
|
||||||
|
disabled={props.disabled}
|
||||||
|
onClick={onTestRun}
|
||||||
|
icon={<IconPlay size="small" />}
|
||||||
|
style={{ backgroundColor: 'rgba(0,178,60,1)', borderRadius: '8px', color: '#fff' }}
|
||||||
|
>
|
||||||
|
Test Run
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Badge count={errorCount} position="rightTop" type="danger">
|
||||||
|
<Button
|
||||||
|
type="danger"
|
||||||
|
disabled={props.disabled}
|
||||||
|
onClick={onTestRun}
|
||||||
|
icon={<IconPlay size="small" />}
|
||||||
|
style={{ backgroundColor: 'rgba(255,115,0, 1)', borderRadius: '8px', color: '#fff' }}
|
||||||
|
>
|
||||||
|
Test Run
|
||||||
|
</Button>
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{button}
|
||||||
|
<TestRunSideSheet visible={visible} onCancel={() => setVisible((v) => !v)} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,138 @@
|
|||||||
|
import { FC, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { WorkflowInputs, WorkflowOutputs } from '@flowgram.ai/runtime-interface';
|
||||||
|
import { useService } from '@flowgram.ai/free-layout-editor';
|
||||||
|
import { Button, JsonViewer, SideSheet } from '@douyinfe/semi-ui';
|
||||||
|
import { IconPlay, IconSpin, IconStop } from '@douyinfe/semi-icons';
|
||||||
|
|
||||||
|
import { NodeStatusGroup } from '../node-status-bar/group';
|
||||||
|
import { WorkflowRuntimeService } from '../../../plugins/runtime-plugin/runtime-service';
|
||||||
|
|
||||||
|
interface TestRunSideSheetProps {
|
||||||
|
visible: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TestRunSideSheet: FC<TestRunSideSheetProps> = ({ visible, onCancel }) => {
|
||||||
|
const runtimeService = useService(WorkflowRuntimeService);
|
||||||
|
const [isRunning, setRunning] = useState(false);
|
||||||
|
const [value, setValue] = useState<string>(`{}`);
|
||||||
|
const [error, setError] = useState<string | undefined>();
|
||||||
|
const [result, setResult] = useState<
|
||||||
|
| {
|
||||||
|
inputs: WorkflowInputs;
|
||||||
|
outputs: WorkflowOutputs;
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
>();
|
||||||
|
|
||||||
|
const onTestRun = async () => {
|
||||||
|
if (isRunning) {
|
||||||
|
await runtimeService.taskCancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setResult(undefined);
|
||||||
|
setError(undefined);
|
||||||
|
setRunning(true);
|
||||||
|
try {
|
||||||
|
await runtimeService.taskRun(value);
|
||||||
|
} catch (e: any) {
|
||||||
|
setError(e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClose = async () => {
|
||||||
|
await runtimeService.taskCancel();
|
||||||
|
setValue(`{}`);
|
||||||
|
setRunning(false);
|
||||||
|
onCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const disposer = runtimeService.onTerminated(({ result }) => {
|
||||||
|
setRunning(false);
|
||||||
|
setResult(result);
|
||||||
|
});
|
||||||
|
return () => disposer.dispose();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const renderRunning = (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '80%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconSpin spin size="large" />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '18px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Running...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderForm = (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: '15px',
|
||||||
|
fontWeight: '500',
|
||||||
|
marginBottom: '10px',
|
||||||
|
color: '#333',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Input
|
||||||
|
</div>
|
||||||
|
<JsonViewer showSearch={false} height={300} value={value} onChange={setValue} />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
color: 'red',
|
||||||
|
fontSize: '14px',
|
||||||
|
marginTop: '30px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<NodeStatusGroup title="Inputs" data={result?.inputs} optional disableCollapse />
|
||||||
|
<NodeStatusGroup title="Outputs" data={result?.outputs} optional disableCollapse />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderButton = (
|
||||||
|
<Button
|
||||||
|
onClick={onTestRun}
|
||||||
|
icon={isRunning ? <IconStop size="small" /> : <IconPlay size="small" />}
|
||||||
|
style={{
|
||||||
|
backgroundColor: isRunning ? 'rgba(87,104,161,0.08)' : 'rgba(0,178,60,1)',
|
||||||
|
borderRadius: '8px',
|
||||||
|
color: isRunning ? 'rgba(15,21,40,0.82)' : '#fff',
|
||||||
|
marginBottom: '16px',
|
||||||
|
width: '100%',
|
||||||
|
height: '40px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isRunning ? 'Cancel' : 'Test Run'}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SideSheet
|
||||||
|
title="Test Run"
|
||||||
|
visible={visible}
|
||||||
|
mask={false}
|
||||||
|
onCancel={onClose}
|
||||||
|
footer={renderButton}
|
||||||
|
>
|
||||||
|
{isRunning ? renderRunning : renderForm}
|
||||||
|
</SideSheet>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -5,12 +5,11 @@ import { useClientContext } from '@flowgram.ai/free-layout-editor';
|
|||||||
import { Tooltip, IconButton, Divider } from '@douyinfe/semi-ui';
|
import { Tooltip, IconButton, Divider } from '@douyinfe/semi-ui';
|
||||||
import { IconUndo, IconRedo } from '@douyinfe/semi-icons';
|
import { IconUndo, IconRedo } from '@douyinfe/semi-icons';
|
||||||
|
|
||||||
|
import { TestRunButton } from '../testrun/testrun-button';
|
||||||
import { AddNode } from '../add-node';
|
import { AddNode } from '../add-node';
|
||||||
import { ZoomSelect } from './zoom-select';
|
import { ZoomSelect } from './zoom-select';
|
||||||
import { SwitchLine } from './switch-line';
|
import { SwitchLine } from './switch-line';
|
||||||
import { ToolContainer, ToolSection } from './styles';
|
import { ToolContainer, ToolSection } from './styles';
|
||||||
import { Save } from './save';
|
|
||||||
import { Run } from './run';
|
|
||||||
import { Readonly } from './readonly';
|
import { Readonly } from './readonly';
|
||||||
import { MinimapSwitch } from './minimap-switch';
|
import { MinimapSwitch } from './minimap-switch';
|
||||||
import { Minimap } from './minimap';
|
import { Minimap } from './minimap';
|
||||||
@ -71,8 +70,7 @@ export const DemoTools = () => {
|
|||||||
<Divider layout="vertical" style={{ height: '16px' }} margin={3} />
|
<Divider layout="vertical" style={{ height: '16px' }} margin={3} />
|
||||||
<AddNode disabled={playground.config.readonly} />
|
<AddNode disabled={playground.config.readonly} />
|
||||||
<Divider layout="vertical" style={{ height: '16px' }} margin={3} />
|
<Divider layout="vertical" style={{ height: '16px' }} margin={3} />
|
||||||
<Save disabled={playground.config.readonly} />
|
<TestRunButton disabled={playground.config.readonly} />
|
||||||
<Run />
|
|
||||||
</ToolSection>
|
</ToolSection>
|
||||||
</ToolContainer>
|
</ToolContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,17 +3,17 @@ import { useState } from 'react';
|
|||||||
import { useService } from '@flowgram.ai/free-layout-editor';
|
import { useService } from '@flowgram.ai/free-layout-editor';
|
||||||
import { Button } from '@douyinfe/semi-ui';
|
import { Button } from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
import { RunningService } from '../../services';
|
import { WorkflowRuntimeService } from '../../plugins/runtime-plugin/runtime-service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the simulation and highlight the lines
|
* Run the simulation and highlight the lines
|
||||||
*/
|
*/
|
||||||
export function Run() {
|
export function Run() {
|
||||||
const [isRunning, setRunning] = useState(false);
|
const [isRunning, setRunning] = useState(false);
|
||||||
const runningService = useService(RunningService);
|
const runtimeService = useService(WorkflowRuntimeService);
|
||||||
const onRun = async () => {
|
const onRun = async () => {
|
||||||
setRunning(true);
|
setRunning(true);
|
||||||
await runningService.startRun();
|
await runtimeService.taskRun('{}');
|
||||||
setRunning(false);
|
setRunning(false);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
|
||||||
import { Field } from '@flowgram.ai/free-layout-editor';
|
import { Field } from '@flowgram.ai/free-layout-editor';
|
||||||
|
|
||||||
import { TypeTag } from '../type-tag';
|
import { TypeTag } from '../type-tag';
|
||||||
@ -5,13 +7,17 @@ import { JsonSchema } from '../../typings';
|
|||||||
import { useIsSidebar } from '../../hooks';
|
import { useIsSidebar } from '../../hooks';
|
||||||
import { FormOutputsContainer } from './styles';
|
import { FormOutputsContainer } from './styles';
|
||||||
|
|
||||||
export function FormOutputs() {
|
interface FormOutputsProps {
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FormOutputs: FC<FormOutputsProps> = ({ name = 'outputs' }) => {
|
||||||
const isSidebar = useIsSidebar();
|
const isSidebar = useIsSidebar();
|
||||||
if (isSidebar) {
|
if (isSidebar) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Field<JsonSchema> name={'outputs'}>
|
<Field<JsonSchema> name={name}>
|
||||||
{({ field }) => {
|
{({ field }) => {
|
||||||
const properties = field.value?.properties;
|
const properties = field.value?.properties;
|
||||||
if (properties) {
|
if (properties) {
|
||||||
@ -25,4 +31,4 @@ export function FormOutputs() {
|
|||||||
}}
|
}}
|
||||||
</Field>
|
</Field>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|||||||
@ -13,8 +13,9 @@ import { createContainerNodePlugin } from '@flowgram.ai/free-container-plugin';
|
|||||||
import { onDragLineEnd } from '../utils';
|
import { onDragLineEnd } from '../utils';
|
||||||
import { FlowNodeRegistry, FlowDocumentJSON } from '../typings';
|
import { FlowNodeRegistry, FlowDocumentJSON } from '../typings';
|
||||||
import { shortcuts } from '../shortcuts';
|
import { shortcuts } from '../shortcuts';
|
||||||
import { CustomService, RunningService } from '../services';
|
import { CustomService } from '../services';
|
||||||
import { createSyncVariablePlugin, createContextMenuPlugin } from '../plugins';
|
import { WorkflowRuntimeService } from '../plugins/runtime-plugin/runtime-service';
|
||||||
|
import { createSyncVariablePlugin, createRuntimePlugin, createContextMenuPlugin } from '../plugins';
|
||||||
import { defaultFormMeta } from '../nodes/default-form-meta';
|
import { defaultFormMeta } from '../nodes/default-form-meta';
|
||||||
import { WorkflowNodeType } from '../nodes';
|
import { WorkflowNodeType } from '../nodes';
|
||||||
import { SelectorBoxPopover } from '../components/selector-box-popover';
|
import { SelectorBoxPopover } from '../components/selector-box-popover';
|
||||||
@ -160,7 +161,7 @@ export function useEditorProps(
|
|||||||
/**
|
/**
|
||||||
* Running line
|
* Running line
|
||||||
*/
|
*/
|
||||||
isFlowingLine: (ctx, line) => ctx.get(RunningService).isFlowingLine(line),
|
isFlowingLine: (ctx, line) => ctx.get(WorkflowRuntimeService).isFlowingLine(line),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shortcuts
|
* Shortcuts
|
||||||
@ -171,7 +172,6 @@ export function useEditorProps(
|
|||||||
*/
|
*/
|
||||||
onBind: ({ bind }) => {
|
onBind: ({ bind }) => {
|
||||||
bind(CustomService).toSelf().inSingletonScope();
|
bind(CustomService).toSelf().inSingletonScope();
|
||||||
bind(RunningService).toSelf().inSingletonScope();
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Playground init
|
* Playground init
|
||||||
@ -264,6 +264,15 @@ export function useEditorProps(
|
|||||||
* ContextMenu plugin
|
* ContextMenu plugin
|
||||||
*/
|
*/
|
||||||
createContextMenuPlugin({}),
|
createContextMenuPlugin({}),
|
||||||
|
createRuntimePlugin({
|
||||||
|
mode: 'browser',
|
||||||
|
// mode: 'server',
|
||||||
|
// serverConfig: {
|
||||||
|
// domain: 'localhost',
|
||||||
|
// port: 4000,
|
||||||
|
// protocol: 'http',
|
||||||
|
// },
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
[]
|
[]
|
||||||
|
|||||||
@ -48,7 +48,7 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
meta: {
|
meta: {
|
||||||
position: {
|
position: {
|
||||||
x: 640,
|
x: 640,
|
||||||
y: 363.25,
|
y: 318.25,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
@ -86,95 +86,13 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
type: 'end',
|
type: 'end',
|
||||||
meta: {
|
meta: {
|
||||||
position: {
|
position: {
|
||||||
x: 2220,
|
x: 2202.9953917050693,
|
||||||
y: 381.75,
|
y: 381.75,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
title: 'End',
|
title: 'End',
|
||||||
outputs: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
result: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'loop_H8M3U',
|
|
||||||
type: 'loop',
|
|
||||||
meta: {
|
|
||||||
position: {
|
|
||||||
x: 1020,
|
|
||||||
y: 547.96875,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
title: 'Loop_2',
|
|
||||||
batchFor: {
|
|
||||||
type: 'ref',
|
|
||||||
content: ['start_0', 'array_obj'],
|
|
||||||
},
|
|
||||||
outputs: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
result: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
blocks: [
|
|
||||||
{
|
|
||||||
id: 'llm_CBdCg',
|
|
||||||
type: 'llm',
|
|
||||||
meta: {
|
|
||||||
position: {
|
|
||||||
x: 180,
|
|
||||||
y: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
title: 'LLM_4',
|
|
||||||
inputsValues: {
|
|
||||||
modelType: {
|
|
||||||
type: 'constant',
|
|
||||||
content: 'gpt-3.5-turbo',
|
|
||||||
},
|
|
||||||
temperature: {
|
|
||||||
type: 'constant',
|
|
||||||
content: 0.5,
|
|
||||||
},
|
|
||||||
systemPrompt: {
|
|
||||||
type: 'constant',
|
|
||||||
content: 'You are an AI assistant.',
|
|
||||||
},
|
|
||||||
prompt: {
|
|
||||||
type: 'constant',
|
|
||||||
content: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputs: {
|
inputs: {
|
||||||
type: 'object',
|
|
||||||
required: ['modelType', 'temperature', 'prompt'],
|
|
||||||
properties: {
|
|
||||||
modelType: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
temperature: {
|
|
||||||
type: 'number',
|
|
||||||
},
|
|
||||||
systemPrompt: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
prompt: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
outputs: {
|
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
result: {
|
result: {
|
||||||
@ -184,78 +102,13 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'llm_gZafu',
|
|
||||||
type: 'llm',
|
|
||||||
meta: {
|
|
||||||
position: {
|
|
||||||
x: 640,
|
|
||||||
y: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
title: 'LLM_5',
|
|
||||||
inputsValues: {
|
|
||||||
modelType: {
|
|
||||||
type: 'constant',
|
|
||||||
content: 'gpt-3.5-turbo',
|
|
||||||
},
|
|
||||||
temperature: {
|
|
||||||
type: 'constant',
|
|
||||||
content: 0.5,
|
|
||||||
},
|
|
||||||
systemPrompt: {
|
|
||||||
type: 'constant',
|
|
||||||
content: 'You are an AI assistant.',
|
|
||||||
},
|
|
||||||
prompt: {
|
|
||||||
type: 'constant',
|
|
||||||
content: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
inputs: {
|
|
||||||
type: 'object',
|
|
||||||
required: ['modelType', 'temperature', 'prompt'],
|
|
||||||
properties: {
|
|
||||||
modelType: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
temperature: {
|
|
||||||
type: 'number',
|
|
||||||
},
|
|
||||||
systemPrompt: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
prompt: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
outputs: {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
result: {
|
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
edges: [
|
|
||||||
{
|
|
||||||
sourceNodeID: 'llm_CBdCg',
|
|
||||||
targetNodeID: 'llm_gZafu',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: '159623',
|
id: '159623',
|
||||||
type: 'comment',
|
type: 'comment',
|
||||||
meta: {
|
meta: {
|
||||||
position: {
|
position: {
|
||||||
x: 640,
|
x: 640,
|
||||||
y: 522.46875,
|
y: 573.96875,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
@ -267,35 +120,42 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'group_V-_st',
|
id: 'loop_sGybT',
|
||||||
type: 'group',
|
type: 'loop',
|
||||||
meta: {
|
meta: {
|
||||||
position: {
|
position: {
|
||||||
x: 1020,
|
x: 1373.5714285714287,
|
||||||
y: 96.25,
|
y: 394.9758064516129,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
title: 'LLM_Group',
|
title: 'Loop_1',
|
||||||
color: 'Violet',
|
|
||||||
},
|
},
|
||||||
blocks: [
|
blocks: [
|
||||||
{
|
{
|
||||||
id: 'llm_0',
|
id: 'llm_6aSyo',
|
||||||
type: 'llm',
|
type: 'llm',
|
||||||
meta: {
|
meta: {
|
||||||
position: {
|
position: {
|
||||||
x: 640,
|
x: -196.8663594470046,
|
||||||
y: 0,
|
y: 142.0046082949309,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
title: 'LLM_0',
|
title: 'LLM_3',
|
||||||
inputsValues: {
|
inputsValues: {
|
||||||
modelType: {
|
modelName: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
content: 'gpt-3.5-turbo',
|
content: 'gpt-3.5-turbo',
|
||||||
},
|
},
|
||||||
|
apiKey: {
|
||||||
|
type: 'constant',
|
||||||
|
content: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||||
|
},
|
||||||
|
apiHost: {
|
||||||
|
type: 'constant',
|
||||||
|
content: 'https://mock-ai-url/api/v3',
|
||||||
|
},
|
||||||
temperature: {
|
temperature: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
content: 0.5,
|
content: 0.5,
|
||||||
@ -311,9 +171,15 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
},
|
},
|
||||||
inputs: {
|
inputs: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['modelType', 'temperature', 'prompt'],
|
required: ['modelName', 'apiKey', 'apiHost', 'temperature', 'prompt'],
|
||||||
properties: {
|
properties: {
|
||||||
modelType: {
|
modelName: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
apiKey: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
apiHost: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
temperature: {
|
temperature: {
|
||||||
@ -338,21 +204,29 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'llm_l_TcE',
|
id: 'llm_ZqKlP',
|
||||||
type: 'llm',
|
type: 'llm',
|
||||||
meta: {
|
meta: {
|
||||||
position: {
|
position: {
|
||||||
x: 180,
|
x: 253.1797235023041,
|
||||||
y: 0,
|
y: 142.00460829493088,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
title: 'LLM_1',
|
title: 'LLM_4',
|
||||||
inputsValues: {
|
inputsValues: {
|
||||||
modelType: {
|
modelName: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
content: 'gpt-3.5-turbo',
|
content: 'gpt-3.5-turbo',
|
||||||
},
|
},
|
||||||
|
apiKey: {
|
||||||
|
type: 'constant',
|
||||||
|
content: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||||
|
},
|
||||||
|
apiHost: {
|
||||||
|
type: 'constant',
|
||||||
|
content: 'https://mock-ai-url/api/v3',
|
||||||
|
},
|
||||||
temperature: {
|
temperature: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
content: 0.5,
|
content: 0.5,
|
||||||
@ -368,9 +242,15 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
},
|
},
|
||||||
inputs: {
|
inputs: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['modelType', 'temperature', 'prompt'],
|
required: ['modelName', 'apiKey', 'apiHost', 'temperature', 'prompt'],
|
||||||
properties: {
|
properties: {
|
||||||
modelType: {
|
modelName: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
apiKey: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
apiHost: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
temperature: {
|
temperature: {
|
||||||
@ -397,18 +277,179 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
],
|
],
|
||||||
edges: [
|
edges: [
|
||||||
{
|
{
|
||||||
sourceNodeID: 'llm_l_TcE',
|
sourceNodeID: 'llm_6aSyo',
|
||||||
targetNodeID: 'llm_0',
|
targetNodeID: 'llm_ZqKlP',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sourceNodeID: 'llm_0',
|
id: 'group_5ci0o',
|
||||||
targetNodeID: 'end_0',
|
type: 'group',
|
||||||
|
meta: {
|
||||||
|
position: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
data: {},
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
id: 'llm_8--A3',
|
||||||
|
type: 'llm',
|
||||||
|
meta: {
|
||||||
|
position: {
|
||||||
|
x: 1177.8341013824886,
|
||||||
|
y: 19.25,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: 'LLM_1',
|
||||||
|
inputsValues: {
|
||||||
|
modelName: {
|
||||||
|
type: 'constant',
|
||||||
|
content: 'gpt-3.5-turbo',
|
||||||
|
},
|
||||||
|
apiKey: {
|
||||||
|
type: 'constant',
|
||||||
|
content: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||||
|
},
|
||||||
|
apiHost: {
|
||||||
|
type: 'constant',
|
||||||
|
content: 'https://mock-ai-url/api/v3',
|
||||||
|
},
|
||||||
|
temperature: {
|
||||||
|
type: 'constant',
|
||||||
|
content: 0.5,
|
||||||
|
},
|
||||||
|
systemPrompt: {
|
||||||
|
type: 'constant',
|
||||||
|
content: 'You are an AI assistant.',
|
||||||
|
},
|
||||||
|
prompt: {
|
||||||
|
type: 'constant',
|
||||||
|
content: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputs: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['modelName', 'apiKey', 'apiHost', 'temperature', 'prompt'],
|
||||||
|
properties: {
|
||||||
|
modelName: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
apiKey: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
apiHost: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
temperature: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
systemPrompt: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
prompt: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
result: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'llm_vTyMa',
|
||||||
|
type: 'llm',
|
||||||
|
meta: {
|
||||||
|
position: {
|
||||||
|
x: 1625.6221198156682,
|
||||||
|
y: 19.25,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: 'LLM_2',
|
||||||
|
inputsValues: {
|
||||||
|
modelName: {
|
||||||
|
type: 'constant',
|
||||||
|
content: 'gpt-3.5-turbo',
|
||||||
|
},
|
||||||
|
apiKey: {
|
||||||
|
type: 'constant',
|
||||||
|
content: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||||
|
},
|
||||||
|
apiHost: {
|
||||||
|
type: 'constant',
|
||||||
|
content: 'https://mock-ai-url/api/v3',
|
||||||
|
},
|
||||||
|
temperature: {
|
||||||
|
type: 'constant',
|
||||||
|
content: 0.5,
|
||||||
|
},
|
||||||
|
systemPrompt: {
|
||||||
|
type: 'constant',
|
||||||
|
content: 'You are an AI assistant.',
|
||||||
|
},
|
||||||
|
prompt: {
|
||||||
|
type: 'constant',
|
||||||
|
content: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inputs: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['modelName', 'apiKey', 'apiHost', 'temperature', 'prompt'],
|
||||||
|
properties: {
|
||||||
|
modelName: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
apiKey: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
apiHost: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
temperature: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
systemPrompt: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
prompt: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
result: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
edges: [
|
||||||
{
|
{
|
||||||
sourceNodeID: 'condition_0',
|
sourceNodeID: 'condition_0',
|
||||||
targetNodeID: 'llm_l_TcE',
|
targetNodeID: 'llm_8--A3',
|
||||||
sourcePortID: 'if_0',
|
sourcePortID: 'if_0',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
sourceNodeID: 'llm_8--A3',
|
||||||
|
targetNodeID: 'llm_vTyMa',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sourceNodeID: 'llm_vTyMa',
|
||||||
|
targetNodeID: 'end_0',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -419,20 +460,20 @@ export const initialData: FlowDocumentJSON = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
sourceNodeID: 'condition_0',
|
sourceNodeID: 'condition_0',
|
||||||
targetNodeID: 'llm_l_TcE',
|
targetNodeID: 'llm_8--A3',
|
||||||
sourcePortID: 'if_0',
|
sourcePortID: 'if_0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sourceNodeID: 'condition_0',
|
sourceNodeID: 'condition_0',
|
||||||
targetNodeID: 'loop_H8M3U',
|
targetNodeID: 'loop_sGybT',
|
||||||
sourcePortID: 'if_f0rOAt',
|
sourcePortID: 'if_f0rOAt',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sourceNodeID: 'llm_0',
|
sourceNodeID: 'llm_vTyMa',
|
||||||
targetNodeID: 'end_0',
|
targetNodeID: 'end_0',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
sourceNodeID: 'loop_H8M3U',
|
sourceNodeID: 'loop_sGybT',
|
||||||
targetNodeID: 'end_0',
|
targetNodeID: 'end_0',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export const renderForm = () => {
|
|||||||
<FormHeader />
|
<FormHeader />
|
||||||
<FormContent>
|
<FormContent>
|
||||||
<Field
|
<Field
|
||||||
name="outputs.properties"
|
name="inputs.properties"
|
||||||
render={({
|
render={({
|
||||||
field: { value: propertiesSchemaValue, onChange: propertiesSchemaChange },
|
field: { value: propertiesSchemaValue, onChange: propertiesSchemaChange },
|
||||||
}: FieldRenderProps<Record<string, JsonSchema>>) => (
|
}: FieldRenderProps<Record<string, JsonSchema>>) => (
|
||||||
@ -43,7 +43,7 @@ export const renderForm = () => {
|
|||||||
</Field>
|
</Field>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormOutputs />
|
<FormOutputs name="inputs" />
|
||||||
</FormContent>
|
</FormContent>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -52,7 +52,7 @@ export const renderForm = () => {
|
|||||||
<>
|
<>
|
||||||
<FormHeader />
|
<FormHeader />
|
||||||
<FormContent>
|
<FormContent>
|
||||||
<FormOutputs />
|
<FormOutputs name="inputs" />
|
||||||
</FormContent>
|
</FormContent>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export const LLMNodeRegistry: FlowNodeRegistry = {
|
|||||||
meta: {
|
meta: {
|
||||||
size: {
|
size: {
|
||||||
width: 360,
|
width: 360,
|
||||||
height: 305,
|
height: 300,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
onAdd() {
|
onAdd() {
|
||||||
@ -25,10 +25,18 @@ export const LLMNodeRegistry: FlowNodeRegistry = {
|
|||||||
data: {
|
data: {
|
||||||
title: `LLM_${++index}`,
|
title: `LLM_${++index}`,
|
||||||
inputsValues: {
|
inputsValues: {
|
||||||
modelType: {
|
modelName: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
content: 'gpt-3.5-turbo',
|
content: 'gpt-3.5-turbo',
|
||||||
},
|
},
|
||||||
|
apiKey: {
|
||||||
|
type: 'constant',
|
||||||
|
content: 'sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||||
|
},
|
||||||
|
apiHost: {
|
||||||
|
type: 'constant',
|
||||||
|
content: 'https://mock-ai-url/api/v3',
|
||||||
|
},
|
||||||
temperature: {
|
temperature: {
|
||||||
type: 'constant',
|
type: 'constant',
|
||||||
content: 0.5,
|
content: 0.5,
|
||||||
@ -44,9 +52,15 @@ export const LLMNodeRegistry: FlowNodeRegistry = {
|
|||||||
},
|
},
|
||||||
inputs: {
|
inputs: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['modelType', 'temperature', 'prompt'],
|
required: ['modelName', 'apiKey', 'apiHost', 'temperature', 'prompt'],
|
||||||
properties: {
|
properties: {
|
||||||
modelType: {
|
modelName: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
apiKey: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
apiHost: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
temperature: {
|
temperature: {
|
||||||
|
|||||||
@ -39,8 +39,8 @@ export const LoopNodeRegistry: FlowNodeRegistry = {
|
|||||||
* 子画布 padding 设置
|
* 子画布 padding 设置
|
||||||
*/
|
*/
|
||||||
padding: () => ({
|
padding: () => ({
|
||||||
top: 125,
|
top: 120,
|
||||||
bottom: 100,
|
bottom: 60,
|
||||||
left: 100,
|
left: 100,
|
||||||
right: 100,
|
right: 100,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -14,6 +14,7 @@ interface LoopNodeJSON extends FlowNodeJSON {
|
|||||||
export const LoopFormRender = ({ form }: FormRenderProps<LoopNodeJSON>) => {
|
export const LoopFormRender = ({ form }: FormRenderProps<LoopNodeJSON>) => {
|
||||||
const isSidebar = useIsSidebar();
|
const isSidebar = useIsSidebar();
|
||||||
const { readonly } = useNodeRenderContext();
|
const { readonly } = useNodeRenderContext();
|
||||||
|
const formHeight = 85;
|
||||||
|
|
||||||
const batchFor = (
|
const batchFor = (
|
||||||
<Field<IFlowRefValue> name={`batchFor`}>
|
<Field<IFlowRefValue> name={`batchFor`}>
|
||||||
@ -48,7 +49,7 @@ export const LoopFormRender = ({ form }: FormRenderProps<LoopNodeJSON>) => {
|
|||||||
<FormHeader />
|
<FormHeader />
|
||||||
<FormContent>
|
<FormContent>
|
||||||
{batchFor}
|
{batchFor}
|
||||||
<SubCanvasRender />
|
<SubCanvasRender offsetY={-formHeight} />
|
||||||
<FormOutputs />
|
<FormOutputs />
|
||||||
</FormContent>
|
</FormContent>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
export { createSyncVariablePlugin } from './sync-variable-plugin/sync-variable-plugin';
|
export { createSyncVariablePlugin } from './sync-variable-plugin/sync-variable-plugin';
|
||||||
export { createContextMenuPlugin } from './context-menu-plugin';
|
export { createContextMenuPlugin } from './context-menu-plugin';
|
||||||
|
export { createRuntimePlugin } from './runtime-plugin';
|
||||||
|
|||||||
@ -0,0 +1,17 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
import { TaskCancelAPI, TaskReportAPI, TaskResultAPI, TaskRunAPI } from '@flowgram.ai/runtime-js';
|
||||||
|
import { FlowGramAPIName, IRuntimeClient } from '@flowgram.ai/runtime-interface';
|
||||||
|
import { injectable } from '@flowgram.ai/free-layout-editor';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class WorkflowRuntimeClient implements IRuntimeClient {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
public [FlowGramAPIName.TaskRun]: IRuntimeClient[FlowGramAPIName.TaskRun] = TaskRunAPI;
|
||||||
|
|
||||||
|
public [FlowGramAPIName.TaskReport]: IRuntimeClient[FlowGramAPIName.TaskReport] = TaskReportAPI;
|
||||||
|
|
||||||
|
public [FlowGramAPIName.TaskResult]: IRuntimeClient[FlowGramAPIName.TaskResult] = TaskResultAPI;
|
||||||
|
|
||||||
|
public [FlowGramAPIName.TaskCancel]: IRuntimeClient[FlowGramAPIName.TaskCancel] = TaskCancelAPI;
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { definePluginCreator, PluginContext } from '@flowgram.ai/free-layout-editor';
|
||||||
|
|
||||||
|
import { RuntimePluginOptions } from './type';
|
||||||
|
import { WorkflowRuntimeServerClient } from './server-client';
|
||||||
|
import { WorkflowRuntimeService } from './runtime-service';
|
||||||
|
import { WorkflowRuntimeClient } from './browser-client';
|
||||||
|
|
||||||
|
export const createRuntimePlugin = definePluginCreator<RuntimePluginOptions, PluginContext>({
|
||||||
|
onBind({ bind, rebind }, options) {
|
||||||
|
bind(WorkflowRuntimeClient).toSelf().inSingletonScope();
|
||||||
|
bind(WorkflowRuntimeServerClient).toSelf().inSingletonScope();
|
||||||
|
if (options.mode === 'server') {
|
||||||
|
rebind(WorkflowRuntimeClient).to(WorkflowRuntimeServerClient);
|
||||||
|
}
|
||||||
|
bind(WorkflowRuntimeService).toSelf().inSingletonScope();
|
||||||
|
},
|
||||||
|
onInit(ctx, options) {
|
||||||
|
if (options.mode === 'server') {
|
||||||
|
const serverClient = ctx.get(WorkflowRuntimeServerClient);
|
||||||
|
serverClient.init(options.serverConfig);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export { createRuntimePlugin } from './create-runtime-plugin';
|
||||||
|
export { WorkflowRuntimeClient } from './browser-client';
|
||||||
@ -0,0 +1,172 @@
|
|||||||
|
import {
|
||||||
|
IReport,
|
||||||
|
NodeReport,
|
||||||
|
WorkflowInputs,
|
||||||
|
WorkflowOutputs,
|
||||||
|
WorkflowStatus,
|
||||||
|
} from '@flowgram.ai/runtime-interface';
|
||||||
|
import {
|
||||||
|
injectable,
|
||||||
|
inject,
|
||||||
|
WorkflowDocument,
|
||||||
|
Playground,
|
||||||
|
WorkflowLineEntity,
|
||||||
|
WorkflowNodeEntity,
|
||||||
|
WorkflowNodeLinesData,
|
||||||
|
Emitter,
|
||||||
|
getNodeForm,
|
||||||
|
} from '@flowgram.ai/free-layout-editor';
|
||||||
|
|
||||||
|
import { WorkflowRuntimeClient } from '../browser-client';
|
||||||
|
|
||||||
|
const SYNC_TASK_REPORT_INTERVAL = 500;
|
||||||
|
|
||||||
|
interface NodeRunningStatus {
|
||||||
|
nodeID: string;
|
||||||
|
status: WorkflowStatus;
|
||||||
|
nodeResultLength: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class WorkflowRuntimeService {
|
||||||
|
@inject(Playground) playground: Playground;
|
||||||
|
|
||||||
|
@inject(WorkflowDocument) document: WorkflowDocument;
|
||||||
|
|
||||||
|
@inject(WorkflowRuntimeClient) runtimeClient: WorkflowRuntimeClient;
|
||||||
|
|
||||||
|
private runningNodes: WorkflowNodeEntity[] = [];
|
||||||
|
|
||||||
|
private taskID?: string;
|
||||||
|
|
||||||
|
private syncTaskReportIntervalID?: ReturnType<typeof setInterval>;
|
||||||
|
|
||||||
|
private reportEmitter = new Emitter<NodeReport>();
|
||||||
|
|
||||||
|
private resetEmitter = new Emitter<{}>();
|
||||||
|
|
||||||
|
public terminatedEmitter = new Emitter<{
|
||||||
|
result?: {
|
||||||
|
inputs: WorkflowInputs;
|
||||||
|
outputs: WorkflowOutputs;
|
||||||
|
};
|
||||||
|
}>();
|
||||||
|
|
||||||
|
private nodeRunningStatus: Map<string, NodeRunningStatus>;
|
||||||
|
|
||||||
|
public onNodeReportChange = this.reportEmitter.event;
|
||||||
|
|
||||||
|
public onReset = this.resetEmitter.event;
|
||||||
|
|
||||||
|
public onTerminated = this.terminatedEmitter.event;
|
||||||
|
|
||||||
|
public isFlowingLine(line: WorkflowLineEntity) {
|
||||||
|
return this.runningNodes.some((node) =>
|
||||||
|
node.getData(WorkflowNodeLinesData).inputLines.includes(line)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async taskRun(inputsString: string): Promise<void> {
|
||||||
|
if (this.taskID) {
|
||||||
|
await this.taskCancel();
|
||||||
|
}
|
||||||
|
if (!this.validate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.reset();
|
||||||
|
const output = await this.runtimeClient.TaskRun({
|
||||||
|
schema: JSON.stringify(this.document.toJSON()),
|
||||||
|
inputs: JSON.parse(inputsString) as WorkflowInputs,
|
||||||
|
});
|
||||||
|
if (!output) {
|
||||||
|
this.terminatedEmitter.fire({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.taskID = output.taskID;
|
||||||
|
this.syncTaskReportIntervalID = setInterval(() => {
|
||||||
|
this.syncTaskReport();
|
||||||
|
}, SYNC_TASK_REPORT_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async taskCancel(): Promise<void> {
|
||||||
|
if (!this.taskID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.runtimeClient.TaskCancel({
|
||||||
|
taskID: this.taskID,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async validate(): Promise<boolean> {
|
||||||
|
const allForms = this.document.getAllNodes().map((node) => getNodeForm(node));
|
||||||
|
const formValidations = await Promise.all(allForms.map(async (form) => form?.validate()));
|
||||||
|
const validations = formValidations.filter((validation) => validation !== undefined);
|
||||||
|
const isValid = validations.every((validation) => validation);
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private reset(): void {
|
||||||
|
this.taskID = undefined;
|
||||||
|
this.nodeRunningStatus = new Map();
|
||||||
|
this.runningNodes = [];
|
||||||
|
if (this.syncTaskReportIntervalID) {
|
||||||
|
clearInterval(this.syncTaskReportIntervalID);
|
||||||
|
}
|
||||||
|
this.resetEmitter.fire({});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async syncTaskReport(): Promise<void> {
|
||||||
|
if (!this.taskID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const output = await this.runtimeClient.TaskReport({
|
||||||
|
taskID: this.taskID,
|
||||||
|
});
|
||||||
|
if (!output) {
|
||||||
|
clearInterval(this.syncTaskReportIntervalID);
|
||||||
|
console.error('Sync task report failed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { workflowStatus, inputs, outputs } = output;
|
||||||
|
if (workflowStatus.terminated) {
|
||||||
|
clearInterval(this.syncTaskReportIntervalID);
|
||||||
|
if (Object.keys(outputs).length > 0) {
|
||||||
|
this.terminatedEmitter.fire({ result: { inputs, outputs } });
|
||||||
|
} else {
|
||||||
|
this.terminatedEmitter.fire({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.updateReport(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateReport(report: IReport): void {
|
||||||
|
const { reports } = report;
|
||||||
|
this.runningNodes = [];
|
||||||
|
this.document.getAllNodes().forEach((node) => {
|
||||||
|
const nodeID = node.id;
|
||||||
|
const nodeReport = reports[nodeID];
|
||||||
|
if (!nodeReport) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nodeReport.status === WorkflowStatus.Processing) {
|
||||||
|
this.runningNodes.push(node);
|
||||||
|
}
|
||||||
|
const runningStatus = this.nodeRunningStatus.get(nodeID);
|
||||||
|
if (
|
||||||
|
!runningStatus ||
|
||||||
|
nodeReport.status !== runningStatus.status ||
|
||||||
|
nodeReport.snapshots.length !== runningStatus.nodeResultLength
|
||||||
|
) {
|
||||||
|
this.nodeRunningStatus.set(nodeID, {
|
||||||
|
nodeID,
|
||||||
|
status: nodeReport.status,
|
||||||
|
nodeResultLength: nodeReport.snapshots.length,
|
||||||
|
});
|
||||||
|
this.reportEmitter.fire(nodeReport);
|
||||||
|
this.document.linesManager.forceUpdate();
|
||||||
|
} else if (nodeReport.status === WorkflowStatus.Processing) {
|
||||||
|
this.reportEmitter.fire(nodeReport);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
import { ServerConfig } from '../type';
|
||||||
|
|
||||||
|
export const DEFAULT_SERVER_CONFIG: ServerConfig = {
|
||||||
|
domain: 'localhost',
|
||||||
|
port: 4000,
|
||||||
|
protocol: 'http',
|
||||||
|
};
|
||||||
@ -0,0 +1,134 @@
|
|||||||
|
import {
|
||||||
|
FlowGramAPIName,
|
||||||
|
IRuntimeClient,
|
||||||
|
TaskCancelInput,
|
||||||
|
TaskCancelOutput,
|
||||||
|
TaskReportInput,
|
||||||
|
TaskReportOutput,
|
||||||
|
TaskResultInput,
|
||||||
|
TaskResultOutput,
|
||||||
|
TaskRunInput,
|
||||||
|
TaskRunOutput,
|
||||||
|
} from '@flowgram.ai/runtime-interface';
|
||||||
|
import { injectable } from '@flowgram.ai/free-layout-editor';
|
||||||
|
|
||||||
|
import type { ServerError } from './type';
|
||||||
|
import { DEFAULT_SERVER_CONFIG } from './constant';
|
||||||
|
import { ServerConfig } from '../type';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class WorkflowRuntimeServerClient implements IRuntimeClient {
|
||||||
|
private config: ServerConfig = DEFAULT_SERVER_CONFIG;
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
public init(config: ServerConfig) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async [FlowGramAPIName.TaskRun](input: TaskRunInput): Promise<TaskRunOutput | undefined> {
|
||||||
|
try {
|
||||||
|
const body = JSON.stringify(input);
|
||||||
|
const response = await fetch(this.getURL('/api/task/run'), {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: body,
|
||||||
|
redirect: 'follow',
|
||||||
|
});
|
||||||
|
const output: TaskRunOutput | ServerError = await response.json();
|
||||||
|
if (this.isError(output)) {
|
||||||
|
console.error('TaskRun failed', output);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async [FlowGramAPIName.TaskReport](
|
||||||
|
input: TaskReportInput
|
||||||
|
): Promise<TaskReportOutput | undefined> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(this.getURL(`/api/task/report?taskID=${input.taskID}`), {
|
||||||
|
method: 'GET',
|
||||||
|
redirect: 'follow',
|
||||||
|
});
|
||||||
|
const output: TaskReportOutput | ServerError = await response.json();
|
||||||
|
if (this.isError(output)) {
|
||||||
|
console.error('TaskReport failed', output);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async [FlowGramAPIName.TaskResult](
|
||||||
|
input: TaskResultInput
|
||||||
|
): Promise<TaskResultOutput | undefined> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(this.getURL(`/api/task/result?taskID=${input.taskID}`), {
|
||||||
|
method: 'GET',
|
||||||
|
redirect: 'follow',
|
||||||
|
});
|
||||||
|
const output: TaskResultOutput | ServerError = await response.json();
|
||||||
|
if (this.isError(output)) {
|
||||||
|
console.error('TaskReport failed', output);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async [FlowGramAPIName.TaskCancel](input: TaskCancelInput): Promise<TaskCancelOutput> {
|
||||||
|
try {
|
||||||
|
const body = JSON.stringify(input);
|
||||||
|
const response = await fetch(this.getURL(`/api/task/cancel`), {
|
||||||
|
method: 'PUT',
|
||||||
|
redirect: 'follow',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
const output: TaskCancelOutput | ServerError = await response.json();
|
||||||
|
if (this.isError(output)) {
|
||||||
|
console.error('TaskReport failed', output);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private isError(output: unknown | undefined): output is ServerError {
|
||||||
|
return !!output && (output as ServerError).code !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getURL(path: string): string {
|
||||||
|
const protocol = this.config.protocol ?? window.location.protocol;
|
||||||
|
const host = this.config.port
|
||||||
|
? `${this.config.domain}:${this.config.port}`
|
||||||
|
: this.config.domain;
|
||||||
|
return `${protocol}://${host}${path}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
export interface ServerError {
|
||||||
|
code: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
16
apps/demo-free-layout/src/plugins/runtime-plugin/type.ts
Normal file
16
apps/demo-free-layout/src/plugins/runtime-plugin/type.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export interface RuntimeBrowserOptions {
|
||||||
|
mode?: 'browser';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RuntimeServerOptions {
|
||||||
|
mode: 'server';
|
||||||
|
serverConfig: ServerConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RuntimePluginOptions = RuntimeBrowserOptions | RuntimeServerOptions;
|
||||||
|
|
||||||
|
export interface ServerConfig {
|
||||||
|
domain: string;
|
||||||
|
port?: number;
|
||||||
|
protocol?: string;
|
||||||
|
}
|
||||||
@ -1,2 +1 @@
|
|||||||
export { CustomService } from './custom-service';
|
export { CustomService } from './custom-service';
|
||||||
export { RunningService } from './running-service';
|
|
||||||
|
|||||||
@ -1,47 +0,0 @@
|
|||||||
import {
|
|
||||||
injectable,
|
|
||||||
inject,
|
|
||||||
WorkflowDocument,
|
|
||||||
Playground,
|
|
||||||
delay,
|
|
||||||
WorkflowLineEntity,
|
|
||||||
WorkflowNodeEntity,
|
|
||||||
WorkflowNodeLinesData,
|
|
||||||
} from '@flowgram.ai/free-layout-editor';
|
|
||||||
const RUNNING_INTERVAL = 1000;
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class RunningService {
|
|
||||||
@inject(Playground) playground: Playground;
|
|
||||||
|
|
||||||
@inject(WorkflowDocument) document: WorkflowDocument;
|
|
||||||
|
|
||||||
private _runningNodes: WorkflowNodeEntity[] = [];
|
|
||||||
|
|
||||||
async addRunningNode(node: WorkflowNodeEntity): Promise<void> {
|
|
||||||
this._runningNodes.push(node);
|
|
||||||
node.renderData.node.classList.add('node-running');
|
|
||||||
this.document.linesManager.forceUpdate(); // Refresh line renderer
|
|
||||||
await delay(RUNNING_INTERVAL);
|
|
||||||
// Child Nodes
|
|
||||||
await Promise.all(node.blocks.map((nextNode) => this.addRunningNode(nextNode)));
|
|
||||||
// Sibling Nodes
|
|
||||||
const nextNodes = node.getData(WorkflowNodeLinesData).outputNodes;
|
|
||||||
await Promise.all(nextNodes.map((nextNode) => this.addRunningNode(nextNode)));
|
|
||||||
}
|
|
||||||
|
|
||||||
async startRun(): Promise<void> {
|
|
||||||
await this.addRunningNode(this.document.getNode('start_0')!);
|
|
||||||
this._runningNodes.forEach((node) => {
|
|
||||||
node.renderData.node.classList.remove('node-running');
|
|
||||||
});
|
|
||||||
this._runningNodes = [];
|
|
||||||
this.document.linesManager.forceUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
isFlowingLine(line: WorkflowLineEntity) {
|
|
||||||
return this._runningNodes.some((node) =>
|
|
||||||
node.getData(WorkflowNodeLinesData).outputLines.includes(line)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1232
common/config/rush/pnpm-lock.yaml
generated
1232
common/config/rush/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -9,15 +9,17 @@
|
|||||||
"douyinfe",
|
"douyinfe",
|
||||||
"flowgram",
|
"flowgram",
|
||||||
"flowgram.ai",
|
"flowgram.ai",
|
||||||
|
"gedit",
|
||||||
"Hoverable",
|
"Hoverable",
|
||||||
|
"langchain",
|
||||||
"openbracket",
|
"openbracket",
|
||||||
"rsbuild",
|
"rsbuild",
|
||||||
"rspack",
|
"rspack",
|
||||||
"rspress",
|
"rspress",
|
||||||
"Sandpack",
|
"Sandpack",
|
||||||
|
"testrun",
|
||||||
"zoomin",
|
"zoomin",
|
||||||
"zoomout",
|
"zoomout"
|
||||||
"gedit"
|
|
||||||
],
|
],
|
||||||
"ignoreWords": [],
|
"ignoreWords": [],
|
||||||
"import": []
|
"import": []
|
||||||
|
|||||||
@ -1,23 +1,20 @@
|
|||||||
import React, { CSSProperties, type FC } from 'react';
|
import React, { CSSProperties, type FC } from 'react';
|
||||||
|
|
||||||
import { useCurrentEntity } from '@flowgram.ai/free-layout-core';
|
|
||||||
|
|
||||||
import { SubCanvasRenderStyle } from './style';
|
import { SubCanvasRenderStyle } from './style';
|
||||||
import { SubCanvasTips } from '../tips';
|
import { SubCanvasTips } from '../tips';
|
||||||
import { SubCanvasBorder } from '../border';
|
import { SubCanvasBorder } from '../border';
|
||||||
import { SubCanvasBackground } from '../background';
|
import { SubCanvasBackground } from '../background';
|
||||||
import { useNodeSize, useSyncNodeRenderSize } from '../../hooks';
|
import { useNodeSize, useSyncNodeRenderSize } from '../../hooks';
|
||||||
|
|
||||||
interface ISubCanvasBorder {
|
interface ISubCanvasRender {
|
||||||
|
offsetY: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SubCanvasRender: FC<ISubCanvasBorder> = ({ className, style }) => {
|
export const SubCanvasRender: FC<ISubCanvasRender> = ({ className, style, offsetY }) => {
|
||||||
const node = useCurrentEntity();
|
|
||||||
const nodeSize = useNodeSize();
|
const nodeSize = useNodeSize();
|
||||||
const nodeHeight = nodeSize?.height ?? 0;
|
const nodeHeight = nodeSize?.height ?? 0;
|
||||||
const { padding } = node.transform;
|
|
||||||
|
|
||||||
useSyncNodeRenderSize(nodeSize);
|
useSyncNodeRenderSize(nodeSize);
|
||||||
|
|
||||||
@ -25,7 +22,7 @@ export const SubCanvasRender: FC<ISubCanvasBorder> = ({ className, style }) => {
|
|||||||
<SubCanvasRenderStyle
|
<SubCanvasRenderStyle
|
||||||
className={`sub-canvas-render ${className ?? ''}`}
|
className={`sub-canvas-render ${className ?? ''}`}
|
||||||
style={{
|
style={{
|
||||||
height: nodeHeight - padding.top,
|
height: nodeHeight + offsetY,
|
||||||
...style,
|
...style,
|
||||||
}}
|
}}
|
||||||
data-flow-editor-selectable="true"
|
data-flow-editor-selectable="true"
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
export const SubCanvasTipsStyle = styled.div`
|
export const SubCanvasTipsStyle = styled.div`
|
||||||
|
pointer-events: auto;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
||||||
|
|||||||
6
packages/runtime/interface/.eslintrc.cjs
Normal file
6
packages/runtime/interface/.eslintrc.cjs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
const { defineConfig } = require('@flowgram.ai/eslint-config');
|
||||||
|
|
||||||
|
module.exports = defineConfig({
|
||||||
|
preset: 'base',
|
||||||
|
packageRoot: __dirname,
|
||||||
|
});
|
||||||
44
packages/runtime/interface/package.json
Normal file
44
packages/runtime/interface/package.json
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"name": "@flowgram.ai/runtime-interface",
|
||||||
|
"version": "0.1.8",
|
||||||
|
"homepage": "https://flowgram.ai/",
|
||||||
|
"repository": "https://github.com/bytedance/flowgram.ai",
|
||||||
|
"license": "MIT",
|
||||||
|
"exports": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/esm/index.js",
|
||||||
|
"require": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"module": "./dist/esm/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"dev": "npm run watch",
|
||||||
|
"build": "npm run build:fast -- --dts-resolve",
|
||||||
|
"build:fast": "tsup src/index.ts --format cjs,esm --sourcemap --legacy-output",
|
||||||
|
"build:watch": "npm run build:fast -- --dts-resolve",
|
||||||
|
"clean": "rimraf dist",
|
||||||
|
"test": "exit 0",
|
||||||
|
"test:cov": "exit 0",
|
||||||
|
"ts-check": "tsc --noEmit",
|
||||||
|
"watch": "npm run build:fast -- --dts-resolve --watch --ignore-watch dist"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"zod": "^3.24.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@flowgram.ai/eslint-config": "workspace:*",
|
||||||
|
"@flowgram.ai/ts-config": "workspace:*",
|
||||||
|
"eslint": "^8.54.0",
|
||||||
|
"tsup": "^8.0.1",
|
||||||
|
"typescript": "^5.0.4",
|
||||||
|
"vitest": "^0.34.6"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public",
|
||||||
|
"registry": "https://registry.npmjs.org/"
|
||||||
|
}
|
||||||
|
}
|
||||||
22
packages/runtime/interface/src/api/constant.ts
Normal file
22
packages/runtime/interface/src/api/constant.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export enum FlowGramAPIMethod {
|
||||||
|
GET = 'GET',
|
||||||
|
POST = 'POST',
|
||||||
|
PUT = 'PUT',
|
||||||
|
DELETE = 'DELETE',
|
||||||
|
PATCH = 'PATCH',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum FlowGramAPIName {
|
||||||
|
ServerInfo = 'ServerInfo',
|
||||||
|
TaskRun = 'TaskRun',
|
||||||
|
TaskReport = 'TaskReport',
|
||||||
|
TaskResult = 'TaskResult',
|
||||||
|
TaskCancel = 'TaskCancel',
|
||||||
|
Validation = 'Validation',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum FlowGramAPIModule {
|
||||||
|
Info = 'Info',
|
||||||
|
Task = 'Task',
|
||||||
|
Validation = 'Validation',
|
||||||
|
}
|
||||||
19
packages/runtime/interface/src/api/define.ts
Normal file
19
packages/runtime/interface/src/api/define.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { ValidationDefine } from './validation';
|
||||||
|
import { FlowGramAPIDefines } from './type';
|
||||||
|
import { TaskRunDefine } from './task-run';
|
||||||
|
import { TaskResultDefine } from './task-result';
|
||||||
|
import { TaskReportDefine } from './task-report';
|
||||||
|
import { TaskCancelDefine } from './task-cancel';
|
||||||
|
import { ServerInfoDefine } from './server-info';
|
||||||
|
import { FlowGramAPIName } from './constant';
|
||||||
|
|
||||||
|
export const FlowGramAPIs: FlowGramAPIDefines = {
|
||||||
|
[FlowGramAPIName.ServerInfo]: ServerInfoDefine,
|
||||||
|
[FlowGramAPIName.TaskRun]: TaskRunDefine,
|
||||||
|
[FlowGramAPIName.TaskReport]: TaskReportDefine,
|
||||||
|
[FlowGramAPIName.TaskResult]: TaskResultDefine,
|
||||||
|
[FlowGramAPIName.TaskCancel]: TaskCancelDefine,
|
||||||
|
[FlowGramAPIName.Validation]: ValidationDefine,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FlowGramAPINames = Object.keys(FlowGramAPIs) as FlowGramAPIName[];
|
||||||
10
packages/runtime/interface/src/api/index.ts
Normal file
10
packages/runtime/interface/src/api/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export * from './type';
|
||||||
|
export * from './define';
|
||||||
|
export * from './constant';
|
||||||
|
|
||||||
|
export * from './task-run';
|
||||||
|
export * from './server-info';
|
||||||
|
export * from './task-report';
|
||||||
|
export * from './validation';
|
||||||
|
export * from './task-result';
|
||||||
|
export * from './task-cancel';
|
||||||
31
packages/runtime/interface/src/api/schema.ts
Normal file
31
packages/runtime/interface/src/api/schema.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import z from 'zod';
|
||||||
|
|
||||||
|
const WorkflowIOZodSchema = z.record(z.string(), z.any());
|
||||||
|
const WorkflowSnapshotZodSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
nodeID: z.string(),
|
||||||
|
inputs: WorkflowIOZodSchema,
|
||||||
|
outputs: WorkflowIOZodSchema.optional(),
|
||||||
|
data: WorkflowIOZodSchema,
|
||||||
|
branch: z.string().optional(),
|
||||||
|
});
|
||||||
|
const WorkflowStatusZodShape = {
|
||||||
|
status: z.string(),
|
||||||
|
terminated: z.boolean(),
|
||||||
|
startTime: z.number(),
|
||||||
|
endTime: z.number().optional(),
|
||||||
|
timeCost: z.number(),
|
||||||
|
};
|
||||||
|
const WorkflowStatusZodSchema = z.object(WorkflowStatusZodShape);
|
||||||
|
|
||||||
|
export const WorkflowZodSchema = {
|
||||||
|
Inputs: WorkflowIOZodSchema,
|
||||||
|
Outputs: WorkflowIOZodSchema,
|
||||||
|
Status: WorkflowStatusZodSchema,
|
||||||
|
Snapshot: WorkflowSnapshotZodSchema,
|
||||||
|
NodeReport: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
...WorkflowStatusZodShape,
|
||||||
|
snapshots: z.array(WorkflowSnapshotZodSchema),
|
||||||
|
}),
|
||||||
|
};
|
||||||
31
packages/runtime/interface/src/api/server-info/index.ts
Normal file
31
packages/runtime/interface/src/api/server-info/index.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import z from 'zod';
|
||||||
|
|
||||||
|
import { type FlowGramAPIDefine } from '@api/type';
|
||||||
|
import { FlowGramAPIMethod, FlowGramAPIModule, FlowGramAPIName } from '@api/constant';
|
||||||
|
|
||||||
|
export interface ServerInfoInput {}
|
||||||
|
|
||||||
|
export interface ServerInfoOutput {
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
runtime: string;
|
||||||
|
version: string;
|
||||||
|
time: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServerInfoDefine: FlowGramAPIDefine = {
|
||||||
|
name: FlowGramAPIName.ServerInfo,
|
||||||
|
method: FlowGramAPIMethod.GET,
|
||||||
|
path: '/info',
|
||||||
|
module: FlowGramAPIModule.Info,
|
||||||
|
schema: {
|
||||||
|
input: z.undefined(),
|
||||||
|
output: z.object({
|
||||||
|
name: z.string(),
|
||||||
|
runtime: z.string(),
|
||||||
|
version: z.string(),
|
||||||
|
time: z.string(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
27
packages/runtime/interface/src/api/task-cancel/index.ts
Normal file
27
packages/runtime/interface/src/api/task-cancel/index.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import z from 'zod';
|
||||||
|
|
||||||
|
import { FlowGramAPIDefine } from '@api/type';
|
||||||
|
import { FlowGramAPIName, FlowGramAPIMethod, FlowGramAPIModule } from '@api/constant';
|
||||||
|
|
||||||
|
export interface TaskCancelInput {
|
||||||
|
taskID: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TaskCancelOutput = {
|
||||||
|
success: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TaskCancelDefine: FlowGramAPIDefine = {
|
||||||
|
name: FlowGramAPIName.TaskCancel,
|
||||||
|
method: FlowGramAPIMethod.PUT,
|
||||||
|
path: '/task/cancel',
|
||||||
|
module: FlowGramAPIModule.Task,
|
||||||
|
schema: {
|
||||||
|
input: z.object({
|
||||||
|
taskID: z.string(),
|
||||||
|
}),
|
||||||
|
output: z.object({
|
||||||
|
success: z.boolean(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
31
packages/runtime/interface/src/api/task-report/index.ts
Normal file
31
packages/runtime/interface/src/api/task-report/index.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import z from 'zod';
|
||||||
|
|
||||||
|
import { IReport } from '@runtime/index';
|
||||||
|
import { FlowGramAPIDefine } from '@api/type';
|
||||||
|
import { WorkflowZodSchema } from '@api/schema';
|
||||||
|
import { FlowGramAPIName, FlowGramAPIMethod, FlowGramAPIModule } from '@api/constant';
|
||||||
|
|
||||||
|
export interface TaskReportInput {
|
||||||
|
taskID: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TaskReportOutput = IReport | undefined;
|
||||||
|
|
||||||
|
export const TaskReportDefine: FlowGramAPIDefine = {
|
||||||
|
name: FlowGramAPIName.TaskReport,
|
||||||
|
method: FlowGramAPIMethod.GET,
|
||||||
|
path: '/task/report',
|
||||||
|
module: FlowGramAPIModule.Task,
|
||||||
|
schema: {
|
||||||
|
input: z.object({
|
||||||
|
taskID: z.string(),
|
||||||
|
}),
|
||||||
|
output: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
inputs: WorkflowZodSchema.Inputs,
|
||||||
|
outputs: WorkflowZodSchema.Outputs,
|
||||||
|
workflowStatus: WorkflowZodSchema.Status,
|
||||||
|
reports: z.record(z.string(), WorkflowZodSchema.NodeReport),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
25
packages/runtime/interface/src/api/task-result/index.ts
Normal file
25
packages/runtime/interface/src/api/task-result/index.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import z from 'zod';
|
||||||
|
|
||||||
|
import { WorkflowOutputs } from '@runtime/index';
|
||||||
|
import { FlowGramAPIDefine } from '@api/type';
|
||||||
|
import { WorkflowZodSchema } from '@api/schema';
|
||||||
|
import { FlowGramAPIName, FlowGramAPIMethod, FlowGramAPIModule } from '@api/constant';
|
||||||
|
|
||||||
|
export interface TaskResultInput {
|
||||||
|
taskID: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TaskResultOutput = WorkflowOutputs | undefined;
|
||||||
|
|
||||||
|
export const TaskResultDefine: FlowGramAPIDefine = {
|
||||||
|
name: FlowGramAPIName.TaskResult,
|
||||||
|
method: FlowGramAPIMethod.GET,
|
||||||
|
path: '/task/result',
|
||||||
|
module: FlowGramAPIModule.Task,
|
||||||
|
schema: {
|
||||||
|
input: z.object({
|
||||||
|
taskID: z.string(),
|
||||||
|
}),
|
||||||
|
output: WorkflowZodSchema.Outputs,
|
||||||
|
},
|
||||||
|
};
|
||||||
31
packages/runtime/interface/src/api/task-run/index.ts
Normal file
31
packages/runtime/interface/src/api/task-run/index.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import z from 'zod';
|
||||||
|
|
||||||
|
import { WorkflowInputs } from '@runtime/index';
|
||||||
|
import { FlowGramAPIDefine } from '@api/type';
|
||||||
|
import { WorkflowZodSchema } from '@api/schema';
|
||||||
|
import { FlowGramAPIMethod, FlowGramAPIModule, FlowGramAPIName } from '@api/constant';
|
||||||
|
|
||||||
|
export interface TaskRunInput {
|
||||||
|
inputs: WorkflowInputs;
|
||||||
|
schema: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskRunOutput {
|
||||||
|
taskID: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TaskRunDefine: FlowGramAPIDefine = {
|
||||||
|
name: FlowGramAPIName.TaskRun,
|
||||||
|
method: FlowGramAPIMethod.POST,
|
||||||
|
path: '/task/run',
|
||||||
|
module: FlowGramAPIModule.Task,
|
||||||
|
schema: {
|
||||||
|
input: z.object({
|
||||||
|
schema: z.string(),
|
||||||
|
inputs: WorkflowZodSchema.Inputs,
|
||||||
|
}),
|
||||||
|
output: z.object({
|
||||||
|
taskID: z.string(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
18
packages/runtime/interface/src/api/type.ts
Normal file
18
packages/runtime/interface/src/api/type.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type z from 'zod';
|
||||||
|
|
||||||
|
import { FlowGramAPIMethod, FlowGramAPIModule, FlowGramAPIName } from './constant';
|
||||||
|
|
||||||
|
export interface FlowGramAPIDefine {
|
||||||
|
name: FlowGramAPIName;
|
||||||
|
method: FlowGramAPIMethod;
|
||||||
|
path: `/${string}`;
|
||||||
|
module: FlowGramAPIModule;
|
||||||
|
schema: {
|
||||||
|
input: z.ZodFirstPartySchemaTypes;
|
||||||
|
output: z.ZodFirstPartySchemaTypes;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FlowGramAPIDefines {
|
||||||
|
[key: string]: FlowGramAPIDefine;
|
||||||
|
}
|
||||||
43
packages/runtime/interface/src/api/validation/index.ts
Normal file
43
packages/runtime/interface/src/api/validation/index.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import z from 'zod';
|
||||||
|
|
||||||
|
import { ValidationResult } from '@runtime/index';
|
||||||
|
import { FlowGramAPIDefine } from '@api/type';
|
||||||
|
import { FlowGramAPIMethod, FlowGramAPIModule, FlowGramAPIName } from '@api/constant';
|
||||||
|
|
||||||
|
export interface ValidationReq {
|
||||||
|
schema: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ValidationRes extends ValidationResult {}
|
||||||
|
|
||||||
|
export const ValidationDefine: FlowGramAPIDefine = {
|
||||||
|
name: FlowGramAPIName.Validation,
|
||||||
|
method: FlowGramAPIMethod.POST,
|
||||||
|
path: '/validation',
|
||||||
|
module: FlowGramAPIModule.Validation,
|
||||||
|
schema: {
|
||||||
|
input: z.object({
|
||||||
|
schema: z.string(),
|
||||||
|
}),
|
||||||
|
output: z.object({
|
||||||
|
valid: z.boolean(),
|
||||||
|
nodeErrors: z.array(
|
||||||
|
z.object({
|
||||||
|
message: z.string(),
|
||||||
|
nodeID: z.string(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
edgeErrors: z.array(
|
||||||
|
z.object({
|
||||||
|
message: z.string(),
|
||||||
|
edge: z.object({
|
||||||
|
sourceNodeID: z.string(),
|
||||||
|
targetNodeID: z.string(),
|
||||||
|
sourcePortID: z.string().optional(),
|
||||||
|
targetPortID: z.string().optional(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
18
packages/runtime/interface/src/client/index.ts
Normal file
18
packages/runtime/interface/src/client/index.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type {
|
||||||
|
FlowGramAPIName,
|
||||||
|
TaskCancelInput,
|
||||||
|
TaskCancelOutput,
|
||||||
|
TaskReportInput,
|
||||||
|
TaskReportOutput,
|
||||||
|
TaskResultInput,
|
||||||
|
TaskResultOutput,
|
||||||
|
TaskRunInput,
|
||||||
|
TaskRunOutput,
|
||||||
|
} from '@api/index';
|
||||||
|
|
||||||
|
export interface IRuntimeClient {
|
||||||
|
[FlowGramAPIName.TaskRun]: (input: TaskRunInput) => Promise<TaskRunOutput | undefined>;
|
||||||
|
[FlowGramAPIName.TaskReport]: (input: TaskReportInput) => Promise<TaskReportOutput | undefined>;
|
||||||
|
[FlowGramAPIName.TaskResult]: (input: TaskResultInput) => Promise<TaskResultOutput | undefined>;
|
||||||
|
[FlowGramAPIName.TaskCancel]: (input: TaskCancelInput) => Promise<TaskCancelOutput | undefined>;
|
||||||
|
}
|
||||||
5
packages/runtime/interface/src/index.ts
Normal file
5
packages/runtime/interface/src/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from './api';
|
||||||
|
export * from './schema';
|
||||||
|
export * from './node';
|
||||||
|
export * from './runtime';
|
||||||
|
export * from './client';
|
||||||
11
packages/runtime/interface/src/node/constant.ts
Normal file
11
packages/runtime/interface/src/node/constant.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export enum FlowGramNode {
|
||||||
|
Root = 'root',
|
||||||
|
Start = 'start',
|
||||||
|
End = 'end',
|
||||||
|
LLM = 'llm',
|
||||||
|
code = 'code',
|
||||||
|
Condition = 'condition',
|
||||||
|
Loop = 'loop',
|
||||||
|
Comment = 'comment',
|
||||||
|
Group = 'group',
|
||||||
|
}
|
||||||
13
packages/runtime/interface/src/node/end/index.ts
Normal file
13
packages/runtime/interface/src/node/end/index.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { IFlowConstantRefValue } from '@schema/value';
|
||||||
|
import { WorkflowNodeSchema } from '@schema/node';
|
||||||
|
import { IJsonSchema } from '@schema/json-schema';
|
||||||
|
import { FlowGramNode } from '@node/constant';
|
||||||
|
|
||||||
|
interface EndNodeData {
|
||||||
|
title: string;
|
||||||
|
inputs: IJsonSchema<'object'>;
|
||||||
|
outputs: IJsonSchema<'object'>;
|
||||||
|
outputValues: Record<string, IFlowConstantRefValue>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EndNodeSchema = WorkflowNodeSchema<FlowGramNode.End, EndNodeData>;
|
||||||
4
packages/runtime/interface/src/node/index.ts
Normal file
4
packages/runtime/interface/src/node/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export { FlowGramNode } from './constant';
|
||||||
|
export { EndNodeSchema } from './end';
|
||||||
|
export { LLMNodeSchema } from './llm';
|
||||||
|
export { StartNodeSchema } from './start';
|
||||||
20
packages/runtime/interface/src/node/llm/index.ts
Normal file
20
packages/runtime/interface/src/node/llm/index.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { IFlowConstantRefValue } from '@schema/value';
|
||||||
|
import { WorkflowNodeSchema } from '@schema/node';
|
||||||
|
import { IJsonSchema } from '@schema/json-schema';
|
||||||
|
import { FlowGramNode } from '@node/constant';
|
||||||
|
|
||||||
|
interface LLMNodeData {
|
||||||
|
title: string;
|
||||||
|
inputs: IJsonSchema<'object'>;
|
||||||
|
outputs: IJsonSchema<'object'>;
|
||||||
|
inputValues: {
|
||||||
|
apiKey: IFlowConstantRefValue;
|
||||||
|
modelType: IFlowConstantRefValue;
|
||||||
|
baseURL: IFlowConstantRefValue;
|
||||||
|
temperature: IFlowConstantRefValue;
|
||||||
|
systemPrompt: IFlowConstantRefValue;
|
||||||
|
prompt: IFlowConstantRefValue;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LLMNodeSchema = WorkflowNodeSchema<FlowGramNode.LLM, LLMNodeData>;
|
||||||
10
packages/runtime/interface/src/node/start/index.ts
Normal file
10
packages/runtime/interface/src/node/start/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { WorkflowNodeSchema } from '@schema/node';
|
||||||
|
import { IJsonSchema } from '@schema/json-schema';
|
||||||
|
import { FlowGramNode } from '@node/constant';
|
||||||
|
|
||||||
|
interface StartNodeData {
|
||||||
|
title: string;
|
||||||
|
outputs: IJsonSchema<'object'>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StartNodeSchema = WorkflowNodeSchema<FlowGramNode.Start, StartNodeData>;
|
||||||
3
packages/runtime/interface/src/runtime/base/index.ts
Normal file
3
packages/runtime/interface/src/runtime/base/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export type { VOData } from './value-object';
|
||||||
|
export type { InvokeParams, WorkflowRuntimeInvoke } from './invoke';
|
||||||
|
export type { WorkflowInputs, WorkflowOutputs } from './inputs-outputs';
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export type WorkflowInputs = Record<string, any>;
|
||||||
|
export type WorkflowOutputs = Record<string, any>;
|
||||||
9
packages/runtime/interface/src/runtime/base/invoke.ts
Normal file
9
packages/runtime/interface/src/runtime/base/invoke.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { WorkflowSchema } from '@schema/index';
|
||||||
|
import { WorkflowInputs } from './inputs-outputs';
|
||||||
|
|
||||||
|
export interface InvokeParams {
|
||||||
|
schema: WorkflowSchema;
|
||||||
|
inputs: WorkflowInputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WorkflowRuntimeInvoke = (params: InvokeParams) => Promise<WorkflowInputs>;
|
||||||
@ -0,0 +1 @@
|
|||||||
|
export type VOData<T> = Omit<T, 'id'>;
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
export type ContainerService = any;
|
||||||
|
|
||||||
|
export interface IContainer {
|
||||||
|
get<T = ContainerService>(key: any): T;
|
||||||
|
}
|
||||||
25
packages/runtime/interface/src/runtime/context/index.ts
Normal file
25
packages/runtime/interface/src/runtime/context/index.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { IVariableStore } from '@runtime/variable';
|
||||||
|
import { IStatusCenter } from '@runtime/status';
|
||||||
|
import { ISnapshotCenter } from '@runtime/snapshot';
|
||||||
|
import { IIOCenter } from '@runtime/io-center';
|
||||||
|
import { IState } from '../state';
|
||||||
|
import { IReporter } from '../reporter';
|
||||||
|
import { IDocument } from '../document';
|
||||||
|
import { InvokeParams } from '../base';
|
||||||
|
|
||||||
|
export interface ContextData {
|
||||||
|
variableStore: IVariableStore;
|
||||||
|
state: IState;
|
||||||
|
document: IDocument;
|
||||||
|
ioCenter: IIOCenter;
|
||||||
|
snapshotCenter: ISnapshotCenter;
|
||||||
|
statusCenter: IStatusCenter;
|
||||||
|
reporter: IReporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IContext extends ContextData {
|
||||||
|
id: string;
|
||||||
|
init(params: InvokeParams): void;
|
||||||
|
dispose(): void;
|
||||||
|
sub(): IContext;
|
||||||
|
}
|
||||||
14
packages/runtime/interface/src/runtime/document/document.ts
Normal file
14
packages/runtime/interface/src/runtime/document/document.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { WorkflowSchema } from '@schema/index';
|
||||||
|
import { INode } from './node';
|
||||||
|
import { IEdge } from './edge';
|
||||||
|
|
||||||
|
export interface IDocument {
|
||||||
|
id: string;
|
||||||
|
nodes: INode[];
|
||||||
|
edges: IEdge[];
|
||||||
|
root: INode;
|
||||||
|
start: INode;
|
||||||
|
end: INode;
|
||||||
|
init(schema: WorkflowSchema): void;
|
||||||
|
dispose(): void;
|
||||||
|
}
|
||||||
16
packages/runtime/interface/src/runtime/document/edge.ts
Normal file
16
packages/runtime/interface/src/runtime/document/edge.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { IPort } from './port';
|
||||||
|
import { INode } from './node';
|
||||||
|
|
||||||
|
export interface IEdge {
|
||||||
|
id: string;
|
||||||
|
from: INode;
|
||||||
|
to: INode;
|
||||||
|
fromPort: IPort;
|
||||||
|
toPort: IPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateEdgeParams {
|
||||||
|
id: string;
|
||||||
|
from: INode;
|
||||||
|
to: INode;
|
||||||
|
}
|
||||||
4
packages/runtime/interface/src/runtime/document/index.ts
Normal file
4
packages/runtime/interface/src/runtime/document/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export type { IDocument } from './document';
|
||||||
|
export type { IEdge, CreateEdgeParams } from './edge';
|
||||||
|
export type { NodeDeclare as NodeVariable, INode, CreateNodeParams } from './node';
|
||||||
|
export type { IPort, CreatePortParams } from './port';
|
||||||
41
packages/runtime/interface/src/runtime/document/node.ts
Normal file
41
packages/runtime/interface/src/runtime/document/node.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { IFlowConstantRefValue, IJsonSchema, PositionSchema } from '@schema/index';
|
||||||
|
import { FlowGramNode } from '@node/constant';
|
||||||
|
import { IPort } from './port';
|
||||||
|
import { IEdge } from './edge';
|
||||||
|
|
||||||
|
export interface NodeDeclare {
|
||||||
|
inputsValues?: Record<string, IFlowConstantRefValue>;
|
||||||
|
inputs?: IJsonSchema;
|
||||||
|
outputs?: IJsonSchema;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INode<T = any> {
|
||||||
|
id: string;
|
||||||
|
type: FlowGramNode;
|
||||||
|
name: string;
|
||||||
|
position: PositionSchema;
|
||||||
|
declare: NodeDeclare;
|
||||||
|
data: T;
|
||||||
|
ports: {
|
||||||
|
inputs: IPort[];
|
||||||
|
outputs: IPort[];
|
||||||
|
};
|
||||||
|
edges: {
|
||||||
|
inputs: IEdge[];
|
||||||
|
outputs: IEdge[];
|
||||||
|
};
|
||||||
|
parent: INode | null;
|
||||||
|
children: INode[];
|
||||||
|
prev: INode[];
|
||||||
|
next: INode[];
|
||||||
|
isBranch: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateNodeParams {
|
||||||
|
id: string;
|
||||||
|
type: FlowGramNode;
|
||||||
|
name: string;
|
||||||
|
position: PositionSchema;
|
||||||
|
variable?: NodeDeclare;
|
||||||
|
data?: any;
|
||||||
|
}
|
||||||
16
packages/runtime/interface/src/runtime/document/port.ts
Normal file
16
packages/runtime/interface/src/runtime/document/port.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { WorkflowPortType } from '@schema/index';
|
||||||
|
import { INode } from './node';
|
||||||
|
import { IEdge } from './edge';
|
||||||
|
|
||||||
|
export interface IPort {
|
||||||
|
id: string;
|
||||||
|
node: INode;
|
||||||
|
edges: IEdge[];
|
||||||
|
type: WorkflowPortType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreatePortParams {
|
||||||
|
id: string;
|
||||||
|
node: INode;
|
||||||
|
type: WorkflowPortType;
|
||||||
|
}
|
||||||
16
packages/runtime/interface/src/runtime/engine/index.ts
Normal file
16
packages/runtime/interface/src/runtime/engine/index.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { ITask } from '../task';
|
||||||
|
import { IExecutor } from '../executor';
|
||||||
|
import { INode } from '../document';
|
||||||
|
import { IContext } from '../context';
|
||||||
|
import { InvokeParams } from '../base';
|
||||||
|
|
||||||
|
export interface EngineServices {
|
||||||
|
Executor: IExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IEngine {
|
||||||
|
invoke(params: InvokeParams): ITask;
|
||||||
|
executeNode(params: { context: IContext; node: INode }): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IEngine = Symbol.for('Engine');
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import { ExecutionContext, ExecutionResult, INodeExecutor } from './node-executor';
|
||||||
|
|
||||||
|
export interface IExecutor {
|
||||||
|
execute: (context: ExecutionContext) => Promise<ExecutionResult>;
|
||||||
|
register: (executor: INodeExecutor) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IExecutor = Symbol.for('Executor');
|
||||||
7
packages/runtime/interface/src/runtime/executor/index.ts
Normal file
7
packages/runtime/interface/src/runtime/executor/index.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export { IExecutor } from './executor';
|
||||||
|
export type {
|
||||||
|
ExecutionContext,
|
||||||
|
ExecutionResult,
|
||||||
|
INodeExecutor,
|
||||||
|
INodeExecutorFactory,
|
||||||
|
} from './node-executor';
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import { FlowGramNode } from '@node/index';
|
||||||
|
import { INode } from '../document';
|
||||||
|
import { IContext } from '../context';
|
||||||
|
import { IContainer } from '../container';
|
||||||
|
import { WorkflowInputs, WorkflowOutputs } from '../base';
|
||||||
|
|
||||||
|
export interface ExecutionContext {
|
||||||
|
node: INode;
|
||||||
|
inputs: WorkflowInputs;
|
||||||
|
container: IContainer;
|
||||||
|
runtime: IContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExecutionResult {
|
||||||
|
outputs: WorkflowOutputs;
|
||||||
|
branch?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodeExecutor {
|
||||||
|
type: FlowGramNode;
|
||||||
|
execute: (context: ExecutionContext) => Promise<ExecutionResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INodeExecutorFactory {
|
||||||
|
new (): INodeExecutor;
|
||||||
|
}
|
||||||
14
packages/runtime/interface/src/runtime/index.ts
Normal file
14
packages/runtime/interface/src/runtime/index.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export * from './container';
|
||||||
|
export * from './base';
|
||||||
|
export * from './engine';
|
||||||
|
export * from './context';
|
||||||
|
export * from './document';
|
||||||
|
export * from './executor';
|
||||||
|
export * from './io-center';
|
||||||
|
export * from './snapshot';
|
||||||
|
export * from './reporter';
|
||||||
|
export * from './state';
|
||||||
|
export * from './status';
|
||||||
|
export * from './task';
|
||||||
|
export * from './validation';
|
||||||
|
export * from './variable';
|
||||||
17
packages/runtime/interface/src/runtime/io-center/index.ts
Normal file
17
packages/runtime/interface/src/runtime/io-center/index.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { WorkflowInputs, WorkflowOutputs } from '../base';
|
||||||
|
|
||||||
|
export interface IOData {
|
||||||
|
inputs: WorkflowInputs;
|
||||||
|
outputs: WorkflowOutputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Input & Output */
|
||||||
|
export interface IIOCenter {
|
||||||
|
inputs: WorkflowInputs;
|
||||||
|
outputs: WorkflowOutputs;
|
||||||
|
setInputs(inputs: WorkflowInputs): void;
|
||||||
|
setOutputs(outputs: WorkflowOutputs): void;
|
||||||
|
init(inputs: WorkflowInputs): void;
|
||||||
|
dispose(): void;
|
||||||
|
export(): IOData;
|
||||||
|
}
|
||||||
23
packages/runtime/interface/src/runtime/reporter/index.ts
Normal file
23
packages/runtime/interface/src/runtime/reporter/index.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { StatusData, IStatusCenter } from '../status';
|
||||||
|
import { Snapshot, ISnapshotCenter } from '../snapshot';
|
||||||
|
import { WorkflowInputs, WorkflowOutputs } from '../base';
|
||||||
|
export interface NodeReport extends StatusData {
|
||||||
|
id: string;
|
||||||
|
snapshots: Snapshot[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReport {
|
||||||
|
id: string;
|
||||||
|
inputs: WorkflowInputs;
|
||||||
|
outputs: WorkflowOutputs;
|
||||||
|
workflowStatus: StatusData;
|
||||||
|
reports: Record<string, NodeReport>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IReporter {
|
||||||
|
snapshotCenter: ISnapshotCenter;
|
||||||
|
statusCenter: IStatusCenter;
|
||||||
|
init(): void;
|
||||||
|
dispose(): void;
|
||||||
|
export(): IReport;
|
||||||
|
}
|
||||||
2
packages/runtime/interface/src/runtime/snapshot/index.ts
Normal file
2
packages/runtime/interface/src/runtime/snapshot/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export type { ISnapshot, Snapshot, SnapshotData } from './snapshot';
|
||||||
|
export type { ISnapshotCenter } from './snapshot-center';
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { ISnapshot, Snapshot, SnapshotData } from './snapshot';
|
||||||
|
|
||||||
|
export interface ISnapshotCenter {
|
||||||
|
id: string;
|
||||||
|
create(snapshot: Partial<SnapshotData>): ISnapshot;
|
||||||
|
exportAll(): Snapshot[];
|
||||||
|
export(): Record<string, Snapshot[]>;
|
||||||
|
init(): void;
|
||||||
|
dispose(): void;
|
||||||
|
}
|
||||||
21
packages/runtime/interface/src/runtime/snapshot/snapshot.ts
Normal file
21
packages/runtime/interface/src/runtime/snapshot/snapshot.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { WorkflowInputs, WorkflowOutputs } from '../base';
|
||||||
|
|
||||||
|
export interface SnapshotData {
|
||||||
|
nodeID: string;
|
||||||
|
inputs: WorkflowInputs;
|
||||||
|
outputs: WorkflowOutputs;
|
||||||
|
data: any;
|
||||||
|
branch?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Snapshot extends SnapshotData {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISnapshot {
|
||||||
|
id: string;
|
||||||
|
data: Partial<SnapshotData>;
|
||||||
|
addData(data: Partial<SnapshotData>): void;
|
||||||
|
validate(): boolean;
|
||||||
|
export(): Snapshot;
|
||||||
|
}
|
||||||
20
packages/runtime/interface/src/runtime/state/index.ts
Normal file
20
packages/runtime/interface/src/runtime/state/index.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { IFlowConstantRefValue, IFlowRefValue, WorkflowVariableType } from '@schema/index';
|
||||||
|
import { IVariableParseResult, IVariableStore } from '../variable';
|
||||||
|
import { INode } from '../document';
|
||||||
|
import { WorkflowInputs, WorkflowOutputs } from '../base';
|
||||||
|
|
||||||
|
export interface IState {
|
||||||
|
id: string;
|
||||||
|
variableStore: IVariableStore;
|
||||||
|
init(): void;
|
||||||
|
dispose(): void;
|
||||||
|
getNodeInputs(node: INode): WorkflowInputs;
|
||||||
|
setNodeOutputs(params: { node: INode; outputs: WorkflowOutputs }): void;
|
||||||
|
parseRef<T = unknown>(ref: IFlowRefValue): IVariableParseResult<T> | null;
|
||||||
|
parseValue<T = unknown>(
|
||||||
|
flowValue: IFlowConstantRefValue,
|
||||||
|
type?: WorkflowVariableType
|
||||||
|
): IVariableParseResult<T> | null;
|
||||||
|
isExecutedNode(node: INode): boolean;
|
||||||
|
addExecutedNode(node: INode): void;
|
||||||
|
}
|
||||||
33
packages/runtime/interface/src/runtime/status/index.ts
Normal file
33
packages/runtime/interface/src/runtime/status/index.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
export enum WorkflowStatus {
|
||||||
|
Pending = 'pending',
|
||||||
|
Processing = 'processing',
|
||||||
|
Succeeded = 'succeeded',
|
||||||
|
Failed = 'failed',
|
||||||
|
Canceled = 'canceled',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StatusData {
|
||||||
|
status: WorkflowStatus;
|
||||||
|
terminated: boolean;
|
||||||
|
startTime: number;
|
||||||
|
endTime?: number;
|
||||||
|
timeCost: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IStatus extends StatusData {
|
||||||
|
id: string;
|
||||||
|
process(): void;
|
||||||
|
success(): void;
|
||||||
|
fail(): void;
|
||||||
|
cancel(): void;
|
||||||
|
export(): StatusData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IStatusCenter {
|
||||||
|
workflow: IStatus;
|
||||||
|
nodeStatus(nodeID: string): IStatus;
|
||||||
|
init(): void;
|
||||||
|
dispose(): void;
|
||||||
|
getStatusNodeIDs(status: WorkflowStatus): string[];
|
||||||
|
exportNodeStatus(): Record<string, StatusData>;
|
||||||
|
}
|
||||||
14
packages/runtime/interface/src/runtime/task/index.ts
Normal file
14
packages/runtime/interface/src/runtime/task/index.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { IContext } from '../context';
|
||||||
|
import { WorkflowOutputs } from '../base';
|
||||||
|
|
||||||
|
export interface ITask {
|
||||||
|
id: string;
|
||||||
|
processing: Promise<WorkflowOutputs>;
|
||||||
|
context: IContext;
|
||||||
|
cancel(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskParams {
|
||||||
|
processing: Promise<WorkflowOutputs>;
|
||||||
|
context: IContext;
|
||||||
|
}
|
||||||
12
packages/runtime/interface/src/runtime/validation/index.ts
Normal file
12
packages/runtime/interface/src/runtime/validation/index.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { WorkflowSchema } from '@schema/index';
|
||||||
|
|
||||||
|
export interface ValidationResult {
|
||||||
|
valid: boolean;
|
||||||
|
errors?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IValidation {
|
||||||
|
validate(schema: WorkflowSchema): ValidationResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const IValidation = Symbol.for('Validation');
|
||||||
44
packages/runtime/interface/src/runtime/variable/index.ts
Normal file
44
packages/runtime/interface/src/runtime/variable/index.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { WorkflowVariableType } from '@schema/index';
|
||||||
|
|
||||||
|
interface VariableTypeInfo {
|
||||||
|
type: WorkflowVariableType;
|
||||||
|
itemsType?: WorkflowVariableType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IVariable<T = Object> extends VariableTypeInfo {
|
||||||
|
id: string;
|
||||||
|
nodeID: string;
|
||||||
|
key: string;
|
||||||
|
value: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IVariableParseResult<T = unknown> extends VariableTypeInfo {
|
||||||
|
value: T;
|
||||||
|
type: WorkflowVariableType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IVariableStore {
|
||||||
|
id: string;
|
||||||
|
store: Map<string, Map<string, IVariable>>;
|
||||||
|
setParent(parent: IVariableStore): void;
|
||||||
|
setVariable(
|
||||||
|
params: {
|
||||||
|
nodeID: string;
|
||||||
|
key: string;
|
||||||
|
value: Object;
|
||||||
|
} & VariableTypeInfo
|
||||||
|
): void;
|
||||||
|
setValue(params: {
|
||||||
|
nodeID: string;
|
||||||
|
variableKey: string;
|
||||||
|
variablePath?: string[];
|
||||||
|
value: Object;
|
||||||
|
}): void;
|
||||||
|
getValue<T = unknown>(params: {
|
||||||
|
nodeID: string;
|
||||||
|
variableKey: string;
|
||||||
|
variablePath?: string[];
|
||||||
|
}): IVariableParseResult<T> | null;
|
||||||
|
init(): void;
|
||||||
|
dispose(): void;
|
||||||
|
}
|
||||||
14
packages/runtime/interface/src/schema/constant.ts
Normal file
14
packages/runtime/interface/src/schema/constant.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export enum WorkflowPortType {
|
||||||
|
Input = 'input',
|
||||||
|
Output = 'output',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum WorkflowVariableType {
|
||||||
|
String = 'string',
|
||||||
|
Integer = 'integer',
|
||||||
|
Number = 'number',
|
||||||
|
Boolean = 'boolean',
|
||||||
|
Object = 'object',
|
||||||
|
Array = 'array',
|
||||||
|
Null = 'null',
|
||||||
|
}
|
||||||
6
packages/runtime/interface/src/schema/edge.ts
Normal file
6
packages/runtime/interface/src/schema/edge.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export interface WorkflowEdgeSchema {
|
||||||
|
sourceNodeID: string;
|
||||||
|
targetNodeID: string;
|
||||||
|
sourcePortID?: string;
|
||||||
|
targetPortID?: string;
|
||||||
|
}
|
||||||
8
packages/runtime/interface/src/schema/index.ts
Normal file
8
packages/runtime/interface/src/schema/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export { WorkflowEdgeSchema } from './edge';
|
||||||
|
export { JsonSchemaBasicType, IJsonSchema, IBasicJsonSchema } from './json-schema';
|
||||||
|
export { WorkflowNodeMetaSchema } from './node-meta';
|
||||||
|
export { WorkflowNodeSchema } from './node';
|
||||||
|
export { WorkflowSchema } from './workflow';
|
||||||
|
export { XYSchema, PositionSchema } from './xy';
|
||||||
|
export { WorkflowPortType, WorkflowVariableType } from './constant';
|
||||||
|
export { IFlowConstantRefValue, IFlowConstantValue, IFlowRefValue } from './value';
|
||||||
33
packages/runtime/interface/src/schema/json-schema.ts
Normal file
33
packages/runtime/interface/src/schema/json-schema.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// TODO copy packages/materials/form-materials/src/typings/json-schema/index.ts
|
||||||
|
|
||||||
|
export type JsonSchemaBasicType =
|
||||||
|
| 'boolean'
|
||||||
|
| 'string'
|
||||||
|
| 'integer'
|
||||||
|
| 'number'
|
||||||
|
| 'object'
|
||||||
|
| 'array'
|
||||||
|
| 'map';
|
||||||
|
|
||||||
|
export interface IJsonSchema<T = string> {
|
||||||
|
type: T;
|
||||||
|
default?: any;
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
enum?: (string | number)[];
|
||||||
|
properties?: Record<string, IJsonSchema<T>>;
|
||||||
|
additionalProperties?: IJsonSchema<T>;
|
||||||
|
items?: IJsonSchema<T>;
|
||||||
|
required?: string[];
|
||||||
|
$ref?: string;
|
||||||
|
extra?: {
|
||||||
|
index?: number;
|
||||||
|
// Used in BaseType.isEqualWithJSONSchema, the type comparison will be weak
|
||||||
|
weak?: boolean;
|
||||||
|
// Set the render component
|
||||||
|
formComponent?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IBasicJsonSchema = IJsonSchema<JsonSchemaBasicType>;
|
||||||
6
packages/runtime/interface/src/schema/node-meta.ts
Normal file
6
packages/runtime/interface/src/schema/node-meta.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { PositionSchema } from './xy';
|
||||||
|
|
||||||
|
export interface WorkflowNodeMetaSchema {
|
||||||
|
position: PositionSchema;
|
||||||
|
canvasPosition?: PositionSchema;
|
||||||
|
}
|
||||||
19
packages/runtime/interface/src/schema/node.ts
Normal file
19
packages/runtime/interface/src/schema/node.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import type { IFlowConstantRefValue } from './value';
|
||||||
|
import type { WorkflowNodeMetaSchema } from './node-meta';
|
||||||
|
import { IJsonSchema } from './json-schema';
|
||||||
|
import type { WorkflowEdgeSchema } from './edge';
|
||||||
|
|
||||||
|
export interface WorkflowNodeSchema<T = string, D = any> {
|
||||||
|
id: string;
|
||||||
|
type: T;
|
||||||
|
meta: WorkflowNodeMetaSchema;
|
||||||
|
data: D & {
|
||||||
|
title?: string;
|
||||||
|
inputsValues?: Record<string, IFlowConstantRefValue>;
|
||||||
|
inputs?: IJsonSchema;
|
||||||
|
outputs?: IJsonSchema;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
blocks?: WorkflowNodeSchema[];
|
||||||
|
edges?: WorkflowEdgeSchema[];
|
||||||
|
}
|
||||||
29
packages/runtime/interface/src/schema/value.ts
Normal file
29
packages/runtime/interface/src/schema/value.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// TODO copy packages/materials/form-materials/src/typings/flow-value/index.ts
|
||||||
|
|
||||||
|
export interface IFlowConstantValue {
|
||||||
|
type: 'constant';
|
||||||
|
content?: string | number | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFlowRefValue {
|
||||||
|
type: 'ref';
|
||||||
|
content?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFlowExpressionValue {
|
||||||
|
type: 'expression';
|
||||||
|
content?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFlowTemplateValue {
|
||||||
|
type: 'template';
|
||||||
|
content?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IFlowValue =
|
||||||
|
| IFlowConstantValue
|
||||||
|
| IFlowRefValue
|
||||||
|
| IFlowExpressionValue
|
||||||
|
| IFlowTemplateValue;
|
||||||
|
|
||||||
|
export type IFlowConstantRefValue = IFlowConstantValue | IFlowRefValue;
|
||||||
7
packages/runtime/interface/src/schema/workflow.ts
Normal file
7
packages/runtime/interface/src/schema/workflow.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import type { WorkflowNodeSchema } from './node';
|
||||||
|
import type { WorkflowEdgeSchema } from './edge';
|
||||||
|
|
||||||
|
export interface WorkflowSchema {
|
||||||
|
nodes: WorkflowNodeSchema[];
|
||||||
|
edges: WorkflowEdgeSchema[];
|
||||||
|
}
|
||||||
6
packages/runtime/interface/src/schema/xy.ts
Normal file
6
packages/runtime/interface/src/schema/xy.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export interface XYSchema {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PositionSchema = XYSchema;
|
||||||
29
packages/runtime/interface/tsconfig.json
Normal file
29
packages/runtime/interface/tsconfig.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"extends": "@flowgram.ai/ts-config/tsconfig.flow.path.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "src",
|
||||||
|
"paths": {
|
||||||
|
"@api/*": [
|
||||||
|
"api/*"
|
||||||
|
],
|
||||||
|
"@node/*": [
|
||||||
|
"node/*"
|
||||||
|
],
|
||||||
|
"@runtime/*": [
|
||||||
|
"runtime/*"
|
||||||
|
],
|
||||||
|
"@schema/*": [
|
||||||
|
"schema/*"
|
||||||
|
],
|
||||||
|
"@client/*": [
|
||||||
|
"client/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
6
packages/runtime/js-core/.eslintrc.cjs
Normal file
6
packages/runtime/js-core/.eslintrc.cjs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
const { defineConfig } = require('@flowgram.ai/eslint-config');
|
||||||
|
|
||||||
|
module.exports = defineConfig({
|
||||||
|
preset: 'base',
|
||||||
|
packageRoot: __dirname,
|
||||||
|
});
|
||||||
53
packages/runtime/js-core/package.json
Normal file
53
packages/runtime/js-core/package.json
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"name": "@flowgram.ai/runtime-js",
|
||||||
|
"version": "0.1.8",
|
||||||
|
"homepage": "https://flowgram.ai/",
|
||||||
|
"repository": "https://github.com/bytedance/flowgram.ai",
|
||||||
|
"license": "MIT",
|
||||||
|
"exports": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/esm/index.js",
|
||||||
|
"require": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"module": "./dist/esm/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"dev": "npm run watch",
|
||||||
|
"build": "npm run build:fast -- --dts-resolve",
|
||||||
|
"build:fast": "tsup src/index.ts --format cjs,esm --sourcemap --legacy-output",
|
||||||
|
"build:watch": "npm run build:fast -- --dts-resolve",
|
||||||
|
"clean": "rimraf dist",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:cov": "vitest run --coverage",
|
||||||
|
"ts-check": "tsc --noEmit",
|
||||||
|
"watch": "npm run build:fast -- --dts-resolve --watch --ignore-watch dist"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@langchain/openai": "^0.5.11",
|
||||||
|
"@langchain/core": "^0.3.57",
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
|
"zod": "^3.24.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@flowgram.ai/runtime-interface": "workspace:*",
|
||||||
|
"@flowgram.ai/eslint-config": "workspace:*",
|
||||||
|
"@flowgram.ai/ts-config": "workspace:*",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
|
"@types/uuid": "^9.0.1",
|
||||||
|
"@vitest/coverage-v8": "^0.32.0",
|
||||||
|
"eslint": "^8.54.0",
|
||||||
|
"dotenv": "~16.5.0",
|
||||||
|
"tsup": "^8.0.1",
|
||||||
|
"typescript": "^5.0.4",
|
||||||
|
"vitest": "^0.34.6"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public",
|
||||||
|
"registry": "https://registry.npmjs.org/"
|
||||||
|
}
|
||||||
|
}
|
||||||
17
packages/runtime/js-core/src/api/index.ts
Normal file
17
packages/runtime/js-core/src/api/index.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { FlowGramAPIName } from '@flowgram.ai/runtime-interface';
|
||||||
|
|
||||||
|
import { TaskRunAPI } from './task-run';
|
||||||
|
import { TaskResultAPI } from './task-result';
|
||||||
|
import { TaskReportAPI } from './task-report';
|
||||||
|
import { TaskCancelAPI } from './task-cancel';
|
||||||
|
|
||||||
|
export { TaskRunAPI, TaskResultAPI, TaskReportAPI, TaskCancelAPI };
|
||||||
|
|
||||||
|
export const WorkflowRuntimeAPIs: Record<FlowGramAPIName, (i: any) => any> = {
|
||||||
|
[FlowGramAPIName.TaskRun]: TaskRunAPI,
|
||||||
|
[FlowGramAPIName.TaskReport]: TaskReportAPI,
|
||||||
|
[FlowGramAPIName.TaskResult]: TaskResultAPI,
|
||||||
|
[FlowGramAPIName.TaskCancel]: TaskCancelAPI,
|
||||||
|
[FlowGramAPIName.ServerInfo]: () => {}, // TODO
|
||||||
|
[FlowGramAPIName.Validation]: () => {}, // TODO
|
||||||
|
};
|
||||||
13
packages/runtime/js-core/src/api/task-cancel.ts
Normal file
13
packages/runtime/js-core/src/api/task-cancel.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { TaskCancelInput, TaskCancelOutput } from '@flowgram.ai/runtime-interface';
|
||||||
|
|
||||||
|
import { WorkflowApplication } from '@application/workflow';
|
||||||
|
|
||||||
|
export const TaskCancelAPI = async (input: TaskCancelInput): Promise<TaskCancelOutput> => {
|
||||||
|
const app = WorkflowApplication.instance;
|
||||||
|
const { taskID } = input;
|
||||||
|
const success = app.cancel(taskID);
|
||||||
|
const output: TaskCancelOutput = {
|
||||||
|
success,
|
||||||
|
};
|
||||||
|
return output;
|
||||||
|
};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user