@motiadev/workbench 0.0.29 → 0.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.html +1 -1
  3. package/dist/src/components/app-sidebar.js +2 -2
  4. package/dist/src/components/logs/log-console.js +1 -1
  5. package/dist/src/components/logs/log-detail.js +1 -1
  6. package/dist/src/components/logs/log-field.js +7 -3
  7. package/dist/src/components/logs/logs.js +1 -1
  8. package/dist/src/components/states/state-detail.js +2 -4
  9. package/dist/src/components/states/state-value.d.ts +8 -0
  10. package/dist/src/components/states/state-value.js +51 -0
  11. package/dist/src/components/states/states.js +1 -1
  12. package/dist/src/components/ui/badge.js +2 -2
  13. package/dist/src/components/ui/button.d.ts +1 -1
  14. package/dist/src/components/ui/sidebar.d.ts +11 -55
  15. package/dist/src/components/ui/sidebar.js +14 -220
  16. package/dist/src/components/ui/table.js +1 -1
  17. package/dist/src/hooks/use-debounced.d.ts +1 -0
  18. package/dist/src/hooks/use-debounced.js +20 -0
  19. package/dist/src/index.css +17 -4
  20. package/dist/src/publicComponents/api-node.d.ts +2 -4
  21. package/dist/src/publicComponents/api-node.js +3 -3
  22. package/dist/src/publicComponents/base-handle.d.ts +1 -0
  23. package/dist/src/publicComponents/base-handle.js +5 -2
  24. package/dist/src/publicComponents/base-node.d.ts +3 -2
  25. package/dist/src/publicComponents/base-node.js +13 -9
  26. package/dist/src/publicComponents/colorMap.d.ts +6 -0
  27. package/dist/src/publicComponents/colorMap.js +6 -0
  28. package/dist/src/publicComponents/components/header-bar.d.ts +11 -0
  29. package/dist/src/publicComponents/components/header-bar.js +15 -0
  30. package/dist/src/publicComponents/cron-node.js +2 -2
  31. package/dist/src/publicComponents/emits.js +2 -2
  32. package/dist/src/publicComponents/event-node.d.ts +0 -1
  33. package/dist/src/publicComponents/event-node.js +3 -5
  34. package/dist/src/publicComponents/node-details.d.ts +17 -0
  35. package/dist/src/publicComponents/node-details.js +19 -0
  36. package/dist/src/publicComponents/subscribe.d.ts +1 -2
  37. package/dist/src/publicComponents/subscribe.js +2 -2
  38. package/dist/src/route-wrapper.js +2 -2
  39. package/dist/src/routes/flow.js +8 -4
  40. package/dist/src/routes/index.js +3 -2
  41. package/dist/src/views/flow/base-edge.js +3 -3
  42. package/dist/src/views/flow/flow-view.d.ts +7 -2
  43. package/dist/src/views/flow/flow-view.js +18 -5
  44. package/dist/src/views/flow/hooks/use-get-flow-state.d.ts +7 -3
  45. package/dist/src/views/flow/hooks/use-get-flow-state.js +9 -5
  46. package/dist/src/views/flow/hooks/use-save-workflow-config.d.ts +3 -0
  47. package/dist/src/views/flow/hooks/use-save-workflow-config.js +23 -0
  48. package/dist/src/views/flow/legend.js +46 -46
  49. package/dist/src/views/flow/node-organizer.js +3 -1
  50. package/dist/src/views/flow/nodes/language-indicator.js +7 -7
  51. package/dist/src/views/flow/nodes/nodes.types.d.ts +21 -7
  52. package/dist/tsconfig.app.tsbuildinfo +1 -1
  53. package/dist/tsconfig.node.tsbuildinfo +1 -1
  54. package/package.json +1 -1
  55. package/dist/src/components/states/state-field.d.ts +0 -8
  56. package/dist/src/components/states/state-field.js +0 -16
@@ -149,7 +149,7 @@ table {
149
149
  text-rendering: optimizeLegibility;
150
150
  -webkit-font-smoothing: antialiased;
151
151
  -moz-osx-font-smoothing: grayscale;
152
- font-family: 'PT Sans', serif;
152
+ font-family: 'DM Sans', serif;
153
153
  font-optical-sizing: auto;
154
154
  }
155
155
 
@@ -162,7 +162,7 @@ body {
162
162
 
163
163
  button,
164
164
  textarea {
165
- font-family: 'PT Sans', serif;
165
+ font-family: 'DM Sans', serif;
166
166
  }
167
167
 
168
168
  strong {
@@ -216,7 +216,8 @@ body,
216
216
  --sidebar-ring: 217.2 91.2% 59.8%;
217
217
  }
218
218
  .dark {
219
- --background: 224 71.4% 4.1%;
219
+ --background: 249 23% 11%;
220
+ --border: 240 18% 18%;
220
221
  --foreground: 210 20% 98%;
221
222
  --card: 224 71.4% 4.1%;
222
223
  --card-foreground: 210 20% 98%;
@@ -232,7 +233,6 @@ body,
232
233
  --accent-foreground: 210 20% 98%;
233
234
  --destructive: 0 62.8% 30.6%;
234
235
  --destructive-foreground: 210 20% 98%;
235
- --border: 215 27.9% 16.9%;
236
236
  --input: 215 27.9% 16.9%;
237
237
  --ring: 216 12.2% 83.9%;
238
238
  --chart-1: 220 70% 50%;
@@ -249,6 +249,19 @@ body,
249
249
  --sidebar-border: 240 3.7% 15.9%;
250
250
  --sidebar-ring: 217.2 91.2% 59.8%;
251
251
  }
252
+
253
+ .text-md {
254
+ font-size: 14px;
255
+ }
256
+
257
+ .text-lg {
258
+ font-size: 16px !important;
259
+ }
260
+
261
+ * {
262
+ -webkit-font-smoothing: antialiased;
263
+ -moz-osx-font-smoothing: grayscale;
264
+ }
252
265
  }
