@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.
- package/LICENSE +8 -0
- package/README.md +1 -0
- package/dist/lib/browser/index.mjs +2499 -0
- package/dist/lib/browser/index.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -0
- package/dist/lib/node/index.cjs +2591 -0
- package/dist/lib/node/index.cjs.map +7 -0
- package/dist/lib/node/meta.json +1 -0
- package/dist/lib/node-esm/index.mjs +2499 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/types/src/compute-layout.d.ts +9 -0
- package/dist/types/src/compute-layout.d.ts.map +1 -0
- package/dist/types/src/compute.stories.d.ts +28 -0
- package/dist/types/src/compute.stories.d.ts.map +1 -0
- package/dist/types/src/graph/controller.d.ts +139 -0
- package/dist/types/src/graph/controller.d.ts.map +1 -0
- package/dist/types/src/graph/index.d.ts +3 -0
- package/dist/types/src/graph/index.d.ts.map +1 -0
- package/dist/types/src/graph/node-defs.d.ts +6 -0
- package/dist/types/src/graph/node-defs.d.ts.map +1 -0
- package/dist/types/src/hooks/compute-context.d.ts +7 -0
- package/dist/types/src/hooks/compute-context.d.ts.map +1 -0
- package/dist/types/src/hooks/index.d.ts +4 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -0
- package/dist/types/src/hooks/useComputeNodeState.d.ts +19 -0
- package/dist/types/src/hooks/useComputeNodeState.d.ts.map +1 -0
- package/dist/types/src/hooks/useGraphMonitor.d.ts +14 -0
- package/dist/types/src/hooks/useGraphMonitor.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +6 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/json.test.d.ts +21 -0
- package/dist/types/src/json.test.d.ts.map +1 -0
- package/dist/types/src/registry.d.ts +9 -0
- package/dist/types/src/registry.d.ts.map +1 -0
- package/dist/types/src/schema.test.d.ts +2 -0
- package/dist/types/src/schema.test.d.ts.map +1 -0
- package/dist/types/src/shapes/Append.d.ts +54 -0
- package/dist/types/src/shapes/Append.d.ts.map +1 -0
- package/dist/types/src/shapes/Array.d.ts +38 -0
- package/dist/types/src/shapes/Array.d.ts.map +1 -0
- package/dist/types/src/shapes/Audio.d.ts +54 -0
- package/dist/types/src/shapes/Audio.d.ts.map +1 -0
- package/dist/types/src/shapes/Beacon.d.ts +54 -0
- package/dist/types/src/shapes/Beacon.d.ts.map +1 -0
- package/dist/types/src/shapes/Boolean.d.ts +233 -0
- package/dist/types/src/shapes/Boolean.d.ts.map +1 -0
- package/dist/types/src/shapes/Chat.d.ts +57 -0
- package/dist/types/src/shapes/Chat.d.ts.map +1 -0
- package/dist/types/src/shapes/Constant.d.ts +60 -0
- package/dist/types/src/shapes/Constant.d.ts.map +1 -0
- package/dist/types/src/shapes/Database.d.ts +54 -0
- package/dist/types/src/shapes/Database.d.ts.map +1 -0
- package/dist/types/src/shapes/Function.d.ts +54 -0
- package/dist/types/src/shapes/Function.d.ts.map +1 -0
- package/dist/types/src/shapes/Gpt.d.ts +54 -0
- package/dist/types/src/shapes/Gpt.d.ts.map +1 -0
- package/dist/types/src/shapes/GptRealtime.d.ts +54 -0
- package/dist/types/src/shapes/GptRealtime.d.ts.map +1 -0
- package/dist/types/src/shapes/Json.d.ts +107 -0
- package/dist/types/src/shapes/Json.d.ts.map +1 -0
- package/dist/types/src/shapes/Logic.d.ts +109 -0
- package/dist/types/src/shapes/Logic.d.ts.map +1 -0
- package/dist/types/src/shapes/Queue.d.ts +58 -0
- package/dist/types/src/shapes/Queue.d.ts.map +1 -0
- package/dist/types/src/shapes/RNG.d.ts +58 -0
- package/dist/types/src/shapes/RNG.d.ts.map +1 -0
- package/dist/types/src/shapes/Scope.d.ts +54 -0
- package/dist/types/src/shapes/Scope.d.ts.map +1 -0
- package/dist/types/src/shapes/Surface.d.ts +54 -0
- package/dist/types/src/shapes/Surface.d.ts.map +1 -0
- package/dist/types/src/shapes/Switch.d.ts +54 -0
- package/dist/types/src/shapes/Switch.d.ts.map +1 -0
- package/dist/types/src/shapes/Table.d.ts +54 -0
- package/dist/types/src/shapes/Table.d.ts.map +1 -0
- package/dist/types/src/shapes/Template.d.ts +56 -0
- package/dist/types/src/shapes/Template.d.ts.map +1 -0
- package/dist/types/src/shapes/Text.d.ts +54 -0
- package/dist/types/src/shapes/Text.d.ts.map +1 -0
- package/dist/types/src/shapes/TextToImage.d.ts +54 -0
- package/dist/types/src/shapes/TextToImage.d.ts.map +1 -0
- package/dist/types/src/shapes/Thread.d.ts +58 -0
- package/dist/types/src/shapes/Thread.d.ts.map +1 -0
- package/dist/types/src/shapes/Trigger.d.ts +64 -0
- package/dist/types/src/shapes/Trigger.d.ts.map +1 -0
- package/dist/types/src/shapes/common/Box.d.ts +25 -0
- package/dist/types/src/shapes/common/Box.d.ts.map +1 -0
- package/dist/types/src/shapes/common/FunctionBody.d.ts +15 -0
- package/dist/types/src/shapes/common/FunctionBody.d.ts.map +1 -0
- package/dist/types/src/shapes/common/TypeSelect.d.ts +4 -0
- package/dist/types/src/shapes/common/TypeSelect.d.ts.map +1 -0
- package/dist/types/src/shapes/common/index.d.ts +4 -0
- package/dist/types/src/shapes/common/index.d.ts.map +1 -0
- package/dist/types/src/shapes/defs.d.ts +39 -0
- package/dist/types/src/shapes/defs.d.ts.map +1 -0
- package/dist/types/src/shapes/index.d.ts +27 -0
- package/dist/types/src/shapes/index.d.ts.map +1 -0
- package/dist/types/src/testing/circuits.d.ts +193 -0
- package/dist/types/src/testing/circuits.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +85 -0
- package/src/README.md +47 -0
- package/src/compute-layout.ts +37 -0
- package/src/compute.stories.tsx +362 -0
- package/src/graph/controller.ts +405 -0
- package/src/graph/index.ts +6 -0
- package/src/graph/node-defs.ts +82 -0
- package/src/hooks/compute-context.ts +19 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/useComputeNodeState.ts +83 -0
- package/src/hooks/useGraphMonitor.ts +133 -0
- package/src/index.ts +9 -0
- package/src/json.test.ts +35 -0
- package/src/registry.ts +100 -0
- package/src/schema.test.ts +62 -0
- package/src/shapes/Append.tsx +43 -0
- package/src/shapes/Array.tsx +61 -0
- package/src/shapes/Audio.tsx +55 -0
- package/src/shapes/Beacon.tsx +56 -0
- package/src/shapes/Boolean.tsx +215 -0
- package/src/shapes/Chat.tsx +77 -0
- package/src/shapes/Constant.tsx +125 -0
- package/src/shapes/Database.tsx +39 -0
- package/src/shapes/Function.tsx +40 -0
- package/src/shapes/Gpt.tsx +91 -0
- package/src/shapes/GptRealtime.tsx +168 -0
- package/src/shapes/Json.tsx +103 -0
- package/src/shapes/Logic.tsx +82 -0
- package/src/shapes/Queue.tsx +78 -0
- package/src/shapes/RNG.tsx +84 -0
- package/src/shapes/Scope.tsx +54 -0
- package/src/shapes/Surface.tsx +57 -0
- package/src/shapes/Switch.tsx +53 -0
- package/src/shapes/Table.tsx +45 -0
- package/src/shapes/Template.tsx +98 -0
- package/src/shapes/Text.tsx +56 -0
- package/src/shapes/TextToImage.tsx +39 -0
- package/src/shapes/Thread.tsx +87 -0
- package/src/shapes/Trigger.tsx +152 -0
- package/src/shapes/common/Box.tsx +74 -0
- package/src/shapes/common/FunctionBody.tsx +122 -0
- package/src/shapes/common/TypeSelect.tsx +27 -0
- package/src/shapes/common/index.ts +7 -0
- package/src/shapes/defs.ts +50 -0
- package/src/shapes/index.ts +31 -0
- package/src/testing/circuits.ts +320 -0
- package/src/testing/index.ts +5 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Context, Effect, Either, Exit, Layer, Scope } from 'effect';
|
|
6
|
+
|
|
7
|
+
import { type ImageContentBlock } from '@dxos/artifact';
|
|
8
|
+
import { Event, synchronized } from '@dxos/async';
|
|
9
|
+
import {
|
|
10
|
+
isNotExecuted,
|
|
11
|
+
makeValueBag,
|
|
12
|
+
type ComputeEdge,
|
|
13
|
+
type ComputeEvent,
|
|
14
|
+
type ComputeGraphModel,
|
|
15
|
+
type ComputeMeta,
|
|
16
|
+
type ComputeNode,
|
|
17
|
+
type ComputeRequirements,
|
|
18
|
+
EventLogger,
|
|
19
|
+
type GptInput,
|
|
20
|
+
type GptOutput,
|
|
21
|
+
GptService,
|
|
22
|
+
GraphExecutor,
|
|
23
|
+
MockGpt,
|
|
24
|
+
QueueService,
|
|
25
|
+
SpaceService,
|
|
26
|
+
type ValueBag,
|
|
27
|
+
} from '@dxos/conductor';
|
|
28
|
+
import { Resource } from '@dxos/context';
|
|
29
|
+
import type { EdgeClient, EdgeHttpClient } from '@dxos/edge-client';
|
|
30
|
+
import { log } from '@dxos/log';
|
|
31
|
+
import { type CanvasGraphModel } from '@dxos/react-ui-canvas-editor';
|
|
32
|
+
|
|
33
|
+
import { resolveComputeNode } from './node-defs';
|
|
34
|
+
import { createComputeGraph } from '../hooks';
|
|
35
|
+
import { type ComputeShape } from '../shapes';
|
|
36
|
+
|
|
37
|
+
// TODO(burdon): API package for conductor.
|
|
38
|
+
export const InvalidStateError = Error;
|
|
39
|
+
|
|
40
|
+
export type FunctionCallback<INPUT, OUTPUT> = (input: INPUT) => Promise<OUTPUT>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Callback to notify the controller of a scheduled update.
|
|
44
|
+
*/
|
|
45
|
+
export type AsyncUpdate<T> = (value: T) => void;
|
|
46
|
+
|
|
47
|
+
export interface GptExecutor {
|
|
48
|
+
invoke: FunctionCallback<GptInput, GptOutput>;
|
|
49
|
+
|
|
50
|
+
// TODO(dmaretskyi): A hack to get image artifacts working. Rework into querying images from the ai-service store.
|
|
51
|
+
imageCache: Map<string, ImageContentBlock>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type RuntimeValue =
|
|
55
|
+
| {
|
|
56
|
+
type: 'not-executed'; // TODO(burdon): Different from pending?
|
|
57
|
+
}
|
|
58
|
+
| {
|
|
59
|
+
type: 'executed';
|
|
60
|
+
value: any;
|
|
61
|
+
}
|
|
62
|
+
| {
|
|
63
|
+
type: 'pending';
|
|
64
|
+
}
|
|
65
|
+
| {
|
|
66
|
+
type: 'error';
|
|
67
|
+
error: string;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type Services = {
|
|
71
|
+
gpt: Context.Tag.Service<GptService>;
|
|
72
|
+
edgeClient?: EdgeClient;
|
|
73
|
+
edgeHttpClient?: EdgeHttpClient;
|
|
74
|
+
spaceService?: Context.Tag.Service<SpaceService>;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
type ComputeOutputEvent = {
|
|
78
|
+
nodeId: string;
|
|
79
|
+
property: string;
|
|
80
|
+
value: RuntimeValue;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Nodes that will automatically trigger the execution of the graph on startup.
|
|
85
|
+
*/
|
|
86
|
+
const AUTO_TRIGGER_NODES = ['chat', 'switch', 'constant'];
|
|
87
|
+
|
|
88
|
+
export const createComputeGraphController = (graph: CanvasGraphModel<ComputeShape>, services?: Partial<Services>) => {
|
|
89
|
+
const computeGraph = createComputeGraph(graph);
|
|
90
|
+
const controller = new ComputeGraphController(computeGraph);
|
|
91
|
+
controller.setServices(services ?? {});
|
|
92
|
+
return { controller, graph };
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Controller that manages compute graph state, execution, and service coordination.
|
|
97
|
+
*/
|
|
98
|
+
export class ComputeGraphController extends Resource {
|
|
99
|
+
private readonly _executor = new GraphExecutor({
|
|
100
|
+
computeNodeResolver: (node) => resolveComputeNode(node),
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
private _services: Partial<Services> = {};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Canvas force-sets outputs of those nodes.
|
|
107
|
+
*/
|
|
108
|
+
private _forcedOutputs: Record<string, Record<string, unknown>> = {};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Runtime state of the execution graph.
|
|
112
|
+
*/
|
|
113
|
+
private _runtimeStateInputs: Record<string, Record<string, RuntimeValue>> = {};
|
|
114
|
+
|
|
115
|
+
private _runtimeStateOutputs: Record<string, Record<string, RuntimeValue>> = {};
|
|
116
|
+
|
|
117
|
+
// TODO(burdon): Remove? Make state reactive?
|
|
118
|
+
public readonly update = new Event();
|
|
119
|
+
|
|
120
|
+
/** Computed result. */
|
|
121
|
+
public readonly output = new Event<ComputeOutputEvent>();
|
|
122
|
+
|
|
123
|
+
public readonly events = new Event<ComputeEvent>();
|
|
124
|
+
|
|
125
|
+
constructor(
|
|
126
|
+
/** Persistent compute graph. */
|
|
127
|
+
private readonly _graph: ComputeGraphModel,
|
|
128
|
+
) {
|
|
129
|
+
super();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
toJSON() {
|
|
133
|
+
return {
|
|
134
|
+
graph: this._graph,
|
|
135
|
+
state: {
|
|
136
|
+
inputs: this._runtimeStateInputs,
|
|
137
|
+
outputs: this._runtimeStateOutputs,
|
|
138
|
+
},
|
|
139
|
+
forcedOutputs: this._forcedOutputs,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
setServices(services: Partial<Services>) {
|
|
144
|
+
Object.assign(this._services, services);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
get graph() {
|
|
148
|
+
return this._graph;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
get userState() {
|
|
152
|
+
return this._forcedOutputs;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
get inputStates() {
|
|
156
|
+
return this._runtimeStateInputs;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
get outputStates() {
|
|
160
|
+
return this._runtimeStateOutputs;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Inputs and outputs for all nodes.
|
|
165
|
+
*/
|
|
166
|
+
get state() {
|
|
167
|
+
const ids = [...new Set([...Object.keys(this._runtimeStateInputs), ...Object.keys(this._runtimeStateOutputs)])];
|
|
168
|
+
return Object.fromEntries(
|
|
169
|
+
ids.map((id) => [
|
|
170
|
+
id,
|
|
171
|
+
{
|
|
172
|
+
node: this._graph.getNode(id),
|
|
173
|
+
input: this._runtimeStateInputs[id],
|
|
174
|
+
output: this._runtimeStateOutputs[id],
|
|
175
|
+
},
|
|
176
|
+
]),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
addNode(node: ComputeNode) {
|
|
181
|
+
this._graph.addNode(node);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
addEdge(edge: ComputeEdge) {
|
|
185
|
+
this._graph.addEdge(edge);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
getComputeNode(nodeId: string): ComputeNode {
|
|
189
|
+
return this._graph.getNode(nodeId);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
getInputs(nodeId: string) {
|
|
193
|
+
return this._runtimeStateInputs[nodeId] ?? {};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
getOutputs(nodeId: string) {
|
|
197
|
+
return this._runtimeStateOutputs[nodeId] ?? {};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
setOutput(nodeId: string, property: string, value: any) {
|
|
201
|
+
this._forcedOutputs[nodeId] ??= {};
|
|
202
|
+
this._forcedOutputs[nodeId][property] = value;
|
|
203
|
+
|
|
204
|
+
queueMicrotask(async () => {
|
|
205
|
+
try {
|
|
206
|
+
await this.exec(nodeId);
|
|
207
|
+
} catch (err) {
|
|
208
|
+
log.catch(err);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async getMeta(node: ComputeNode): Promise<ComputeMeta> {
|
|
214
|
+
const { meta } = await resolveComputeNode(node);
|
|
215
|
+
return meta;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async evalNode(nodeId: string) {
|
|
219
|
+
const executor = this._executor.clone();
|
|
220
|
+
await executor.load(this._graph);
|
|
221
|
+
|
|
222
|
+
for (const [nodeId, outputs] of Object.entries(this._forcedOutputs)) {
|
|
223
|
+
executor.setOutputs(nodeId, Effect.succeed(makeValueBag(outputs)));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const services = this._createServiceLayer();
|
|
227
|
+
await Effect.runPromise(
|
|
228
|
+
Effect.gen(this, function* () {
|
|
229
|
+
const scope = yield* Scope.make();
|
|
230
|
+
|
|
231
|
+
// TODO(dmaretskyi): Code duplication.
|
|
232
|
+
const executable = yield* Effect.promise(() => resolveComputeNode(this._graph.getNode(nodeId)));
|
|
233
|
+
const computingOutputs = executable.exec != null;
|
|
234
|
+
// TODO(dmaretskyi): Check if the node has a compute function and run computeOutputs if it does.
|
|
235
|
+
const effect = (computingOutputs ? executor.computeOutputs(nodeId) : executor.computeInputs(nodeId)).pipe(
|
|
236
|
+
Effect.withSpan('runGraph'),
|
|
237
|
+
Effect.provide(services),
|
|
238
|
+
Scope.extend(scope),
|
|
239
|
+
|
|
240
|
+
Effect.flatMap(computeValueBag),
|
|
241
|
+
Effect.withSpan('test'),
|
|
242
|
+
Effect.tap((values) => {
|
|
243
|
+
for (const [key, value] of Object.entries(values)) {
|
|
244
|
+
if (computingOutputs) {
|
|
245
|
+
this._onOutputComputed(nodeId, key, value);
|
|
246
|
+
} else {
|
|
247
|
+
this._onInputComputed(nodeId, key, value);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}),
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
yield* effect;
|
|
254
|
+
|
|
255
|
+
yield* Scope.close(scope, Exit.void);
|
|
256
|
+
}),
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
this.update.emit();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Executes the graph.
|
|
264
|
+
* @param startFromNode - Node to start from, otherwise all {@link AUTO_TRIGGER_NODES} are executed.
|
|
265
|
+
*/
|
|
266
|
+
@synchronized
|
|
267
|
+
async exec(startFromNode?: string) {
|
|
268
|
+
this._runtimeStateInputs = {};
|
|
269
|
+
this._runtimeStateOutputs = {};
|
|
270
|
+
const executor = this._executor.clone();
|
|
271
|
+
await executor.load(this._graph);
|
|
272
|
+
|
|
273
|
+
for (const [nodeId, outputs] of Object.entries(this._forcedOutputs)) {
|
|
274
|
+
executor.setOutputs(nodeId, Effect.succeed(makeValueBag(outputs)));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// TODO(dmaretskyi): Stop hardcoding.
|
|
278
|
+
const triggerNodes =
|
|
279
|
+
startFromNode != null
|
|
280
|
+
? [this._graph.getNode(startFromNode)]
|
|
281
|
+
: this._graph.nodes.filter((node) => node.type != null && AUTO_TRIGGER_NODES.includes(node.type));
|
|
282
|
+
const allAffectedNodes = [...new Set(triggerNodes.flatMap((node) => executor.getAllDependantNodes(node.id)))];
|
|
283
|
+
|
|
284
|
+
const services = this._createServiceLayer();
|
|
285
|
+
await Effect.runPromise(
|
|
286
|
+
Effect.gen(this, function* () {
|
|
287
|
+
const scope = yield* Scope.make();
|
|
288
|
+
|
|
289
|
+
// TODO(burdon): Return map?
|
|
290
|
+
const tasks: Effect.Effect<unknown, any, never>[] = [];
|
|
291
|
+
for (const node of allAffectedNodes) {
|
|
292
|
+
// TODO(dmaretskyi): Code duplication.
|
|
293
|
+
const executable = yield* Effect.promise(() => resolveComputeNode(this._graph.getNode(node)));
|
|
294
|
+
const computingOutputs = executable.exec != null;
|
|
295
|
+
|
|
296
|
+
// TODO(dmaretskyi): Check if the node has a compute function and run computeOutputs if it does.
|
|
297
|
+
const effect = (computingOutputs ? executor.computeOutputs(node) : executor.computeInputs(node)).pipe(
|
|
298
|
+
Effect.withSpan('runGraph'),
|
|
299
|
+
Effect.provide(services),
|
|
300
|
+
Scope.extend(scope),
|
|
301
|
+
Effect.flatMap(computeValueBag),
|
|
302
|
+
Effect.withSpan('test'),
|
|
303
|
+
Effect.tap((values) => {
|
|
304
|
+
for (const [key, value] of Object.entries(values)) {
|
|
305
|
+
if (computingOutputs) {
|
|
306
|
+
this._onOutputComputed(node, key, value);
|
|
307
|
+
} else {
|
|
308
|
+
this._onInputComputed(node, key, value);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}),
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
tasks.push(effect);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
//
|
|
318
|
+
yield* Effect.all(tasks);
|
|
319
|
+
|
|
320
|
+
//
|
|
321
|
+
yield* Scope.close(scope, Exit.void);
|
|
322
|
+
}),
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
this.update.emit();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private _createServiceLayer(): Layer.Layer<Exclude<ComputeRequirements, Scope.Scope>> {
|
|
329
|
+
const services = { ...DEFAULT_SERVICES, ...this._services };
|
|
330
|
+
const logLayer = Layer.succeed(EventLogger, this._createLogger());
|
|
331
|
+
const gptLayer = Layer.succeed(GptService, services.gpt!);
|
|
332
|
+
const queueLayer =
|
|
333
|
+
services.edgeHttpClient != null ? QueueService.fromClient(services.edgeHttpClient) : QueueService.notAvailable;
|
|
334
|
+
|
|
335
|
+
const spaceLayer =
|
|
336
|
+
services.spaceService != null ? Layer.succeed(SpaceService, services.spaceService) : SpaceService.empty;
|
|
337
|
+
|
|
338
|
+
return Layer.mergeAll(logLayer, gptLayer, queueLayer, spaceLayer);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private _createLogger(): Context.Tag.Service<EventLogger> {
|
|
342
|
+
return {
|
|
343
|
+
log: (event) => {
|
|
344
|
+
this._handleEvent(event);
|
|
345
|
+
},
|
|
346
|
+
nodeId: undefined, // Not in a context of a specific node.
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private _handleEvent(event: ComputeEvent) {
|
|
351
|
+
log('handleEvent', { event });
|
|
352
|
+
switch (event.type) {
|
|
353
|
+
case 'compute-input': {
|
|
354
|
+
this._onInputComputed(event.nodeId, event.property, { type: 'executed', value: event.value });
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
case 'compute-output': {
|
|
359
|
+
this._onOutputComputed(event.nodeId, event.property, { type: 'executed', value: event.value });
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
this.events.emit(event);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
private _onInputComputed(nodeId: string, property: string, value: RuntimeValue) {
|
|
367
|
+
this._runtimeStateInputs[nodeId] ??= {};
|
|
368
|
+
this._runtimeStateInputs[nodeId][property] = value;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private _onOutputComputed(nodeId: string, property: string, value: RuntimeValue) {
|
|
372
|
+
this._runtimeStateOutputs[nodeId] ??= {};
|
|
373
|
+
this._runtimeStateOutputs[nodeId][property] = value;
|
|
374
|
+
|
|
375
|
+
// TODO(burdon): Only fire if changed?
|
|
376
|
+
this.output.emit({ nodeId, property, value });
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const DEFAULT_SERVICES: Services = {
|
|
381
|
+
gpt: new MockGpt(),
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Waits for all effects in the bag to complete and returns the `RuntimeValue` for each property.
|
|
386
|
+
*/
|
|
387
|
+
const computeValueBag = (bag: ValueBag<any>): Effect.Effect<Record<string, RuntimeValue>, never, never> => {
|
|
388
|
+
return Effect.all(
|
|
389
|
+
Object.entries(bag.values).map(([key, eff]) =>
|
|
390
|
+
Effect.either(eff).pipe(
|
|
391
|
+
Effect.map((value) => {
|
|
392
|
+
if (Either.isLeft(value)) {
|
|
393
|
+
if (isNotExecuted(value.left)) {
|
|
394
|
+
return [key, { type: 'not-executed' }] as const;
|
|
395
|
+
} else {
|
|
396
|
+
return [key, { type: 'error', error: value.left }] as const;
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
return [key, { type: 'executed', value: value.right }] as const;
|
|
400
|
+
}
|
|
401
|
+
}),
|
|
402
|
+
),
|
|
403
|
+
),
|
|
404
|
+
).pipe(Effect.map((entries) => Object.fromEntries(entries) as Record<string, RuntimeValue>));
|
|
405
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
NODE_INPUT,
|
|
7
|
+
NODE_OUTPUT,
|
|
8
|
+
type ComputeNode,
|
|
9
|
+
type Executable,
|
|
10
|
+
type NodeType,
|
|
11
|
+
registry,
|
|
12
|
+
getTemplateInputSchema,
|
|
13
|
+
} from '@dxos/conductor';
|
|
14
|
+
import { raise } from '@dxos/debug';
|
|
15
|
+
import { ObjectId, toJsonSchema } from '@dxos/echo-schema';
|
|
16
|
+
import { invariant } from '@dxos/invariant';
|
|
17
|
+
|
|
18
|
+
import { type ComputeShape, type ConstantShape, type TemplateShape } from '../shapes';
|
|
19
|
+
|
|
20
|
+
export const resolveComputeNode = async (node: ComputeNode): Promise<Executable> => {
|
|
21
|
+
const impl = registry[node.type as NodeType];
|
|
22
|
+
invariant(impl, `Unknown node type: ${node.type}`);
|
|
23
|
+
return impl;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const isValidComputeNode = (type: string): boolean => {
|
|
27
|
+
return nodeFactory[type as NodeType] !== undefined;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const createComputeNode = (shape: ComputeShape): ComputeNode => {
|
|
31
|
+
const type = shape.type ?? raise(new Error('Type not specified'));
|
|
32
|
+
const factory = nodeFactory[type as NodeType] ?? raise(new Error(`Unknown shape type: ${type}`));
|
|
33
|
+
return factory(shape);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const nodeFactory: Record<NodeType | 'trigger', (shape: ComputeShape) => ComputeNode> = {
|
|
37
|
+
// System.
|
|
38
|
+
[NODE_INPUT]: () => createNode(NODE_INPUT),
|
|
39
|
+
[NODE_OUTPUT]: () => createNode(NODE_OUTPUT),
|
|
40
|
+
|
|
41
|
+
// Extensions.
|
|
42
|
+
['text-to-image' as const]: () => createNode('text-to-image'), // TODO(burdon): Rename ai-impage-tool
|
|
43
|
+
['and' as const]: () => createNode('and'),
|
|
44
|
+
['append' as const]: () => createNode('append'),
|
|
45
|
+
['audio' as const]: () => createNode('audio'),
|
|
46
|
+
['beacon' as const]: () => createNode('beacon'),
|
|
47
|
+
['chat' as const]: () => createNode('chat'),
|
|
48
|
+
['constant' as const]: (shape) =>
|
|
49
|
+
createNode('constant', {
|
|
50
|
+
value: (shape as ConstantShape).value,
|
|
51
|
+
}),
|
|
52
|
+
['database' as const]: () => createNode('database'),
|
|
53
|
+
['gpt' as const]: () => createNode('gpt'),
|
|
54
|
+
['gpt-realtime' as const]: () => createNode('gpt-realtime'),
|
|
55
|
+
['if' as const]: () => createNode('if'),
|
|
56
|
+
['if-else' as const]: () => createNode('if-else'),
|
|
57
|
+
['function' as const]: () => createNode('function'),
|
|
58
|
+
['json' as const]: () => createNode('json'),
|
|
59
|
+
['json-transform' as const]: () => createNode('json-transform'),
|
|
60
|
+
['not' as const]: () => createNode('not'),
|
|
61
|
+
['or' as const]: () => createNode('or'),
|
|
62
|
+
['queue' as const]: () => createNode('queue'),
|
|
63
|
+
['rng' as const]: () => createNode('rng'),
|
|
64
|
+
['reducer' as const]: () => createNode('reducer'),
|
|
65
|
+
['scope' as const]: () => createNode('scope'),
|
|
66
|
+
['surface' as const]: () => createNode('surface'),
|
|
67
|
+
['switch' as const]: () => createNode('switch'),
|
|
68
|
+
['template' as const]: (shape) => {
|
|
69
|
+
const node = createNode('template', { valueType: (shape as TemplateShape).valueType, value: shape.text });
|
|
70
|
+
node.inputSchema = toJsonSchema(getTemplateInputSchema(node));
|
|
71
|
+
return node;
|
|
72
|
+
},
|
|
73
|
+
['text' as const]: () => createNode('text'),
|
|
74
|
+
['thread' as const]: () => createNode('thread'),
|
|
75
|
+
['trigger' as const]: () => createNode(NODE_INPUT),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const createNode = (type: string, props?: Partial<ComputeNode>): ComputeNode => ({
|
|
79
|
+
id: ObjectId.random(),
|
|
80
|
+
type,
|
|
81
|
+
...props,
|
|
82
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { createContext, useContext } from 'react';
|
|
6
|
+
|
|
7
|
+
import { raise } from '@dxos/debug';
|
|
8
|
+
|
|
9
|
+
import type { ComputeGraphController } from '../graph';
|
|
10
|
+
|
|
11
|
+
export type ComputeContextType = {
|
|
12
|
+
controller: ComputeGraphController;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const ComputeContext = createContext<ComputeContextType | null>(null);
|
|
16
|
+
|
|
17
|
+
export const useComputeContext = () => {
|
|
18
|
+
return useContext(ComputeContext) ?? raise(new Error('Missing ComputeContext'));
|
|
19
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import type { ComputeNode, ComputeMeta, ComputeEvent } from '@dxos/conductor';
|
|
8
|
+
import { S } from '@dxos/echo-schema';
|
|
9
|
+
import { invariant } from '@dxos/invariant';
|
|
10
|
+
|
|
11
|
+
import { useComputeContext } from './compute-context';
|
|
12
|
+
import { type RuntimeValue } from '../graph';
|
|
13
|
+
import { type ComputeShape } from '../shapes';
|
|
14
|
+
|
|
15
|
+
export type ComputeNodeState = {
|
|
16
|
+
node: ComputeNode;
|
|
17
|
+
meta: ComputeMeta;
|
|
18
|
+
runtime: {
|
|
19
|
+
inputs: Record<string, RuntimeValue>;
|
|
20
|
+
outputs: Record<string, RuntimeValue>;
|
|
21
|
+
setOutput: (property: string, value: any) => void;
|
|
22
|
+
evalNode: () => void;
|
|
23
|
+
subscribeToEventLog: (cb: (event: ComputeEvent) => void) => void;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Runtime state of a compute node.
|
|
29
|
+
*/
|
|
30
|
+
export const useComputeNodeState = (shape: ComputeShape): ComputeNodeState => {
|
|
31
|
+
const { controller } = useComputeContext();
|
|
32
|
+
invariant(controller);
|
|
33
|
+
|
|
34
|
+
const [meta, setMeta] = useState<ComputeMeta>();
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
let disposed = false;
|
|
37
|
+
queueMicrotask(async () => {
|
|
38
|
+
invariant(shape.node, 'Node not specified');
|
|
39
|
+
const node = controller.getComputeNode(shape.node);
|
|
40
|
+
const meta = await controller.getMeta(node);
|
|
41
|
+
if (disposed) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
setMeta(meta);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return () => {
|
|
48
|
+
disposed = true;
|
|
49
|
+
};
|
|
50
|
+
}, [shape.node]);
|
|
51
|
+
|
|
52
|
+
const evalNode = useCallback(() => {
|
|
53
|
+
return controller.evalNode(shape.node!);
|
|
54
|
+
}, [shape.node]);
|
|
55
|
+
|
|
56
|
+
const subscribeToEventLog = useCallback(
|
|
57
|
+
(cb: (event: ComputeEvent) => void) => {
|
|
58
|
+
return controller.events.on((ev) => {
|
|
59
|
+
if (ev.nodeId === shape.node) {
|
|
60
|
+
cb(ev);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
[shape.node],
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
node: controller.getComputeNode(shape.node!),
|
|
69
|
+
meta: meta ?? {
|
|
70
|
+
input: S.Struct({}),
|
|
71
|
+
output: S.Struct({}),
|
|
72
|
+
},
|
|
73
|
+
runtime: {
|
|
74
|
+
inputs: controller.getInputs(shape.node!),
|
|
75
|
+
outputs: controller.getOutputs(shape.node!),
|
|
76
|
+
setOutput: (property: string, value: any) => {
|
|
77
|
+
controller.setOutput(shape.node!, property, value);
|
|
78
|
+
},
|
|
79
|
+
evalNode,
|
|
80
|
+
subscribeToEventLog,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
};
|