@dxos/react-ui-canvas-compute 0.7.5-labs.5f04cf6

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 (149) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +1 -0
  3. package/dist/lib/browser/index.mjs +2499 -0
  4. package/dist/lib/browser/index.mjs.map +7 -0
  5. package/dist/lib/browser/meta.json +1 -0
  6. package/dist/lib/node/index.cjs +2591 -0
  7. package/dist/lib/node/index.cjs.map +7 -0
  8. package/dist/lib/node/meta.json +1 -0
  9. package/dist/lib/node-esm/index.mjs +2499 -0
  10. package/dist/lib/node-esm/index.mjs.map +7 -0
  11. package/dist/lib/node-esm/meta.json +1 -0
  12. package/dist/types/src/compute-layout.d.ts +9 -0
  13. package/dist/types/src/compute-layout.d.ts.map +1 -0
  14. package/dist/types/src/compute.stories.d.ts +28 -0
  15. package/dist/types/src/compute.stories.d.ts.map +1 -0
  16. package/dist/types/src/graph/controller.d.ts +139 -0
  17. package/dist/types/src/graph/controller.d.ts.map +1 -0
  18. package/dist/types/src/graph/index.d.ts +3 -0
  19. package/dist/types/src/graph/index.d.ts.map +1 -0
  20. package/dist/types/src/graph/node-defs.d.ts +6 -0
  21. package/dist/types/src/graph/node-defs.d.ts.map +1 -0
  22. package/dist/types/src/hooks/compute-context.d.ts +7 -0
  23. package/dist/types/src/hooks/compute-context.d.ts.map +1 -0
  24. package/dist/types/src/hooks/index.d.ts +4 -0
  25. package/dist/types/src/hooks/index.d.ts.map +1 -0
  26. package/dist/types/src/hooks/useComputeNodeState.d.ts +19 -0
  27. package/dist/types/src/hooks/useComputeNodeState.d.ts.map +1 -0
  28. package/dist/types/src/hooks/useGraphMonitor.d.ts +14 -0
  29. package/dist/types/src/hooks/useGraphMonitor.d.ts.map +1 -0
  30. package/dist/types/src/index.d.ts +6 -0
  31. package/dist/types/src/index.d.ts.map +1 -0
  32. package/dist/types/src/json.test.d.ts +21 -0
  33. package/dist/types/src/json.test.d.ts.map +1 -0
  34. package/dist/types/src/registry.d.ts +9 -0
  35. package/dist/types/src/registry.d.ts.map +1 -0
  36. package/dist/types/src/schema.test.d.ts +2 -0
  37. package/dist/types/src/schema.test.d.ts.map +1 -0
  38. package/dist/types/src/shapes/Append.d.ts +54 -0
  39. package/dist/types/src/shapes/Append.d.ts.map +1 -0
  40. package/dist/types/src/shapes/Array.d.ts +38 -0
  41. package/dist/types/src/shapes/Array.d.ts.map +1 -0
  42. package/dist/types/src/shapes/Audio.d.ts +54 -0
  43. package/dist/types/src/shapes/Audio.d.ts.map +1 -0
  44. package/dist/types/src/shapes/Beacon.d.ts +54 -0
  45. package/dist/types/src/shapes/Beacon.d.ts.map +1 -0
  46. package/dist/types/src/shapes/Boolean.d.ts +233 -0
  47. package/dist/types/src/shapes/Boolean.d.ts.map +1 -0
  48. package/dist/types/src/shapes/Chat.d.ts +57 -0
  49. package/dist/types/src/shapes/Chat.d.ts.map +1 -0
  50. package/dist/types/src/shapes/Constant.d.ts +60 -0
  51. package/dist/types/src/shapes/Constant.d.ts.map +1 -0
  52. package/dist/types/src/shapes/Database.d.ts +54 -0
  53. package/dist/types/src/shapes/Database.d.ts.map +1 -0
  54. package/dist/types/src/shapes/Function.d.ts +54 -0
  55. package/dist/types/src/shapes/Function.d.ts.map +1 -0
  56. package/dist/types/src/shapes/Gpt.d.ts +54 -0
  57. package/dist/types/src/shapes/Gpt.d.ts.map +1 -0
  58. package/dist/types/src/shapes/GptRealtime.d.ts +54 -0
  59. package/dist/types/src/shapes/GptRealtime.d.ts.map +1 -0
  60. package/dist/types/src/shapes/Json.d.ts +107 -0
  61. package/dist/types/src/shapes/Json.d.ts.map +1 -0
  62. package/dist/types/src/shapes/Logic.d.ts +109 -0
  63. package/dist/types/src/shapes/Logic.d.ts.map +1 -0
  64. package/dist/types/src/shapes/Queue.d.ts +58 -0
  65. package/dist/types/src/shapes/Queue.d.ts.map +1 -0
  66. package/dist/types/src/shapes/RNG.d.ts +58 -0
  67. package/dist/types/src/shapes/RNG.d.ts.map +1 -0
  68. package/dist/types/src/shapes/Scope.d.ts +54 -0
  69. package/dist/types/src/shapes/Scope.d.ts.map +1 -0
  70. package/dist/types/src/shapes/Surface.d.ts +54 -0
  71. package/dist/types/src/shapes/Surface.d.ts.map +1 -0
  72. package/dist/types/src/shapes/Switch.d.ts +54 -0
  73. package/dist/types/src/shapes/Switch.d.ts.map +1 -0
  74. package/dist/types/src/shapes/Table.d.ts +54 -0
  75. package/dist/types/src/shapes/Table.d.ts.map +1 -0
  76. package/dist/types/src/shapes/Template.d.ts +56 -0
  77. package/dist/types/src/shapes/Template.d.ts.map +1 -0
  78. package/dist/types/src/shapes/Text.d.ts +54 -0
  79. package/dist/types/src/shapes/Text.d.ts.map +1 -0
  80. package/dist/types/src/shapes/TextToImage.d.ts +54 -0
  81. package/dist/types/src/shapes/TextToImage.d.ts.map +1 -0
  82. package/dist/types/src/shapes/Thread.d.ts +58 -0
  83. package/dist/types/src/shapes/Thread.d.ts.map +1 -0
  84. package/dist/types/src/shapes/Trigger.d.ts +64 -0
  85. package/dist/types/src/shapes/Trigger.d.ts.map +1 -0
  86. package/dist/types/src/shapes/common/Box.d.ts +25 -0
  87. package/dist/types/src/shapes/common/Box.d.ts.map +1 -0
  88. package/dist/types/src/shapes/common/FunctionBody.d.ts +15 -0
  89. package/dist/types/src/shapes/common/FunctionBody.d.ts.map +1 -0
  90. package/dist/types/src/shapes/common/TypeSelect.d.ts +4 -0
  91. package/dist/types/src/shapes/common/TypeSelect.d.ts.map +1 -0
  92. package/dist/types/src/shapes/common/index.d.ts +4 -0
  93. package/dist/types/src/shapes/common/index.d.ts.map +1 -0
  94. package/dist/types/src/shapes/defs.d.ts +39 -0
  95. package/dist/types/src/shapes/defs.d.ts.map +1 -0
  96. package/dist/types/src/shapes/index.d.ts +27 -0
  97. package/dist/types/src/shapes/index.d.ts.map +1 -0
  98. package/dist/types/src/testing/circuits.d.ts +193 -0
  99. package/dist/types/src/testing/circuits.d.ts.map +1 -0
  100. package/dist/types/src/testing/index.d.ts +2 -0
  101. package/dist/types/src/testing/index.d.ts.map +1 -0
  102. package/dist/types/tsconfig.tsbuildinfo +1 -0
  103. package/package.json +85 -0
  104. package/src/README.md +47 -0
  105. package/src/compute-layout.ts +37 -0
  106. package/src/compute.stories.tsx +362 -0
  107. package/src/graph/controller.ts +405 -0
  108. package/src/graph/index.ts +6 -0
  109. package/src/graph/node-defs.ts +82 -0
  110. package/src/hooks/compute-context.ts +19 -0
  111. package/src/hooks/index.ts +7 -0
  112. package/src/hooks/useComputeNodeState.ts +83 -0
  113. package/src/hooks/useGraphMonitor.ts +133 -0
  114. package/src/index.ts +9 -0
  115. package/src/json.test.ts +35 -0
  116. package/src/registry.ts +100 -0
  117. package/src/schema.test.ts +62 -0
  118. package/src/shapes/Append.tsx +43 -0
  119. package/src/shapes/Array.tsx +61 -0
  120. package/src/shapes/Audio.tsx +55 -0
  121. package/src/shapes/Beacon.tsx +56 -0
  122. package/src/shapes/Boolean.tsx +215 -0
  123. package/src/shapes/Chat.tsx +77 -0
  124. package/src/shapes/Constant.tsx +125 -0
  125. package/src/shapes/Database.tsx +39 -0
  126. package/src/shapes/Function.tsx +40 -0
  127. package/src/shapes/Gpt.tsx +91 -0
  128. package/src/shapes/GptRealtime.tsx +168 -0
  129. package/src/shapes/Json.tsx +103 -0
  130. package/src/shapes/Logic.tsx +82 -0
  131. package/src/shapes/Queue.tsx +78 -0
  132. package/src/shapes/RNG.tsx +84 -0
  133. package/src/shapes/Scope.tsx +54 -0
  134. package/src/shapes/Surface.tsx +57 -0
  135. package/src/shapes/Switch.tsx +53 -0
  136. package/src/shapes/Table.tsx +45 -0
  137. package/src/shapes/Template.tsx +98 -0
  138. package/src/shapes/Text.tsx +56 -0
  139. package/src/shapes/TextToImage.tsx +39 -0
  140. package/src/shapes/Thread.tsx +87 -0
  141. package/src/shapes/Trigger.tsx +152 -0
  142. package/src/shapes/common/Box.tsx +74 -0
  143. package/src/shapes/common/FunctionBody.tsx +122 -0
  144. package/src/shapes/common/TypeSelect.tsx +27 -0
  145. package/src/shapes/common/index.ts +7 -0
  146. package/src/shapes/defs.ts +50 -0
  147. package/src/shapes/index.ts +31 -0
  148. package/src/testing/circuits.ts +320 -0
  149. package/src/testing/index.ts +5 -0