253
266
 
254
267
  @layer base {
@@ -1,7 +1,5 @@
1
1
  import { PropsWithChildren } from 'react';
2
2
  import { ApiNodeProps } from './node-props';
3
- type Props = PropsWithChildren<ApiNodeProps & {
4
- excludePubsub?: boolean;
5
- }>;
6
- export declare const ApiNode: ({ data, children, excludePubsub }: Props) => import("react/jsx-runtime").JSX.Element;
3
+ type Props = PropsWithChildren<ApiNodeProps>;
4
+ export declare const ApiNode: ({ data, children }: Props) => import("react/jsx-runtime").JSX.Element;
7
5
  export {};
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Webhook } from 'lucide-react';
3
3
  import { BaseNode } from './base-node';
4
- import { Emits } from './emits';
5
- export const ApiNode = ({ data, children, excludePubsub }) => {
6
- return (_jsxs(BaseNode, { variant: "api", title: data.name, disableSourceHandle: !data.emits?.length && !data.virtualEmits?.length, disableTargetHandle: !data.subscribes?.length && !data.virtualSubscribes?.length, children: [data.description && _jsx("div", { className: "text-sm max-w-[300px] text-white/60", children: data.description }), children, data.webhookUrl && (_jsxs("div", { className: "flex gap-1 items-center text-xs text-white/60", children: [_jsx(Webhook, { className: "w-3 h-3 text-white/40" }), _jsx("div", { className: "font-mono", children: data.webhookUrl })] })), !excludePubsub && _jsx(Emits, { emits: data.emits })] }));
4
+ import { DetailItem, NodeDetails } from './node-details';
5
+ export const ApiNode = ({ data, children }) => {
6
+ return (_jsxs(BaseNode, { variant: "api", title: data.name, language: data.language, disableSourceHandle: !data.emits?.length && !data.virtualEmits?.length, disableTargetHandle: !data.subscribes?.length && !data.virtualSubscribes?.length, children: [data.description && _jsx("div", { className: "text-sm text-white/60", children: data.description }), children, data.webhookUrl && (_jsxs("div", { className: "flex gap-1 items-center text-xs text-white/60", children: [_jsx(Webhook, { className: "w-3 h-3 text-white/40" }), _jsx("div", { className: "font-mono", children: data.webhookUrl })] })), _jsx(NodeDetails, { type: "api", name: data.name, subscribes: data.subscribes, emits: data.emits, description: data.description, language: data.language, children: _jsx(DetailItem, { label: "Webhook URL", children: _jsxs("div", { className: "flex gap-1 items-center text-xs text-white/60", children: [_jsx(Webhook, { className: "w-3 h-3 text-white/40" }), _jsx("div", { className: "font-mono", children: data.webhookUrl })] }) }) })] }));
7
7
  };
@@ -2,6 +2,7 @@ import React, { HTMLAttributes } from 'react';
2
2
  import { HandleProps } from '@xyflow/react';
3
3
  type Props = HandleProps & Omit<HTMLAttributes<HTMLDivElement>, 'id'> & {
4
4
  isHidden?: boolean;
5
+ variant?: string | null;
5
6
  };
6
7
  export declare const BaseHandle: React.FC<Props>;
7
8
  export {};
@@ -1,7 +1,10 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Position, Handle as RFHandle } from '@xyflow/react';
3
3
  import clsx from 'clsx';
4
+ import { colorMap } from './colorMap';
4
5
  export const BaseHandle = (props) => {
5
- const { isHidden, position, ...rest } = props;
6
- return (_jsx("div", { className: clsx('absolute w-1 h-1', position === Position.Top && '-top-[10px]', position === Position.Bottom && '-bottom-[10px]', 'left-1/2 -ml-[2px]', isHidden && 'hidden'), children: _jsx(RFHandle, { ...rest, position: position, className: "\n !static\n !w-1\n !h-1\n !min-w-[6px]\n !min-h-[6px]\n !p-0\n !border-none\n !bg-[#666666]\n !transform-none\n !rounded-full\n !outline-none\n !shadow-none\n " }) }));
6
+ const { isHidden, position, variant, ...rest } = props;
7
+ return (_jsx("div", { className: clsx('absolute w-[6px] h-[6px]', position === Position.Top && '-top-[20px]', position === Position.Bottom && '-bottom-[20px]', 'left-1/2 -ml-[2px]', isHidden && 'hidden'), children: _jsx(RFHandle, { ...rest, position: position, style: {
8
+ background: colorMap[variant],
9
+ }, className: "\n !static\n !w-[6px]\n !h-[6px]\n !min-w-[6px]\n !min-h-[6px]\n !p-0\n !border-none\n !transform-none\n !rounded-full\n !outline-none\n !shadow-none\n " }) }));
7
10
  };
