Skip to content

Commit 14441e7

Browse files
committed
tweaks : fetch node provides more visual feedback .. user-text-input disables button while flow is run
1 parent 33a50ee commit 14441e7

File tree

11 files changed

+182
-82
lines changed

11 files changed

+182
-82
lines changed

apps/vps-web/src/styles.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,18 @@ rect-node:has(.hover) > .shape-circle,
147147
animation: simple-loader-rotation 1s linear infinite;
148148
}
149149

150+
.simple-loader.text-black {
151+
border: 2px solid #000;
152+
border-bottom-color: transparent;
153+
}
154+
155+
.simple-loader--break::before {
156+
content: '';
157+
display: block;
158+
width: 100%;
159+
height: 0;
160+
}
161+
150162
@keyframes simple-loader-rotation {
151163
0% {
152164
transform: rotate(0deg);

examples-test-flows/openai-fetch-completion-as-stream-with-toolcall.json

Lines changed: 55 additions & 55 deletions
Large diffs are not rendered by default.

libs/app-canvas/src/app/flow-app.element.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2056,7 +2056,8 @@ export class FlowAppElement extends AppElement<NodeInfo> {
20562056

20572057
createRunCounterContext = (
20582058
isRunViaRunButton = false,
2059-
shouldResetConnectionSlider = true
2059+
shouldResetConnectionSlider = true,
2060+
onFlowFinished?: () => void
20602061
) => {
20612062
console.log(
20622063
'createRunCounterContext',
@@ -2080,6 +2081,9 @@ export class FlowAppElement extends AppElement<NodeInfo> {
20802081
'max',
20812082
(connectionExecuteHistory.length * 1000).toString()
20822083
);
2084+
if (onFlowFinished) {
2085+
onFlowFinished();
2086+
}
20832087
} else {
20842088
console.log(
20852089
'setRunCounterResetHandler: runCounter.runCounter > 0',

libs/visual-programming-system/src/forms/FormField.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export type FormField = (
4747
fieldType: 'Button';
4848
value: string;
4949
caption: string;
50+
customLoader?: boolean;
5051
onButtonClick?: () => Promise<void> | void;
5152
onChange?: (value: string, formComponent: IFormsComponent) => void;
5253
}

libs/visual-programming-system/src/forms/form-component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@ export class FormsComponent
444444
settings,
445445
setValue: this.setValue,
446446
onButtonClick: formControl.onButtonClick,
447+
customLoader: formControl.customLoader,
447448
isLast: index === this.props.formElements.length - 1,
448449
});
449450
this.components.push(formControlComponent);

libs/visual-programming-system/src/forms/form-fields/button.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface ButtonFieldProps extends BaseFormFieldProps {
1414
settings?: {
1515
showLabel?: boolean;
1616
};
17+
customLoader?: boolean;
1718
onButtonClick?: (formContext: FormContext) => Promise<void> | void;
1819
}
1920

@@ -74,20 +75,26 @@ export class ButtonFieldChildComponent extends FormFieldComponent<ButtonFieldPro
7475
this.isMounted = false;
7576
}
7677

77-
onButtonClick = async (_event: Event) => {
78+
onButtonClick = async (event: Event) => {
7879
if (this.props.onButtonClick) {
7980
if (!this.button) return;
80-
this.button.disabled = true;
81-
this.simpleLoader?.classList.remove('hidden');
81+
event?.preventDefault();
82+
if (!this.props.customLoader) {
83+
this.button.disabled = true;
84+
this.simpleLoader?.classList.remove('hidden');
85+
}
8286
try {
8387
await this.props.onButtonClick({
8488
setFormFieldValue: this.props.setValue,
8589
});
8690
} catch (error) {
8791
console.error(error);
8892
}
89-
this.simpleLoader?.classList.add('hidden');
90-
this.button.disabled = false;
93+
if (!this.props.customLoader) {
94+
this.simpleLoader?.classList.add('hidden');
95+
this.button.disabled = false;
96+
}
97+
return false;
9198
}
9299
};
93100
override render() {

libs/web-flow-executor/src/node-task-registry/canvas-node-task-registry.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,8 @@ export const getNodeFactoryNames = () => {
235235
export const setupCanvasNodeTaskRegistry = (
236236
createRunCounterContext: (
237237
isRunViaRunButton: boolean,
238-
shouldResetConnectionSlider: boolean
238+
shouldResetConnectionSlider: boolean,
239+
onFlowFinished?: () => void
239240
) => RunCounter,
240241
registerExternalNodes?: (
241242
registerNodeFactory: RegisterNodeFactoryFunction

libs/web-flow-executor/src/nodes/fetch.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
NodeTaskFactory,
1010
ThumbConnectionType,
1111
ThumbType,
12+
IDOMElement,
1213
} from '@devhelpr/visual-programming-system';
1314
import { NodeInfo } from '../types/node-info';
1415
import { RunCounter } from '../follow-path/run-counter';
@@ -24,6 +25,20 @@ export const getFetch: NodeTaskFactory<NodeInfo> = (
2425
let node: IRectNodeComponent<NodeInfo>;
2526
let errorNode: INodeComponent<NodeInfo>;
2627
let canvasAppInstance: IFlowCanvasBase<NodeInfo> | undefined = undefined;
28+
let loader: IDOMElement | undefined = undefined;
29+
30+
function showLoader() {
31+
if (loader && loader.domElement) {
32+
(loader.domElement as HTMLElement).classList.remove('hidden');
33+
}
34+
}
35+
36+
function hideLoader() {
37+
if (loader && loader.domElement) {
38+
(loader.domElement as HTMLElement).classList.add('hidden');
39+
}
40+
}
41+
2742
const initializeCompute = () => {
2843
return;
2944
};
@@ -54,6 +69,7 @@ export const getFetch: NodeTaskFactory<NodeInfo> = (
5469
}
5570
function sendEndStream() {
5671
return new Promise<void>((resolve) => {
72+
hideLoader();
5773
runNodeFromThumb(
5874
node.thumbConnectors![3],
5975
canvasAppInstance!,
@@ -83,6 +99,7 @@ export const getFetch: NodeTaskFactory<NodeInfo> = (
8399
);
84100
}
85101
function sendError(error: string) {
102+
hideLoader();
86103
runNodeFromThumb(
87104
node.thumbConnectors![1],
88105
canvasAppInstance!,
@@ -143,6 +160,7 @@ export const getFetch: NodeTaskFactory<NodeInfo> = (
143160
if (responseType === 'json') {
144161
headers.append('Content-Type', 'application/json');
145162
}
163+
showLoader();
146164
fetch(url, {
147165
method: httpMethod,
148166
headers,
@@ -194,6 +212,7 @@ export const getFetch: NodeTaskFactory<NodeInfo> = (
194212
dummyEndpoint: true,
195213
});
196214
});
215+
hideLoader();
197216
} catch (error) {
198217
isFullJson = false;
199218
}
@@ -327,12 +346,25 @@ export const getFetch: NodeTaskFactory<NodeInfo> = (
327346
class: `inner-node rounded p-4
328347
bg-amber-400 text-black
329348
font-bold
330-
flex flex-row justify-center items-center justify-start`,
349+
flex flex-col justify-center items-center justify-start`,
331350
},
332351
undefined,
333352
`Fetch ${text}`.trim()
334353
) as unknown as INodeComponent<NodeInfo>;
335-
354+
const loaderWrapper = createElement(
355+
'div',
356+
{
357+
class: `w-full flex justify-center `,
358+
},
359+
jsxComponentWrapper.domElement
360+
);
361+
loader = createElement(
362+
'div',
363+
{
364+
class: `simple-loader hidden text-black mt-2`,
365+
},
366+
loaderWrapper?.domElement
367+
);
336368
const rect = canvasApp.createRect(
337369
x,
338370
y,

libs/web-flow-executor/src/nodes/iframe-html-node.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ export const getIFrameHtmlNode = (updated: () => void): NodeTask<NodeInfo> => {
187187
const initializeCompute = () => {
188188
currentInput = '';
189189
variables = {};
190+
setHTML(currentInput);
190191
};
191192

192193
const compute = (input: string) => {

libs/web-flow-executor/src/nodes/split-by-case.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,25 @@ import {
2222
import { getVariablePayloadInputUtils } from './variable-payload-input-utils.ts/variable-payload-input-utils';
2323

2424
function handleExpression(expression: string, payload: any) {
25-
const compiledExpression = compileExpressionAsInfo(expression);
26-
const expressionFunction = (
27-
new Function('payload', `${compiledExpression.script}`) as unknown as (
28-
payload?: any
29-
) => any
30-
).bind(compiledExpression.bindings);
25+
try {
26+
const compiledExpression = compileExpressionAsInfo(expression);
27+
const expressionFunction = (
28+
new Function('payload', `${compiledExpression.script}`) as unknown as (
29+
payload?: any
30+
) => any
31+
).bind(compiledExpression.bindings);
3132

32-
const result = runExpression(
33-
expressionFunction,
34-
payload,
35-
false,
36-
compiledExpression.payloadProperties
37-
);
38-
return Boolean(result);
33+
const result = runExpression(
34+
expressionFunction,
35+
payload,
36+
false,
37+
compiledExpression.payloadProperties
38+
);
39+
return Boolean(result);
40+
} catch (error) {
41+
console.error('Split-by-case: Error in handleExpression', error);
42+
return false;
43+
}
3944
}
4045

4146
export const getSplitByCase = (updated: () => void): NodeTask<NodeInfo> => {

0 commit comments

Comments
 (0)