@@ -0,0 +1,45 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { createInputSchema, createOutputSchema, GptMessage } from '@dxos/conductor';
8
+ import { S } from '@dxos/echo-schema';
9
+ import { type ShapeComponentProps, type ShapeDef } from '@dxos/react-ui-canvas-editor';
10
+
11
+ import { createFunctionAnchors, Box } from './common';
12
+ import { ComputeShape, createShape, type CreateShapeProps } from './defs';
13
+
14
+ const InputSchema = createInputSchema(GptMessage);
15
+ const OutputSchema = createOutputSchema(S.mutable(S.Array(GptMessage)));
16
+
17
+ export const TableShape = S.extend(
18
+ ComputeShape,
19
+ S.Struct({
20
+ type: S.Literal('table'),
21
+ }),
22
+ );
23
+
24
+ export type TableShape = S.Schema.Type<typeof TableShape>;
25
+
26
+ export type CreateTableProps = CreateShapeProps<TableShape>;
27
+
28
+ export const createTable = (props: CreateTableProps) =>
29
+ createShape<TableShape>({ type: 'table', size: { width: 320, height: 512 }, ...props });
30
+
31
+ export const TableComponent = ({ shape }: ShapeComponentProps<TableShape>) => {
32
+ // const items = shape.node.items.value;
33
+
34
+ return <Box shape={shape}></Box>;
35
+ };
36
+
37
+ export const tableShape: ShapeDef<TableShape> = {
38
+ type: 'table',
39
+ name: 'Table',
40
+ icon: 'ph--table--regular',
41
+ component: TableComponent,
42
+ createShape: createTable,
43
+ getAnchors: (shape) => createFunctionAnchors(shape, InputSchema, OutputSchema),
44
+ resizable: true,
45
+ };
@@ -0,0 +1,98 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, { useRef } from 'react';
6
+
7
+ import { ComputeValueType, getTemplateInputSchema, TemplateOutput, VoidInput } from '@dxos/conductor';
8
+ import { S, toJsonSchema } from '@dxos/echo-schema';
9
+ import { invariant } from '@dxos/invariant';
10
+ import {
11
+ type ShapeComponentProps,
12
+ type ShapeDef,
13
+ TextBox,
14
+ type TextBoxControl,
15
+ type TextBoxProps,
16
+ } from '@dxos/react-ui-canvas-editor';
17
+
18
+ import { Box, createFunctionAnchors, TypeSelect } from './common';
19
+ import { ComputeShape, createShape, type CreateShapeProps } from './defs';
20
+ import { useComputeNodeState } from '../hooks';
21
+
22
+ //
23
+ // Data
24
+ //
25
+
26
+ export const TemplateShape = S.extend(
27
+ ComputeShape,
28
+ S.Struct({
29
+ type: S.Literal('template'),
30
+ valueType: S.optional(ComputeValueType),
31
+ }),
32
+ );
33
+
34
+ export type TemplateShape = S.Schema.Type<typeof TemplateShape>;
35
+
36
+ //
37
+ // Component
38
+ //
39
+
40
+ type TextInputComponentProps = ShapeComponentProps<TemplateShape> & TextBoxProps & { title?: string };
41
+
42
+ const TextInputComponent = ({ shape, title, ...props }: TextInputComponentProps) => {
43
+ const { node } = useComputeNodeState(shape);
44
+ const inputRef = useRef<TextBoxControl>(null);
45
+
46
+ const handleEnter: TextBoxProps['onEnter'] = (text) => {
47
+ const value = text.trim();
48
+ if (value.length) {
49
+ const schema = getTemplateInputSchema(node);
50
+
51
+ node.value = value;
52
+ node.inputSchema = toJsonSchema(schema);
53
+ }
54
+ };
55
+
56
+ const handleTypeChange = (newType: string) => {
57
+ invariant(S.is(ComputeValueType)(newType), 'Invalid type');
58
+
59
+ node.valueType = newType;
60
+ node.inputSchema = toJsonSchema(getTemplateInputSchema(node));
61
+ };
62
+
63
+ return (
64
+ <Box
65
+ shape={shape}
66
+ title={'Template'}
67
+ status={<TypeSelect value={node.valueType ?? 'string'} onValueChange={handleTypeChange} />}
68
+ >
69
+ <TextBox
70
+ {...props}
71
+ ref={inputRef}
72
+ value={node.value}
73
+ language={node.valueType === 'object' ? 'json' : undefined}
74
+ onBlur={handleEnter}
75
+ onEnter={handleEnter}
76
+ />
77
+ </Box>
78
+ );
79
+ };
80
+
81
+ //
82
+ // Defs
83
+ //
84
+
85
+ export type CreateTemplateProps = CreateShapeProps<TemplateShape> & { text?: string };
86
+
87
+ export const createTemplate = (props: CreateTemplateProps) =>
88
+ createShape<TemplateShape>({ type: 'template', size: { width: 256, height: 384 }, ...props });
89
+
90
+ export const templateShape: ShapeDef<TemplateShape> = {
91
+ type: 'template',
92
+ name: 'Template',
93
+ icon: 'ph--article--regular',
94
+ component: (props) => <TextInputComponent {...props} placeholder={'Prompt'} />,
95
+ createShape: createTemplate,
96
+ getAnchors: (shape) => createFunctionAnchors(shape, VoidInput, TemplateOutput),
97
+ resizable: true,
98
+ };
@@ -0,0 +1,56 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { DEFAULT_INPUT } from '@dxos/conductor';
8
+ import { S } from '@dxos/echo-schema';
9
+ import { TextBox, type ShapeComponentProps, type ShapeDef } from '@dxos/react-ui-canvas-editor';
10
+ import { createAnchorMap } from '@dxos/react-ui-canvas-editor';
11
+
12
+ import { Box, type BoxActionHandler } from './common';
13
+ import { ComputeShape, createAnchorId, createShape, type CreateShapeProps } from './defs';
14
+ import { useComputeNodeState } from '../hooks';
15
+
16
+ export const TextShape = S.extend(
17
+ ComputeShape,
18
+ S.Struct({
19
+ type: S.Literal('text'),
20
+ }),
21
+ );
22
+
23
+ export type TextShape = S.Schema.Type<typeof TextShape>;
24
+
25
+ export type CreateTextProps = CreateShapeProps<TextShape>;
26
+
27
+ export const createText = (props: CreateTextProps) =>
28
+ createShape<TextShape>({ type: 'text', size: { width: 384, height: 384 }, ...props });
29
+
30
+ export const TextComponent = ({ shape }: ShapeComponentProps<TextShape>) => {
31
+ const { runtime } = useComputeNodeState(shape);
32
+ const input = runtime.inputs[DEFAULT_INPUT];
33
+ const value = input?.type === 'executed' ? input.value : 0;
34
+
35
+ const handleAction: BoxActionHandler = (action) => {
36
+ if (action === 'run') {
37
+ runtime.evalNode();
38
+ }
39
+ };
40
+
41
+ return (
42
+ <Box shape={shape} onAction={handleAction}>
43
+ <TextBox value={value} />
44
+ </Box>
45
+ );
46
+ };
47
+
48
+ export const textShape: ShapeDef<TextShape> = {
49
+ type: 'text',
50
+ name: 'Text',
51
+ icon: 'ph--article--regular',
52
+ component: TextComponent,
53
+ createShape: createText,
54
+ getAnchors: (shape) => createAnchorMap(shape, { [createAnchorId('input')]: { x: -1, y: 0 } }),
55
+ resizable: true,
56
+ };
@@ -0,0 +1,39 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { S } from '@dxos/echo-schema';
8
+ import { type ShapeComponentProps, type ShapeDef } from '@dxos/react-ui-canvas-editor';
9
+ import { createAnchorMap } from '@dxos/react-ui-canvas-editor';
10
+
11
+ import { Box } from './common';
12
+ import { ComputeShape, createAnchorId, createShape, type CreateShapeProps } from './defs';
13
+
14
+ export const TextToImageShape = S.extend(
15
+ ComputeShape,
16
+ S.Struct({
17
+ type: S.Literal('text-to-image'),
18
+ }),
19
+ );
20
+
21
+ export type TextToImageShape = S.Schema.Type<typeof TextToImageShape>;
22
+
23
+ export type CreateTextToImageProps = CreateShapeProps<TextToImageShape>;
24
+
25
+ export const createTextToImage = (props: CreateTextToImageProps) =>
26
+ createShape<TextToImageShape>({ type: 'text-to-image', size: { width: 128, height: 64 }, ...props });
27
+
28
+ export const TextToImageComponent = ({ shape }: ShapeComponentProps<TextToImageShape>) => {
29
+ return <Box shape={shape} />;
30
+ };
31
+
32
+ export const textToImageShape: ShapeDef<TextToImageShape> = {
33
+ type: 'text-to-image',
34
+ name: 'Image',
35
+ icon: 'ph--image--regular',
36
+ component: TextToImageComponent,
37
+ createShape: createTextToImage,
38
+ getAnchors: (shape) => createAnchorMap(shape, { [createAnchorId('output')]: { x: 1, y: 0 } }),
39
+ };
@@ -0,0 +1,87 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, { useEffect, useRef } from 'react';
6
+
7
+ import { createInputSchema, createOutputSchema, GptMessage } from '@dxos/conductor';
8
+ import { S } from '@dxos/echo-schema';
9
+ import { type ThemedClassName } from '@dxos/react-ui';
10
+ import { type ShapeComponentProps, type ShapeDef } from '@dxos/react-ui-canvas-editor';
11
+ import { mx } from '@dxos/react-ui-theme';
12
+
13
+ import { createFunctionAnchors, Box } from './common';
14
+ import { ComputeShape, createShape, type CreateShapeProps } from './defs';
15
+
16
+ const InputSchema = createInputSchema(GptMessage);
17
+ const OutputSchema = createOutputSchema(S.mutable(S.Array(GptMessage)));
18
+
19
+ export const ThreadShape = S.extend(
20
+ ComputeShape,
21
+ S.Struct({
22
+ type: S.Literal('thread'),
23
+ }),
24
+ );
25
+
26
+ export type ThreadShape = S.Schema.Type<typeof ThreadShape>;
27
+
28
+ export type CreateThreadProps = CreateShapeProps<ThreadShape>;
29
+
30
+ export const createThread = (props: CreateThreadProps) =>
31
+ createShape<ThreadShape>({ type: 'thread', size: { width: 384, height: 384 }, ...props });
32
+
33
+ export const ThreadComponent = ({ shape }: ShapeComponentProps<ThreadShape>) => {
34
+ const items: any[] = [];
35
+ const scrollRef = useRef<HTMLDivElement>(null);
36
+ useEffect(() => {
37
+ if (scrollRef.current) {
38
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
39
+ }
40
+ }, [items]);
41
+
42
+ return (
43
+ <Box shape={shape}>
44
+ <div ref={scrollRef} className='flex flex-col w-full overflow-y-scroll gap-2 p-2'>
45
+ {[...items].map((item, i) => (
46
+ <ThreadItem key={i} item={item} />
47
+ ))}
48
+ </div>
49
+ </Box>
50
+ );
51
+ };
52
+
53
+ export const ThreadItem = ({ classNames, item }: ThemedClassName<{ item: any }>) => {
54
+ if (typeof item !== 'object') {
55
+ return <div className={mx(classNames)}>{item}</div>;
56
+ }
57
+
58
+ // TODO(burdon): Hack; introspect type.
59
+ // TODO(burdon): Markdown parser.
60
+ const { role, message } = item;
61
+ return (
62
+ <div className={mx('flex', classNames, role === 'user' && 'justify-end')}>
63
+ <div
64
+ className={mx(
65
+ 'block rounded-md p-1 px-2 text-sm',
66
+ role === 'user'
67
+ ? 'bg-blue-100 dark:bg-blue-800'
68
+ : role === 'system'
69
+ ? 'bg-red-100, dark:bg-red-800'
70
+ : 'whitespace-pre-wrap bg-neutral-50 dark:bg-neutral-800',
71
+ )}
72
+ >
73
+ {message}
74
+ </div>
75
+ </div>
76
+ );
77
+ };
78
+
79
+ export const threadShape: ShapeDef<ThreadShape> = {
80
+ type: 'thread',
81
+ name: 'Thread',
82
+ icon: 'ph--chats-circle--regular',
83
+ component: ThreadComponent,
84
+ createShape: createThread,
85
+ getAnchors: (shape) => createFunctionAnchors(shape, InputSchema, OutputSchema),
86
+ resizable: true,
87
+ };
@@ -0,0 +1,152 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, { useEffect } from 'react';
6
+
7
+ import {
8
+ EmailTriggerOutput,
9
+ SubscriptionTriggerOutput,
10
+ TimerTriggerOutput,
11
+ VoidInput,
12
+ WebhookTriggerOutput,
13
+ } from '@dxos/conductor';
14
+ import { Ref, S } from '@dxos/echo-schema';
15
+ import {
16
+ type EmailTrigger,
17
+ FunctionTrigger,
18
+ type SubscriptionTrigger,
19
+ type TimerTrigger,
20
+ TriggerKind,
21
+ type TriggerType,
22
+ type WebhookTrigger,
23
+ } from '@dxos/functions';
24
+ import { create, makeRef } from '@dxos/react-client/echo';
25
+ import { Select, type SelectRootProps } from '@dxos/react-ui';
26
+ import { type ShapeComponentProps, type ShapeDef } from '@dxos/react-ui-canvas-editor';
27
+
28
+ import { createFunctionAnchors, FunctionBody, getHeight } from './common';
29
+ import { ComputeShape, createShape, type CreateShapeProps } from './defs';
30
+
31
+ export const TriggerShape = S.extend(
32
+ ComputeShape,
33
+ S.Struct({
34
+ type: S.Literal('trigger'),
35
+ functionTrigger: S.optional(Ref(FunctionTrigger)),
36
+ }),
37
+ );
38
+ export type TriggerShape = S.Schema.Type<typeof TriggerShape>;
39
+
40
+ export type CreateTriggerProps = CreateShapeProps<Omit<TriggerShape, 'functionTrigger'>> & {
41
+ triggerKind?: TriggerKind;
42
+ };
43
+
44
+ export const createTrigger = (props: CreateTriggerProps): TriggerShape => {
45
+ const functionTrigger = create(FunctionTrigger, {
46
+ enabled: true,
47
+ spec: createTriggerSpec(props.triggerKind ?? TriggerKind.Email),
48
+ });
49
+ return createShape<TriggerShape>({
50
+ type: 'trigger',
51
+ functionTrigger: makeRef(functionTrigger),
52
+ size: { width: 192, height: getHeight(EmailTriggerOutput) },
53
+ ...props,
54
+ });
55
+ };
56
+
57
+ export type TriggerComponentProps = ShapeComponentProps<TriggerShape>;
58
+
59
+ export const TriggerComponent = ({ shape }: TriggerComponentProps) => {
60
+ const functionTrigger = shape.functionTrigger?.target;
61
+
62
+ useEffect(() => {
63
+ if (functionTrigger && !functionTrigger.spec) {
64
+ functionTrigger.spec = createTriggerSpec(TriggerKind.Email);
65
+ }
66
+ }, [functionTrigger, functionTrigger?.spec]);
67
+
68
+ useEffect(() => {
69
+ shape.size.height = getHeight(getOutputSchema(functionTrigger?.spec?.type ?? TriggerKind.Email));
70
+ }, [functionTrigger?.spec?.type]);
71
+
72
+ const setKind = (kind: TriggerKind) => {
73
+ if (functionTrigger?.spec?.type !== kind) {
74
+ functionTrigger!.spec = createTriggerSpec(kind);
75
+ }
76
+ };
77
+
78
+ if (!functionTrigger?.spec) {
79
+ return;
80
+ }
81
+
82
+ return (
83
+ <FunctionBody
84
+ shape={shape}
85
+ status={
86
+ <TriggerKindSelect value={functionTrigger.spec?.type} onValueChange={(kind) => setKind(kind as TriggerKind)} />
87
+ }
88
+ inputSchema={VoidInput}
89
+ outputSchema={getOutputSchema(functionTrigger.spec!.type!)}
90
+ />
91
+ );
92
+ };
93
+
94
+ // TODO(burdon): Factor out.
95
+ const TriggerKindSelect = ({ value, onValueChange }: Pick<SelectRootProps, 'value' | 'onValueChange'>) => {
96
+ return (
97
+ <Select.Root value={value} onValueChange={onValueChange}>
98
+ <Select.TriggerButton variant='ghost' classNames='w-full !px-0' />
99
+ <Select.Portal>
100
+ <Select.Content>
101
+ <Select.ScrollUpButton />
102
+ <Select.Viewport>
103
+ {Object.values(TriggerKind).map((type) => (
104
+ <Select.Option key={type} value={type}>
105
+ {type}
106
+ </Select.Option>
107
+ ))}
108
+ </Select.Viewport>
109
+ <Select.ScrollDownButton />
110
+ <Select.Arrow />
111
+ </Select.Content>
112
+ </Select.Portal>
113
+ </Select.Root>
114
+ );
115
+ };
116
+
117
+ const createTriggerSpec = (kind: TriggerKind): TriggerType => {
118
+ switch (kind) {
119
+ case TriggerKind.Timer:
120
+ return { type: TriggerKind.Timer, cron: '0 0 * * *' } satisfies TimerTrigger;
121
+ case TriggerKind.Webhook:
122
+ return { type: TriggerKind.Webhook, method: 'POST' } satisfies WebhookTrigger;
123
+ case TriggerKind.Subscription:
124
+ return { type: TriggerKind.Subscription, filter: {} } satisfies SubscriptionTrigger;
125
+ case TriggerKind.Email:
126
+ return { type: TriggerKind.Email } satisfies EmailTrigger;
127
+ }
128
+ };
129
+
130
+ const getOutputSchema = (kind: TriggerKind) => {
131
+ const kindToSchema: Record<TriggerKind, S.Schema<any>> = {
132
+ [TriggerKind.Email]: EmailTriggerOutput,
133
+ [TriggerKind.Subscription]: SubscriptionTriggerOutput,
134
+ [TriggerKind.Timer]: TimerTriggerOutput,
135
+ [TriggerKind.Webhook]: WebhookTriggerOutput,
136
+ };
137
+ return kindToSchema[kind];
138
+ };
139
+
140
+ export const triggerShape: ShapeDef<TriggerShape> = {
141
+ type: 'trigger',
142
+ name: 'Trigger',
143
+ icon: 'ph--lightning--regular',
144
+ component: TriggerComponent,
145
+ createShape: createTrigger,
146
+ getAnchors: (shape) =>
147
+ createFunctionAnchors(
148
+ shape,
149
+ VoidInput,
150
+ getOutputSchema(shape.functionTrigger?.target?.spec?.type ?? TriggerKind.Email),
151
+ ),
152
+ };
@@ -0,0 +1,74 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { forwardRef, type PropsWithChildren, type ReactNode } from 'react';
6
+
7
+ import { invariant } from '@dxos/invariant';
8
+ import { Icon, IconButton, type ThemedClassName } from '@dxos/react-ui';
9
+ import { useEditorContext, useShapeDef } from '@dxos/react-ui-canvas-editor';
10
+ import { type Shape } from '@dxos/react-ui-canvas-editor';
11
+ import { mx } from '@dxos/react-ui-theme';
12
+
13
+ export const headerHeight = 32;
14
+ export const footerHeight = 32;
15
+
16
+ export type BoxActionHandler = (action: 'run' | 'open' | 'close') => void;
17
+
18
+ export type BoxProps = PropsWithChildren<
19
+ ThemedClassName<{
20
+ shape: Shape;
21
+ title?: string;
22
+ status?: string | ReactNode;
23
+ open?: boolean;
24
+ onAction?: BoxActionHandler;
25
+ }>
26
+ >;
27
+
28
+ export const Box = forwardRef<HTMLDivElement, BoxProps>(
29
+ ({ children, classNames, shape, title, status, open, onAction }, forwardedRef) => {
30
+ invariant(shape.type);
31
+ const { icon, name, openable } = useShapeDef(shape.type) ?? { icon: 'ph--placeholder--regular' };
32
+ const { debug } = useEditorContext();
33
+
34
+ return (
35
+ <div ref={forwardedRef} className='flex flex-col h-full w-full justify-between'>
36
+ <div className='flex shrink-0 w-full justify-between items-center h-[32px] bg-hoverSurface'>
37
+ <Icon icon={icon} classNames='mx-2' />
38
+ <div className='grow text-sm truncate'>{debug ? shape.type : name ?? shape.text ?? title}</div>
39
+ <IconButton
40
+ classNames='p-1 text-green-500'
41
+ variant='ghost'
42
+ icon='ph--play--regular'
43
+ size={4}
44
+ label='run'
45
+ iconOnly
46
+ onDoubleClick={(ev) => ev.stopPropagation()}
47
+ onClick={(ev) => {
48
+ ev.stopPropagation();
49
+ onAction?.('run');
50
+ }}
51
+ />
52
+ </div>
53
+ <div className={mx('flex flex-col h-full grow overflow-hidden', classNames)}>{children}</div>
54
+ <div className='flex shrink-0 w-full justify-between items-center h-[32px] bg-hoverSurface'>
55
+ <div className='grow px-2 text-sm truncate'>{debug ? shape.id : status}</div>
56
+ {openable && (
57
+ <IconButton
58
+ classNames='p-1'
59
+ variant='ghost'
60
+ icon={open ? 'ph--caret-up--regular' : 'ph--caret-down--regular'}
61
+ size={4}
62
+ label={open ? 'close' : 'open'}
63
+ iconOnly
64
+ onClick={(ev) => {
65
+ ev.stopPropagation();
66
+ onAction?.(open ? 'close' : 'open');
67
+ }}
68
+ />
69
+ )}
70
+ </div>
71
+ </div>
72
+ );
73
+ },
74
+ );
@@ -0,0 +1,122 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, { type JSX, useRef, useState } from 'react';
6
+
7
+ import { VoidInput, VoidOutput } from '@dxos/conductor';
8
+ import { AST, type S } from '@dxos/echo-schema';
9
+ import { useCanvasContext } from '@dxos/react-ui-canvas';
10
+ import { type Polygon, type Shape } from '@dxos/react-ui-canvas-editor';
11
+ import { getParentShapeElement, createAnchors, rowHeight } from '@dxos/react-ui-canvas-editor';
12
+
13
+ import { Box, type BoxProps, footerHeight, headerHeight } from '../common';
14
+ import { createAnchorId, getProperties } from '../defs';
15
+
16
+ const bodyPadding = 8;
17
+ const expandedHeight = 200;
18
+
19
+ export type FunctionBodyProps = {
20
+ shape: Shape;
21
+ name?: string;
22
+ content?: JSX.Element;
23
+ inputSchema?: S.Schema.Any;
24
+ outputSchema?: S.Schema.Any;
25
+ } & Pick<BoxProps, 'status'>;
26
+
27
+ export const FunctionBody = ({
28
+ shape,
29
+ name,
30
+ content,
31
+ inputSchema = VoidInput,
32
+ outputSchema = VoidOutput,
33
+ ...props
34
+ }: FunctionBodyProps) => {
35
+ const { scale } = useCanvasContext();
36
+ const rootRef = useRef<HTMLDivElement>(null);
37
+ const [open, setOpen] = useState(false);
38
+
39
+ const handleAction: BoxProps['onAction'] = (action) => {
40
+ if (!rootRef.current) {
41
+ return;
42
+ }
43
+
44
+ switch (action) {
45
+ case 'open': {
46
+ const el = getParentShapeElement(rootRef.current, shape.id)!;
47
+ const { height } = el.getBoundingClientRect();
48
+ el.style.height = `${height / scale + expandedHeight}px`;
49
+ setOpen(true);
50
+ break;
51
+ }
52
+ case 'close': {
53
+ const el = getParentShapeElement(rootRef.current, shape.id)!;
54
+ el.style.height = '';
55
+ setOpen(false);
56
+ break;
57
+ }
58
+ }
59
+ };
60
+
61
+ // TODO(burdon): Move labels to anchor?
62
+ const inputs = getProperties(inputSchema.ast);
63
+ const outputs = getProperties(outputSchema.ast);
64
+ const columnCount = inputs.length && outputs.length ? 2 : 1;
65
+
66
+ return (
67
+ <Box
68
+ ref={rootRef}
69
+ shape={shape}
70
+ title={name}
71
+ classNames='divide-y divide-separator'
72
+ open={open}
73
+ onAction={handleAction}
74
+ {...props}
75
+ >
76
+ <div
77
+ className={`grid grid-cols-${columnCount} items-center`}
78
+ style={{ paddingTop: bodyPadding, paddingBottom: bodyPadding }}
79
+ >
80
+ {(inputs?.length ?? 0) > 0 && (
81
+ <div className='flex flex-col'>
82
+ {inputs?.map(({ name }) => (
83
+ <div key={name} className='px-2 truncate text-sm font-mono items-center' style={{ height: rowHeight }}>
84
+ {name}
85
+ </div>
86
+ ))}
87
+ </div>
88
+ )}
89
+ {(outputs?.length ?? 0) > 0 && (
90
+ <div className='flex flex-col'>
91
+ {outputs?.map(({ name }) => (
92
+ <div
93
+ key={name}
94
+ className='px-2 truncate text-sm font-mono items-center text-right'
95
+ style={{ height: rowHeight }}
96
+ >
97
+ {name}
98
+ </div>
99
+ ))}
100
+ </div>
101
+ )}
102
+ </div>
103
+ {open && <div className='flex flex-col grow overflow-hidden'>{content}</div>}
104
+ </Box>
105
+ );
106
+ };
107
+
108
+ export const getHeight = (input: S.Schema<any>) => {
109
+ const properties = AST.getPropertySignatures(input.ast);
110
+ return headerHeight + footerHeight + bodyPadding * 2 + properties.length * rowHeight + 2; // Incl. borders.
111
+ };
112
+
113
+ export const createFunctionAnchors = (
114
+ shape: Polygon,
115
+ input: S.Schema<any> = VoidInput,
116
+ output: S.Schema<any> = VoidOutput,
117
+ ) => {
118
+ // TODO(burdon): Set type.
119
+ const inputs = AST.getPropertySignatures(input.ast).map(({ name }) => createAnchorId('input', name.toString()));
120
+ const outputs = AST.getPropertySignatures(output.ast).map(({ name }) => createAnchorId('output', name.toString()));
121
+ return createAnchors({ shape, inputs, outputs, center: { x: 0, y: (headerHeight - footerHeight) / 2 + 1 } });
122
+ };