@@ -1,11 +1,12 @@
1
1
  import { type VariantProps } from 'class-variance-authority';
2
2
  import React, { PropsWithChildren } from 'react';
3
- declare const baseBackgroundVariants: (props?: ({
3
+ declare const baseDot: (props?: ({
4
4
  variant?: "event" | "api" | "noop" | "cron" | null | undefined;
5
5
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
6
  type Props = PropsWithChildren<{
7
- variant?: VariantProps<typeof baseBackgroundVariants>['variant'];
8
7
  title: string;
8
+ variant: VariantProps<typeof baseDot>['variant'];
9
+ language?: string;
9
10
  headerChildren?: React.ReactNode;
10
11
  className?: string;
11
12
  disableSourceHandle?: boolean;
@@ -3,19 +3,23 @@ import { cn } from '@/lib/utils';
3
3
  import { Position } from '@xyflow/react';
4
4
  import { cva } from 'class-variance-authority';
5
5
  import { BaseHandle } from './base-handle';
6
- const baseNodeVariants = cva('relative flex flex-col min-w-[300px] rounded-md overflow-hidden font-mono');
7
- const baseBackgroundVariants = cva('absolute -inset-[1px] rounded-md bg-gradient-to-r', {
6
+ import { LanguageIndicator } from '../views/flow/nodes/language-indicator';
7
+ import { colorMap } from './colorMap';
8
+ const baseDot = cva('w-[6px] h-[6px] rounded-full', {
8
9
  variants: {
9
10
  variant: {
10
- event: 'from-teal-500/20 to-teal-400/10',
11
- api: 'from-blue-500/20 to-blue-400/10',
12
- noop: 'from-white/20 to-white/10',
13
- cron: 'from-purple-500/20 to-purple-400/10',
11
+ event: 'bg-[rgba(0,117,255,1)]',
12
+ api: 'bg-[rgba(189,255,0,1)]',
13
+ noop: 'bg-[rgba(255,49,234,1)]',
14
+ cron: 'bg-[rgba(255,113,11,1)]',
14
15
  },
15
16
  },
16
17
  });
17
- const HeaderBar = ({ text, children }) => (_jsxs("div", { className: "px-3 py-1 border-b border-white/20 bg-black/30 text-xs text-white/70 flex justify-between items-center", children: [_jsx("span", { children: text }), children] }));
18
+ const Dot = ({ variant }) => (_jsx("div", { className: cn(baseDot({ variant })) }));
19
+ const HeaderBar = ({ text, variant, children, }) => (_jsxs("div", { className: "text-sm text-white flex justify-between items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Dot, { variant: variant }), _jsx("span", { children: text })] }), children] }));
18
20
  export const BaseNode = (props) => {
19
- const { title, variant, className, children, disableSourceHandle, disableTargetHandle, headerChildren } = props;
20
- return (_jsxs("div", { className: "group relative", children: [_jsx("div", { className: cn(baseBackgroundVariants({ variant })) }), _jsxs("div", { className: cn(baseNodeVariants(), className), children: [_jsx(HeaderBar, { text: title, children: headerChildren }), _jsx("div", { className: "p-4 space-y-3", children: children })] }), !disableTargetHandle && _jsx(BaseHandle, { type: "target", position: Position.Top }), !disableSourceHandle && _jsx(BaseHandle, { type: "source", position: Position.Bottom }), _jsx("div", { className: "absolute inset-0 -z-10 translate-y-1 translate-x-1 bg-black/20 rounded-md border border-white/5" })] }));
21
+ const { title, variant, children, disableSourceHandle, disableTargetHandle, language } = props;
22
+ return (_jsx("div", { className: "p-[1px] rounded-lg shadow-[0px_7px_14px_0px_rgba(7,0,23,0.98)] max-w-[350px] ", style: {
23
+ background: `linear-gradient(100.74deg, rgba(0, 71, 255, 0) -2.15%, ${colorMap[variant]} 45.08%, rgba(0, 71, 255, 0) 96.79%)`,
24
+ }, children: _jsx("div", { className: "rounded-lg bg-[#060014] p-4", children: _jsxs("div", { className: "group relative", children: [_jsx(HeaderBar, { text: title, variant: variant, children: _jsx(LanguageIndicator, { language: language }) }), _jsx("div", { className: "pt-4 space-y-3", children: children }), !disableTargetHandle && _jsx(BaseHandle, { type: "target", position: Position.Top, variant: variant }), !disableSourceHandle && _jsx(BaseHandle, { type: "source", position: Position.Bottom, variant: variant }), _jsx("div", { className: "absolute inset-0 -z-10 translate-y-1 translate-x-1 bg-black/20 rounded-md border border-white/5" })] }) }) }));
21
25
  };
@@ -0,0 +1,6 @@
1
+ export declare const colorMap: {
2
+ event: string;
3
+ api: string;
4
+ noop: string;
5
+ cron: string;
6
+ };
@@ -0,0 +1,6 @@
1
+ export const colorMap = {
2
+ event: 'rgb(0,117,255)',
3
+ api: 'rgb(189,255,0)',
4
+ noop: 'rgb(255,49,234)',
5
+ cron: 'rgb(255,113,11)',
6
+ };
@@ -0,0 +1,11 @@
1
+ import { type VariantProps } from 'class-variance-authority';
2
+ import React from 'react';
3
+ declare const baseDot: (props?: ({
4
+ variant?: "event" | "api" | "noop" | "cron" | null | undefined;
5
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
6
+ export declare const HeaderBar: ({ text, variant, children, }: {
7
+ text: string;
8
+ variant: VariantProps<typeof baseDot>["variant"];
9
+ children?: React.ReactNode;
10
+ }) => import("react/jsx-runtime").JSX.Element;
11
+ export {};
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from '@/lib/utils';
3
+ import { cva } from 'class-variance-authority';
4
+ const baseDot = cva('w-[6px] h-[6px] rounded-full', {
5
+ variants: {
6
+ variant: {
7
+ event: 'bg-[rgba(0,117,255,1)]',
8
+ api: 'bg-[rgba(189,255,0,1)]',
9
+ noop: 'bg-[rgba(255,49,234,1)]',
10
+ cron: 'bg-[rgba(255,113,11,1)]',
11
+ },
12
+ },
13
+ });
14
+ const Dot = ({ variant }) => (_jsx("div", { className: cn(baseDot({ variant })) }));
15
+ export const HeaderBar = ({ text, variant, children, }) => (_jsxs("div", { className: "text-sm text-white flex justify-between items-center gap-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Dot, { variant: variant }), _jsx("span", { children: text })] }), children] }));
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { BaseNode } from './base-node';
3
- import { Emits } from './emits';
4
3
  import { Clock } from 'lucide-react';
4
+ import { NodeDetails, DetailItem } from './node-details';
5
5
  export const CronNode = ({ data }) => {
6
- return (_jsxs(BaseNode, { variant: "cron", title: data.name, headerChildren: _jsx(Clock, { className: "w-4 h-4 text-purple-400" }), disableTargetHandle: !data.virtualSubscribes?.length, disableSourceHandle: !data.virtualEmits?.length, children: [_jsx("div", { className: "text-sm text-white/70", children: data.description }), _jsxs("div", { className: "text-xs text-white/50 flex items-center gap-2", children: [_jsx(Clock, { className: "w-3 h-3" }), " ", data.cronExpression] }), _jsx(Emits, { emits: data.emits })] }));
6
+ return (_jsxs(BaseNode, { variant: "cron", title: data.name, language: data.language, headerChildren: _jsx(Clock, { className: "w-4 h-4 text-purple-400" }), disableTargetHandle: !data.virtualSubscribes?.length, disableSourceHandle: !data.virtualEmits?.length && !data.emits?.length, children: [_jsx("div", { className: "text-sm text-white/70", children: data.description }), _jsxs("div", { className: "text-xs text-white/50 flex items-center gap-2", children: [_jsx(Clock, { className: "w-3 h-3" }), " ", data.cronExpression] }), _jsx(NodeDetails, { type: "cron", name: data.name, description: data.description, language: data.language, children: _jsx(DetailItem, { label: "Cron Expression", children: _jsx("div", { className: "flex gap-1 items-center text-xs text-white/60", children: _jsxs("div", { className: "text-xs text-white/50 flex items-center gap-2", children: [_jsx(Clock, { className: "w-3 h-3" }), " ", data.cronExpression] }) }) }) })] }));
7
7
  };
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Send } from 'lucide-react';
3
- const toType = (emit) => typeof emit === 'string' ? emit : emit.type;
3
+ const toTopic = (emit) => typeof emit === 'string' ? emit : emit.topic;
4
4
  export const Emits = ({ emits }) => {
5
- return (_jsx(_Fragment, { children: emits.map((emit) => (_jsxs("div", { className: "flex gap-2 items-center text-xs text-white/60", "data-testid": `emits__${toType(emit)}`, children: [_jsx(Send, { className: "w-3 h-3 text-white/40" }), _jsx("div", { className: "font-mono tracking-wider", children: toType(emit) })] }, toType(emit)))) }));
5
+ return (_jsx(_Fragment, { children: emits.map((emit) => (_jsxs("div", { className: "flex gap-2 items-center text-xs text-white/60", "data-testid": `emits__${toTopic(emit)}`, children: [_jsx(Send, { className: "w-4 h-4 text-white/40" }), _jsx("div", { className: "font-mono tracking-wider", children: toTopic(emit) })] }, toTopic(emit)))) }));
6
6
  };
@@ -1,7 +1,6 @@
1
1
  import { PropsWithChildren } from 'react';
2
2
  import { EventNodeProps } from './node-props';
3
3
  type Props = PropsWithChildren<EventNodeProps & {
4
- excludePubsub?: boolean;
5
4
  className?: string;
6
5
  }>;
7
6
  export declare const EventNode: (props: Props) => import("react/jsx-runtime").JSX.Element;
@@ -1,9 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { LanguageIndicator } from '../views/flow/nodes/language-indicator';
3
2
  import { BaseNode } from './base-node';
4
- import { Emits } from './emits';
5
- import { Subscribe } from './subscribe';
3
+ import { NodeDetails } from './node-details';
6
4
  export const EventNode = (props) => {
7
- const { data, excludePubsub, children } = props;
8
- return (_jsxs(BaseNode, { variant: "event", title: data.name, disableSourceHandle: !data.emits.length && !data.virtualEmits?.length, disableTargetHandle: !data.subscribes.length && !data.virtualSubscribes?.length, headerChildren: _jsx(LanguageIndicator, { language: data.language }), children: [data.description && _jsx("div", { className: "text-sm max-w-[300px] text-white/60", children: data.description }), children, !excludePubsub && (_jsx("div", { className: "space-y-2 pt-2 border-t border-white/10", children: _jsx(Subscribe, { data: data }) })), _jsx(Emits, { emits: data.emits })] }));
5
+ const { data, children } = props;
6
+ return (_jsxs(BaseNode, { variant: "event", title: data.name, language: data.language, disableSourceHandle: !data.emits.length && !data.virtualEmits?.length, disableTargetHandle: !data.subscribes.length && !data.virtualSubscribes?.length, children: [children, _jsx(NodeDetails, { type: "event", name: data.name, subscribes: data.subscribes, emits: data.emits, description: data.description, language: data.language })] }));
9
7
  };
@@ -0,0 +1,17 @@
1
+ import React, { PropsWithChildren } from 'react';
2
+ type Props = PropsWithChildren<{
3
+ name: string;
4
+ type: 'event' | 'api' | 'noop' | 'cron';
5
+ subscribes?: string[];
6
+ emits?: Array<string | {
7
+ topic: string;
8
+ label?: string;
9
+ }>;
10
+ description?: string;
11
+ language?: string;
12
+ }>;
13
+ export declare const DetailItem: React.FC<PropsWithChildren<{
14
+ label: string;
15
+ }>>;
16
+ export declare const NodeDetails: React.FC<Props>;
17
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChevronRight } from 'lucide-react';
3
+ import { DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '../components/ui/dialog';
4
+ import { DialogContent } from '../components/ui/dialog';
5
+ import { Dialog } from '../components/ui/dialog';
6
+ import { Label } from '../components/ui/label';
7
+ import { LanguageIndicator } from '../views/flow/nodes/language-indicator';
8
+ import { Emits } from './emits';
9
+ import { Subscribe } from './subscribe';
10
+ import { colorMap } from './colorMap';
11
+ import { HeaderBar } from './components/header-bar';
12
+ export const DetailItem = (props) => {
13
+ const { label, children } = props;
14
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { className: "text-white", children: label }), children] }));
15
+ };
16
+ export const NodeDetails = (props) => {
17
+ const { name, type, subscribes, emits, description, language, children } = props;
18
+ return (_jsxs(Dialog, { children: [_jsx(DialogTrigger, { asChild: true, children: _jsx("div", { className: "flex justify-end gap-2", children: _jsx("div", { className: "border border-solid border-white/10 p-1 rounded-md cursor-pointer", children: _jsx(ChevronRight, { className: "w-4 h-4" }) }) }) }), _jsxs(DialogContent, { className: "border border-solid", style: { borderColor: colorMap[type] }, children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: _jsx(HeaderBar, { variant: type, text: name }) }) }), _jsx(DialogDescription, { children: _jsxs("div", { className: "flex flex-col gap-6", children: [description && (_jsx(DetailItem, { label: "Description", children: _jsx("span", { className: "text-sm text-white/60", children: description }) })), _jsx(DetailItem, { label: "Language", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(LanguageIndicator, { language: language }), _jsx("span", { className: "capitalize", children: language })] }) }), subscribes && (_jsx(DetailItem, { label: "Subscribes", children: _jsx(Subscribe, { subscribes: subscribes }) })), emits && (_jsx(DetailItem, { label: "Emits", children: _jsx(Emits, { emits: emits }) })), children] }) })] })] }));
19
+ };
@@ -1,5 +1,4 @@
1
1
  import React from 'react';
2
- import { EventNodeData } from '../views/flow/nodes/nodes.types';
3
2
  export declare const Subscribe: React.FC<{
4
- data: EventNodeData;
3
+ subscribes: string[];
5
4
  }>;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Eye } from 'lucide-react';
3
- export const Subscribe = ({ data }) => {
4
- return (_jsx(_Fragment, { children: data.subscribes.map((subscribe) => (_jsxs("div", { className: "flex gap-2 items-center text-xs text-white/60", "data-testid": `subscribes__${subscribe}`, children: [_jsx(Eye, { className: "w-3 h-3 text-white/40" }), _jsx("div", { className: "font-mono tracking-wider", children: subscribe })] }, subscribe))) }));
3
+ export const Subscribe = ({ subscribes }) => {
4
+ return (_jsx(_Fragment, { children: subscribes.map((subscribe) => (_jsxs("div", { className: "flex gap-2 items-center text-xs text-white/60", "data-testid": `subscribes__${subscribe}`, children: [_jsx(Eye, { className: "w-4 h-4 text-white/40" }), _jsx("div", { className: "font-mono tracking-wider", children: subscribe })] }, subscribe))) }));
5
5
  };
@@ -1,4 +1,4 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { AppSidebar } from './components/app-sidebar';
3
- import { SidebarProvider } from './components/ui/sidebar';
4
- export const RouteWrapper = ({ children }) => (_jsxs(SidebarProvider, { children: [_jsx(AppSidebar, {}), children] }));
3
+ import { ReactFlowProvider } from '@xyflow/react';
4
+ export const RouteWrapper = ({ children }) => (_jsx("div", { className: "flex flex-row", children: _jsxs(ReactFlowProvider, { children: [_jsx(AppSidebar, {}), _jsx("div", { className: "flex-1", children: children })] }) }));
@@ -7,14 +7,18 @@ export const Flow = () => {
7
7
  const { id } = useParams();
8
8
  const flowId = id;
9
9
  const [flow, setFlow] = useState(null);
10
+ const [flowConfig, setFlowConfig] = useState(null);
10
11
  const fetchFlow = useCallback(() => {
11
- fetch(`/flows/${flowId}`)
12
- .then((res) => res.json())
13
- .then((flow) => setFlow(flow));
12
+ Promise.all([fetch(`/flows/${flowId}`), fetch(`/flows/${flowId}/config`)])
13
+ .then(([flowRes, configRes]) => Promise.all([flowRes.json(), configRes.json()]))
14
+ .then(([flow, config]) => {
15
+ setFlow(flow);
16
+ setFlowConfig(config);
17
+ });
14
18
  }, [flowId]);
15
19
  useEffect(fetchFlow, [fetchFlow]);
16
20
  useFlowUpdateListener(flowId, fetchFlow);
17
21
  if (!flow)
18
22
  return null;
19
- return (_jsx("div", { className: "w-screen h-screen", children: _jsx(FlowView, { flow: flow }) }));
23
+ return (_jsx("div", { className: "w-full h-screen", children: _jsx(FlowView, { flow: flow, flowConfig: flowConfig }) }));
20
24
  };
@@ -1,4 +1,5 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button } from '../components/ui/button';
2
3
  export const Index = () => {
3
- return (_jsx("div", { className: "flex items-center justify-center w-full h-screen", children: _jsx("p", { className: "text-gray-500", children: "Select a flow" }) }));
4
+ return (_jsxs("div", { className: "flex flex-col items-center justify-center w-full h-screen gap-10 bg-gradient-to-r from-black to-gray-900", children: [_jsx("h1", { className: "text-5xl font-extrabold max-w-[600px] text-center", children: "Code-first framework for intelligent workflows" }), _jsx("div", { className: "max-w-[600px] text-center text-xl font-medium text-gray-400", children: "Write in any language. Automate anything. From AI agents to backend automation, Motia runs event-driven workflows with zero overhead." }), _jsx("div", { className: "p-[1px] min-w-[600px] rounded-lg shadow-[0px_7px_14px_0px_rgba(7,0,23,0.98),0px_26px_26px_0px_rgba(7,0,23,0.85),0px_59px_35px_0px_rgba(7,0,23,0.5),0px_105px_42px_0px_rgba(7,0,23,0.15),0px_164px_46px_0px_rgba(7,0,23,0.02)]", children: _jsx("div", { className: "rounded-lg bg-black p-8 font-semibold text-xl min-h-[100px] flex items-center", children: _jsxs("div", { className: "flex items-center gap-2 font-mono", children: [_jsx("span", { className: "text-[rgb(0,117,255)]", children: "$" }), _jsx("span", { className: "text-white", children: "npx motia generate step" })] }) }) }), _jsxs("div", { className: "flex flex-col gap-8 items-center", children: [_jsx("span", { className: "text-gray-400 text-xl", children: "or" }), _jsx("a", { href: "https://motia.dev/docs", target: "_blank", children: _jsx(Button, { size: "lg", className: "text-xl py-6 px-8", children: "Read developer docs" }) })] })] }));
4
5
  };
@@ -5,7 +5,7 @@ import { cn } from '@/lib/utils';
5
5
  const labelVariants = cva('absolute pointer-events-all text-cs border p-1 px-2', {
6
6
  variants: {
7
7
  color: {
8
- default: 'border-[#b3b3b3] bg-black text-gray-100 font-semibold border-solid rounded-full',
8
+ default: 'border-[#b3b3b3] bg-[#060014] text-gray-100 font-semibold border-solid rounded-full',
9
9
  conditional: 'bg-amber-300 border-amber-950 text-amber-950 border-solid font-semibold italic rounded-lg',
10
10
  },
11
11
  },
@@ -28,8 +28,8 @@ export const BaseEdge = (props) => {
28
28
  offset: 10,
29
29
  });
30
30
  return (_jsxs(_Fragment, { children: [_jsx(BaseReactFlowEdge, { path: edgePath, style: {
31
- stroke: data?.variant === 'virtual' ? 'rgb(147, 169, 197)' : 'rgb(133, 176, 132)',
32
- strokeWidth: 0.5,
31
+ stroke: data?.variant === 'virtual' ? 'rgb(111, 111, 111)' : '#0094FF',
32
+ strokeWidth: 2,
33
33
  shapeRendering: 'geometricPrecision',
34
34
  fill: 'none',
35
35
  mixBlendMode: 'screen',
@@ -1,8 +1,13 @@
1
- import '@xyflow/react/dist/style.css';
2
1
  import React from 'react';
3
- import { FlowResponse } from './hooks/use-get-flow-state';
2
+ import { EdgeData, NodeData } from './nodes/nodes.types';
3
+ import { FlowConfigResponse, FlowResponse } from './hooks/use-get-flow-state';
4
+ import { Node as ReactFlowNode, Edge as ReactFlowEdge } from '@xyflow/react';
5
+ import '@xyflow/react/dist/style.css';
6
+ export type FlowNode = ReactFlowNode<NodeData>;
7
+ export type FlowEdge = ReactFlowEdge<EdgeData>;
4
8
  type Props = {
5
9
  flow: FlowResponse;
10
+ flowConfig: FlowConfigResponse;
6
11
  };
7
12
  export declare const FlowView: React.FC<Props>;
8
13
  export {};
@@ -1,20 +1,25 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // packages/workbench/src/views/flow/flow-view.tsx
2
3
  import { LogConsole } from '@/components/logs/log-console';
3
- import { Background, BackgroundVariant, ReactFlow } from '@xyflow/react';
4
- import '@xyflow/react/dist/style.css';
4
+ import { Background, BackgroundVariant, ReactFlow, useReactFlow } from '@xyflow/react';
5
5
  import { useCallback, useEffect, useState } from 'react';
6
6
  import { ArrowHead } from './arrow-head';
7
7
  import { BaseEdge } from './base-edge';
8
8
  import { FlowLoader } from './flow-loader';
9
9
  import { useGetFlowState } from './hooks/use-get-flow-state';
10
10
  import { Legend } from './legend';
11
+ import { useSaveWorkflowConfig } from './hooks/use-save-workflow-config';
11
12
  import { NodeOrganizer } from './node-organizer';
13
+ import { useDebounced } from '@/hooks/use-debounced';
14
+ import '@xyflow/react/dist/style.css';
12
15
  const edgeTypes = {
13
16
  base: BaseEdge,
14
17
  };
15
- export const FlowView = ({ flow }) => {
16
- const { nodes, edges, onNodesChange, onEdgesChange, nodeTypes } = useGetFlowState(flow);
18
+ export const FlowView = ({ flow, flowConfig }) => {
19
+ const { nodes, edges, onNodesChange, onEdgesChange, nodeTypes } = useGetFlowState(flow, flowConfig);
17
20
  const [initialized, setInitialized] = useState(false);
21
+ const { getNodes, getEdges } = useReactFlow();
22
+ const { saveConfig } = useSaveWorkflowConfig(flow.id);
18
23
  const [hoveredType, setHoveredType] = useState(null);
19
24
  useEffect(() => setInitialized(false), [flow]);
20
25
  const onInitialized = useCallback(() => {
@@ -39,8 +44,16 @@ export const FlowView = ({ flow }) => {
39
44
  ...edge,
40
45
  className: getClassName(), // No argument means it's an edge
41
46
  }));
47
+ const saveFlowConfig = useCallback(() => {
48
+ return saveConfig({ steps: getNodes(), edges: getEdges() });
49
+ }, [saveConfig, getNodes, getEdges]);
50
+ const debouncedSaveConfig = useDebounced(saveFlowConfig);
51
+ const onNodesChangeHandler = useCallback((changes) => {
52
+ onNodesChange(changes);
53
+ debouncedSaveConfig();
54
+ }, [onNodesChange, debouncedSaveConfig]);
42
55
  if (!nodeTypes) {
43
56
  return null;
44
57
  }
45
- return (_jsxs("div", { className: "w-full h-full relative bg-black", children: [!initialized && _jsx(FlowLoader, {}), _jsx(Legend, { onHover: setHoveredType }), _jsxs(ReactFlow, { nodes: nodesWithHighlights, edges: edgesWithHighlights, nodeTypes: nodeTypes, edgeTypes: edgeTypes, onNodesChange: onNodesChange, onEdgesChange: onEdgesChange, children: [_jsx(Background, { variant: BackgroundVariant.Dots, gap: 20, size: 1, color: "#444" }), _jsx(NodeOrganizer, { onInitialized: onInitialized }), _jsx("svg", { children: _jsx("defs", { children: _jsx(ArrowHead, { color: "#B3B3B3", id: "arrowhead" }) }) })] }), _jsx(LogConsole, {})] }));
58
+ return (_jsxs("div", { className: "w-full h-full relative bg-black", children: [!initialized && _jsx(FlowLoader, {}), _jsx(Legend, { onHover: setHoveredType }), _jsxs(ReactFlow, { nodes: nodesWithHighlights, edges: edgesWithHighlights, nodeTypes: nodeTypes, edgeTypes: edgeTypes, onNodesChange: onNodesChangeHandler, onEdgesChange: onEdgesChange, children: [_jsx(Background, { variant: BackgroundVariant.Dots, gap: 20, size: 1, color: "#99a", bgColor: "#1d1c2a" }), _jsx(NodeOrganizer, { onInitialized: onInitialized }), _jsx("svg", { children: _jsx("defs", { children: _jsx(ArrowHead, { color: "#B3B3B3", id: "arrowhead" }) }) })] }), _jsx(LogConsole, {})] }));
46
59
  };
@@ -1,8 +1,8 @@
1
1
  import { Edge, Node } from '@xyflow/react';
2
2
  import React from 'react';
3
- import type { EdgeData, NodeData } from '../nodes/nodes.types';
3
+ import type { EdgeData, FlowNodeData, NodeData } from '../nodes/nodes.types';
4
4
  type Emit = string | {
5
- type: string;
5
+ topic: string;
6
6
  label?: string;
7
7
  };
8
8
  type FlowStep = {
@@ -24,13 +24,17 @@ export type FlowResponse = {
24
24
  steps: FlowStep[];
25
25
  edges: FlowEdge[];
26
26
  };
27
+ export type FlowConfigResponse = {
28
+ steps: FlowNodeData[];
29
+ edges: FlowEdge[];
30
+ };
27
31
  type FlowEdge = {
28
32
  id: string;
29
33
  source: string;
30
34
  target: string;
31
35
  data: EdgeData;
32
36
  };
33
- export declare const useGetFlowState: (flow: FlowResponse) => {
37
+ export declare const useGetFlowState: (flow: FlowResponse, flowConfig: FlowConfigResponse) => {
34
38
  nodes: Node<NodeData>[];
35
39
  edges: Edge<EdgeData>[];
36
40
  onNodesChange: import("@xyflow/react").OnNodesChange<Node<NodeData>>;
@@ -4,7 +4,11 @@ import { ApiFlowNode } from '../nodes/api-flow-node';
4
4
  import { NoopFlowNode } from '../nodes/noop-flow-node';
5
5
  import { EventFlowNode } from '../nodes/event-flow-node';
6
6
  import { CronNode } from '@/publicComponents/cron-node';
7
- async function importFlow(flow) {
7
+ const getNodePosition = (flowConfig, stepName) => {
8
+ const configStep = flowConfig?.steps?.find((step) => step.data?.name === stepName);
9
+ return configStep?.position || { x: 0, y: 0 };
10
+ };
11
+ async function importFlow(flow, flowConfig) {
8
12
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
13
  const nodeTypes = {
10
14
  event: EventFlowNode,
@@ -23,7 +27,7 @@ async function importFlow(flow) {
23
27
  const nodes = flow.steps.map((step) => ({
24
28
  id: step.id,
25
29
  type: step.nodeComponentPath ? step.nodeComponentPath : step.type,
26
- position: { x: 0, y: 0 },
30
+ position: getNodePosition(flowConfig, step.name),
27
31
  data: step,
28
32
  language: step.language,
29
33
  }));
@@ -34,7 +38,7 @@ async function importFlow(flow) {
34
38
  }));
35
39
  return { nodes, edges, nodeTypes };
36
40
  }
37
- export const useGetFlowState = (flow) => {
41
+ export const useGetFlowState = (flow, flowConfig) => {
38
42
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
43
  const [nodeTypes, setNodeTypes] = useState();
40
44
  const [nodes, setNodes, onNodesChange] = useNodesState([]);
@@ -42,11 +46,11 @@ export const useGetFlowState = (flow) => {
42
46
  useEffect(() => {
43
47
  if (!flow)
44
48
  return;
45
- importFlow(flow).then(({ nodes, edges, nodeTypes }) => {
49
+ importFlow(flow, flowConfig).then(({ nodes, edges, nodeTypes }) => {
46
50
  setNodes(nodes);
47
51
  setEdges(edges);
48
52
  setNodeTypes(nodeTypes);
49
53
  });
50
- }, [flow, setNodes, setEdges, setNodeTypes]);
54
+ }, [flow, flowConfig, setNodes, setEdges, setNodeTypes]);
51
55
  return { nodes, edges, onNodesChange, onEdgesChange, nodeTypes };
52
56
  };
@@ -0,0 +1,3 @@
1
+ export declare const useSaveWorkflowConfig: (flowId: string) => {
2
+ saveConfig: (config: any) => Promise<any>;
3
+ };
@@ -0,0 +1,23 @@
1
+ import { useCallback } from 'react';
2
+ export const useSaveWorkflowConfig = (flowId) => {
3
+ const saveConfig = useCallback(async (config) => {
4
+ try {
5
+ const response = await fetch(`/flows/${flowId}/config`, {
6
+ method: 'POST',
7
+ headers: {
8
+ 'Content-Type': 'application/json',
9
+ },
10
+ body: JSON.stringify(config),
11
+ });
12
+ if (!response.ok) {
13
+ throw new Error(`Failed to save config: ${response.statusText}`);
14
+ }
15
+ return await response.json();
16
+ }
17
+ catch (error) {
18
+ console.error('Error saving workflow config:', error);
19
+ throw error;
20
+ }
21
+ }, [flowId]);
22
+ return { saveConfig };
23
+ };