@reactor-team/js-sdk 1.0.1
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/.env.example +2 -0
- package/.prettierignore +7 -0
- package/.prettierrc +9 -0
- package/README.md +26 -0
- package/dist/index.d.mts +151 -0
- package/dist/index.d.ts +151 -0
- package/dist/index.js +1000 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +971 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +45 -0
- package/publish_package.sh +45 -0
- package/src/core/CoordinatorClient.ts +160 -0
- package/src/core/GPUMachineClient.ts +172 -0
- package/src/core/Reactor.ts +407 -0
- package/src/core/store.ts +163 -0
- package/src/core/types.ts +99 -0
- package/src/index.ts +5 -0
- package/src/react/ReactorProvider.tsx +86 -0
- package/src/react/ReactorView.tsx +116 -0
- package/src/react/hooks.ts +50 -0
- package/src/types.ts +37 -0
- package/tsconfig.json +19 -0
- package/tsup.config.ts +15 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1000 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defProps = Object.defineProperties;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
10
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
+
var __spreadValues = (a, b) => {
|
|
12
|
+
for (var prop in b || (b = {}))
|
|
13
|
+
if (__hasOwnProp.call(b, prop))
|
|
14
|
+
__defNormalProp(a, prop, b[prop]);
|
|
15
|
+
if (__getOwnPropSymbols)
|
|
16
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
17
|
+
if (__propIsEnum.call(b, prop))
|
|
18
|
+
__defNormalProp(a, prop, b[prop]);
|
|
19
|
+
}
|
|
20
|
+
return a;
|
|
21
|
+
};
|
|
22
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
23
|
+
var __objRest = (source, exclude) => {
|
|
24
|
+
var target = {};
|
|
25
|
+
for (var prop in source)
|
|
26
|
+
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
|
|
27
|
+
target[prop] = source[prop];
|
|
28
|
+
if (source != null && __getOwnPropSymbols)
|
|
29
|
+
for (var prop of __getOwnPropSymbols(source)) {
|
|
30
|
+
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
|
|
31
|
+
target[prop] = source[prop];
|
|
32
|
+
}
|
|
33
|
+
return target;
|
|
34
|
+
};
|
|
35
|
+
var __export = (target, all) => {
|
|
36
|
+
for (var name in all)
|
|
37
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
38
|
+
};
|
|
39
|
+
var __copyProps = (to, from, except, desc) => {
|
|
40
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
41
|
+
for (let key of __getOwnPropNames(from))
|
|
42
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
43
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
44
|
+
}
|
|
45
|
+
return to;
|
|
46
|
+
};
|
|
47
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
48
|
+
var __async = (__this, __arguments, generator) => {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
var fulfilled = (value) => {
|
|
51
|
+
try {
|
|
52
|
+
step(generator.next(value));
|
|
53
|
+
} catch (e) {
|
|
54
|
+
reject(e);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var rejected = (value) => {
|
|
58
|
+
try {
|
|
59
|
+
step(generator.throw(value));
|
|
60
|
+
} catch (e) {
|
|
61
|
+
reject(e);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
65
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// src/index.ts
|
|
70
|
+
var index_exports = {};
|
|
71
|
+
__export(index_exports, {
|
|
72
|
+
Reactor: () => Reactor,
|
|
73
|
+
ReactorProvider: () => ReactorProvider,
|
|
74
|
+
ReactorView: () => ReactorView,
|
|
75
|
+
useReactor: () => useReactor,
|
|
76
|
+
useReactorMessage: () => useReactorMessage,
|
|
77
|
+
useReactorStore: () => useReactorStore
|
|
78
|
+
});
|
|
79
|
+
module.exports = __toCommonJS(index_exports);
|
|
80
|
+
|
|
81
|
+
// src/core/types.ts
|
|
82
|
+
var import_zod = require("zod");
|
|
83
|
+
var ApplicationMessageSchema = import_zod.z.object({
|
|
84
|
+
type: import_zod.z.literal("application"),
|
|
85
|
+
data: import_zod.z.any()
|
|
86
|
+
// Can be any JSON-serializable data
|
|
87
|
+
});
|
|
88
|
+
var FPSMessageSchema = import_zod.z.object({
|
|
89
|
+
type: import_zod.z.literal("fps"),
|
|
90
|
+
data: import_zod.z.number()
|
|
91
|
+
});
|
|
92
|
+
var GPUMachineReceiveMessageSchema = import_zod.z.discriminatedUnion("type", [
|
|
93
|
+
ApplicationMessageSchema,
|
|
94
|
+
FPSMessageSchema
|
|
95
|
+
]);
|
|
96
|
+
var WelcomeMessageSchema = import_zod.z.object({
|
|
97
|
+
type: import_zod.z.literal("welcome"),
|
|
98
|
+
data: import_zod.z.record(import_zod.z.string(), import_zod.z.any())
|
|
99
|
+
});
|
|
100
|
+
var GPUMachineAssignmentDataSchema = import_zod.z.object({
|
|
101
|
+
livekitWsUrl: import_zod.z.string(),
|
|
102
|
+
livekitJwtToken: import_zod.z.string()
|
|
103
|
+
});
|
|
104
|
+
var GPUMachineAssignmentMessageSchema = import_zod.z.object({
|
|
105
|
+
type: import_zod.z.literal("gpu-machine-assigned"),
|
|
106
|
+
data: GPUMachineAssignmentDataSchema
|
|
107
|
+
});
|
|
108
|
+
var EchoMessageSchema = import_zod.z.object({
|
|
109
|
+
type: import_zod.z.literal("echo"),
|
|
110
|
+
data: import_zod.z.record(import_zod.z.string(), import_zod.z.any())
|
|
111
|
+
});
|
|
112
|
+
var WaitingInfoDataSchema = import_zod.z.object({
|
|
113
|
+
position: import_zod.z.number().optional(),
|
|
114
|
+
estimatedWaitTime: import_zod.z.number().optional(),
|
|
115
|
+
averageWaitTime: import_zod.z.number().optional()
|
|
116
|
+
});
|
|
117
|
+
var WaitingInfoMessageSchema = import_zod.z.object({
|
|
118
|
+
type: import_zod.z.literal("waiting-info"),
|
|
119
|
+
data: WaitingInfoDataSchema
|
|
120
|
+
});
|
|
121
|
+
var CoordinatorMessageSchema = import_zod.z.discriminatedUnion("type", [
|
|
122
|
+
WelcomeMessageSchema,
|
|
123
|
+
GPUMachineAssignmentMessageSchema,
|
|
124
|
+
EchoMessageSchema,
|
|
125
|
+
WaitingInfoMessageSchema
|
|
126
|
+
]);
|
|
127
|
+
var GPUMachineSendMessageSchema = import_zod.z.discriminatedUnion("type", [
|
|
128
|
+
ApplicationMessageSchema
|
|
129
|
+
]);
|
|
130
|
+
var SessionSetupMessageSchema = import_zod.z.object({
|
|
131
|
+
type: import_zod.z.literal("sessionSetup"),
|
|
132
|
+
data: import_zod.z.object({
|
|
133
|
+
modelName: import_zod.z.string(),
|
|
134
|
+
modelVersion: import_zod.z.string().default("latest")
|
|
135
|
+
})
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// src/core/CoordinatorClient.ts
|
|
139
|
+
var import_zod2 = require("zod");
|
|
140
|
+
var OptionsSchema = import_zod2.z.object({
|
|
141
|
+
wsUrl: import_zod2.z.string().nonempty(),
|
|
142
|
+
jwtToken: import_zod2.z.string().optional(),
|
|
143
|
+
insecureApiKey: import_zod2.z.string().optional(),
|
|
144
|
+
modelName: import_zod2.z.string(),
|
|
145
|
+
modelVersion: import_zod2.z.string().default("latest")
|
|
146
|
+
}).refine((data) => data.jwtToken || data.insecureApiKey, {
|
|
147
|
+
message: "At least one of jwtToken or insecureApiKey must be provided."
|
|
148
|
+
});
|
|
149
|
+
var CoordinatorClient = class {
|
|
150
|
+
constructor(options) {
|
|
151
|
+
this.eventListeners = /* @__PURE__ */ new Map();
|
|
152
|
+
const validatedOptions = OptionsSchema.parse(options);
|
|
153
|
+
this.wsUrl = validatedOptions.wsUrl;
|
|
154
|
+
this.protocols = validatedOptions.jwtToken ? [`jwt-token.${validatedOptions.jwtToken}`] : [`insecure-api-key.${validatedOptions.insecureApiKey}`];
|
|
155
|
+
this.modelName = validatedOptions.modelName;
|
|
156
|
+
this.modelVersion = validatedOptions.modelVersion;
|
|
157
|
+
}
|
|
158
|
+
// Event Emitter API
|
|
159
|
+
on(event, handler) {
|
|
160
|
+
if (!this.eventListeners.has(event)) {
|
|
161
|
+
this.eventListeners.set(event, /* @__PURE__ */ new Set());
|
|
162
|
+
}
|
|
163
|
+
this.eventListeners.get(event).add(handler);
|
|
164
|
+
}
|
|
165
|
+
off(event, handler) {
|
|
166
|
+
var _a;
|
|
167
|
+
(_a = this.eventListeners.get(event)) == null ? void 0 : _a.delete(handler);
|
|
168
|
+
}
|
|
169
|
+
emit(event, ...args) {
|
|
170
|
+
var _a;
|
|
171
|
+
(_a = this.eventListeners.get(event)) == null ? void 0 : _a.forEach((handler) => handler(...args));
|
|
172
|
+
}
|
|
173
|
+
sendMessage(message) {
|
|
174
|
+
var _a;
|
|
175
|
+
try {
|
|
176
|
+
const messageStr = typeof message === "string" ? message : JSON.stringify(message);
|
|
177
|
+
(_a = this.websocket) == null ? void 0 : _a.send(messageStr);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error("[CoordinatorClient] Failed to send message:", error);
|
|
180
|
+
this.emit("statusChanged", "error");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
connect() {
|
|
184
|
+
return __async(this, null, function* () {
|
|
185
|
+
console.debug("[CoordinatorClient] Connecting to", this.wsUrl);
|
|
186
|
+
this.websocket = new WebSocket(this.wsUrl, this.protocols);
|
|
187
|
+
this.websocket.onopen = () => {
|
|
188
|
+
console.debug("[CoordinatorClient] WebSocket opened");
|
|
189
|
+
this.emit("statusChanged", "connected");
|
|
190
|
+
};
|
|
191
|
+
this.websocket.onmessage = (event) => {
|
|
192
|
+
try {
|
|
193
|
+
let parsedData;
|
|
194
|
+
if (typeof event.data === "string") {
|
|
195
|
+
parsedData = JSON.parse(event.data);
|
|
196
|
+
} else {
|
|
197
|
+
parsedData = event.data;
|
|
198
|
+
}
|
|
199
|
+
console.debug(
|
|
200
|
+
"[CoordinatorClient] Received message from coordinator:",
|
|
201
|
+
parsedData
|
|
202
|
+
);
|
|
203
|
+
const validatedData = CoordinatorMessageSchema.parse(parsedData);
|
|
204
|
+
this.emit(validatedData.type, validatedData.data);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error(
|
|
207
|
+
"[CoordinatorClient] Failed to parse WebSocket message from coordinator:",
|
|
208
|
+
error,
|
|
209
|
+
"message",
|
|
210
|
+
event.data
|
|
211
|
+
);
|
|
212
|
+
this.emit("statusChanged", "error");
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
this.websocket.onclose = (event) => {
|
|
216
|
+
console.debug("[CoordinatorClient] WebSocket closed", event);
|
|
217
|
+
this.websocket = void 0;
|
|
218
|
+
this.emit("statusChanged", "disconnected");
|
|
219
|
+
};
|
|
220
|
+
this.websocket.onerror = (error) => {
|
|
221
|
+
console.error("[CoordinatorClient] WebSocket error:", error);
|
|
222
|
+
this.websocket = void 0;
|
|
223
|
+
this.emit("statusChanged", "error");
|
|
224
|
+
};
|
|
225
|
+
yield new Promise((resolve, reject) => {
|
|
226
|
+
var _a, _b;
|
|
227
|
+
const onOpen = () => {
|
|
228
|
+
var _a2;
|
|
229
|
+
(_a2 = this.websocket) == null ? void 0 : _a2.removeEventListener("error", onError);
|
|
230
|
+
resolve();
|
|
231
|
+
};
|
|
232
|
+
const onError = (error) => {
|
|
233
|
+
var _a2;
|
|
234
|
+
(_a2 = this.websocket) == null ? void 0 : _a2.removeEventListener("open", onOpen);
|
|
235
|
+
reject(error);
|
|
236
|
+
};
|
|
237
|
+
(_a = this.websocket) == null ? void 0 : _a.addEventListener("open", onOpen);
|
|
238
|
+
(_b = this.websocket) == null ? void 0 : _b.addEventListener("error", onError);
|
|
239
|
+
});
|
|
240
|
+
console.log("[CoordinatorClient] WebSocket connected");
|
|
241
|
+
this.sendMessage({
|
|
242
|
+
type: "sessionSetup",
|
|
243
|
+
data: {
|
|
244
|
+
modelName: this.modelName,
|
|
245
|
+
modelVersion: this.modelVersion
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
console.debug("[CoordinatorClient] Setup session message sent");
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Closes the WebSocket connection if it exists.
|
|
253
|
+
* This will trigger the onclose event handler.
|
|
254
|
+
*/
|
|
255
|
+
disconnect() {
|
|
256
|
+
if (this.websocket) {
|
|
257
|
+
console.debug("[CoordinatorClient] Closing WebSocket connection");
|
|
258
|
+
this.websocket.close();
|
|
259
|
+
this.websocket = void 0;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// src/core/GPUMachineClient.ts
|
|
265
|
+
var import_livekit_client = require("livekit-client");
|
|
266
|
+
var GPUMachineClient = class {
|
|
267
|
+
constructor(token, liveKitUrl) {
|
|
268
|
+
this.eventListeners = /* @__PURE__ */ new Map();
|
|
269
|
+
this.token = token;
|
|
270
|
+
this.liveKitUrl = liveKitUrl;
|
|
271
|
+
}
|
|
272
|
+
// Event Emitter API
|
|
273
|
+
on(event, handler) {
|
|
274
|
+
if (!this.eventListeners.has(event)) {
|
|
275
|
+
this.eventListeners.set(event, /* @__PURE__ */ new Set());
|
|
276
|
+
}
|
|
277
|
+
this.eventListeners.get(event).add(handler);
|
|
278
|
+
}
|
|
279
|
+
off(event, handler) {
|
|
280
|
+
var _a;
|
|
281
|
+
(_a = this.eventListeners.get(event)) == null ? void 0 : _a.delete(handler);
|
|
282
|
+
}
|
|
283
|
+
emit(event, ...args) {
|
|
284
|
+
var _a;
|
|
285
|
+
(_a = this.eventListeners.get(event)) == null ? void 0 : _a.forEach((handler) => handler(...args));
|
|
286
|
+
}
|
|
287
|
+
sendMessage(message) {
|
|
288
|
+
try {
|
|
289
|
+
if (this.roomInstance) {
|
|
290
|
+
const messageStr = JSON.stringify(message);
|
|
291
|
+
this.roomInstance.localParticipant.sendText(messageStr, {
|
|
292
|
+
topic: "application"
|
|
293
|
+
});
|
|
294
|
+
} else {
|
|
295
|
+
console.warn(
|
|
296
|
+
"[GPUMachineClient] Cannot send message - not connected to room"
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
} catch (error) {
|
|
300
|
+
console.error("[GPUMachineClient] Failed to send message:", error);
|
|
301
|
+
this.emit("statusChanged", "error");
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
connect() {
|
|
305
|
+
return __async(this, null, function* () {
|
|
306
|
+
this.roomInstance = new import_livekit_client.Room({
|
|
307
|
+
adaptiveStream: true,
|
|
308
|
+
dynacast: true
|
|
309
|
+
});
|
|
310
|
+
this.roomInstance.on(import_livekit_client.RoomEvent.Connected, () => {
|
|
311
|
+
console.debug("[GPUMachineClient] Connected to room");
|
|
312
|
+
this.emit("statusChanged", "connected");
|
|
313
|
+
});
|
|
314
|
+
this.roomInstance.on(import_livekit_client.RoomEvent.Disconnected, () => {
|
|
315
|
+
console.debug("[GPUMachineClient] Disconnected from room");
|
|
316
|
+
this.emit("statusChanged", "disconnected");
|
|
317
|
+
});
|
|
318
|
+
this.roomInstance.on(
|
|
319
|
+
import_livekit_client.RoomEvent.TrackSubscribed,
|
|
320
|
+
(track, _publication, participant) => {
|
|
321
|
+
console.debug(
|
|
322
|
+
"[GPUMachineClient] Track subscribed:",
|
|
323
|
+
track.kind,
|
|
324
|
+
participant.identity
|
|
325
|
+
);
|
|
326
|
+
if (track.kind === import_livekit_client.Track.Kind.Video) {
|
|
327
|
+
const videoTrack = track;
|
|
328
|
+
this.emit("streamChanged", videoTrack);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
);
|
|
332
|
+
this.roomInstance.on(
|
|
333
|
+
import_livekit_client.RoomEvent.TrackUnsubscribed,
|
|
334
|
+
(track, _publication, participant) => {
|
|
335
|
+
console.debug(
|
|
336
|
+
"[GPUMachineClient] Track unsubscribed:",
|
|
337
|
+
track.kind,
|
|
338
|
+
participant.identity
|
|
339
|
+
);
|
|
340
|
+
if (track.kind === import_livekit_client.Track.Kind.Video) {
|
|
341
|
+
this.emit("streamChanged", null);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
);
|
|
345
|
+
this.roomInstance.registerTextStreamHandler(
|
|
346
|
+
"application",
|
|
347
|
+
(reader, participant) => __async(this, null, function* () {
|
|
348
|
+
const text = yield reader.readAll();
|
|
349
|
+
console.log("[GPUMachineClient] Received message:", text);
|
|
350
|
+
try {
|
|
351
|
+
const parsedData = JSON.parse(text);
|
|
352
|
+
const validatedMessage = GPUMachineReceiveMessageSchema.parse(parsedData);
|
|
353
|
+
if (validatedMessage.type === "fps") {
|
|
354
|
+
this.machineFPS = validatedMessage.data;
|
|
355
|
+
}
|
|
356
|
+
this.emit(validatedMessage.type, validatedMessage.data);
|
|
357
|
+
} catch (error) {
|
|
358
|
+
console.error(
|
|
359
|
+
"[GPUMachineClient] Failed to parse/validate message:",
|
|
360
|
+
error
|
|
361
|
+
);
|
|
362
|
+
this.emit("statusChanged", "error");
|
|
363
|
+
}
|
|
364
|
+
})
|
|
365
|
+
);
|
|
366
|
+
yield this.roomInstance.connect(this.liveKitUrl, this.token);
|
|
367
|
+
console.log("[GPUMachineClient] Room connected");
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Closes the LiveKit connection if it exists.
|
|
372
|
+
* This will trigger the onclose event handler.
|
|
373
|
+
*/
|
|
374
|
+
disconnect() {
|
|
375
|
+
return __async(this, null, function* () {
|
|
376
|
+
if (this.roomInstance) {
|
|
377
|
+
console.debug("[GPUMachineClient] Closing LiveKit connection");
|
|
378
|
+
yield this.roomInstance.disconnect();
|
|
379
|
+
this.roomInstance = void 0;
|
|
380
|
+
}
|
|
381
|
+
this.machineFPS = void 0;
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Returns the current fps rate of the machine.
|
|
386
|
+
* @returns The current fps rate of the machine.
|
|
387
|
+
*/
|
|
388
|
+
getFPS() {
|
|
389
|
+
return this.machineFPS;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Returns the current video stream from the GPU machine.
|
|
393
|
+
* @returns The current video stream or undefined if not available.
|
|
394
|
+
*/
|
|
395
|
+
getVideoStream() {
|
|
396
|
+
return this.videoStream;
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
// src/core/Reactor.ts
|
|
401
|
+
var import_zod3 = require("zod");
|
|
402
|
+
var OptionsSchema2 = import_zod3.z.object({
|
|
403
|
+
directConnection: import_zod3.z.object({
|
|
404
|
+
livekitJwtToken: import_zod3.z.string(),
|
|
405
|
+
livekitWsUrl: import_zod3.z.string()
|
|
406
|
+
}).optional(),
|
|
407
|
+
insecureApiKey: import_zod3.z.string().optional(),
|
|
408
|
+
jwtToken: import_zod3.z.string().optional(),
|
|
409
|
+
coordinatorUrl: import_zod3.z.string().optional(),
|
|
410
|
+
modelName: import_zod3.z.string()
|
|
411
|
+
}).refine(
|
|
412
|
+
(data) => data.directConnection || data.insecureApiKey || data.jwtToken,
|
|
413
|
+
{
|
|
414
|
+
message: "At least one of directConnection, insecureApiKey, or jwtToken must be provided."
|
|
415
|
+
}
|
|
416
|
+
);
|
|
417
|
+
var Reactor = class {
|
|
418
|
+
constructor(options) {
|
|
419
|
+
//client for the machine instance
|
|
420
|
+
this.status = "disconnected";
|
|
421
|
+
// Generic event map
|
|
422
|
+
this.eventListeners = /* @__PURE__ */ new Map();
|
|
423
|
+
const validatedOptions = OptionsSchema2.parse(options);
|
|
424
|
+
this.coordinatorUrl = validatedOptions.coordinatorUrl || "ws://localhost:8080/ws";
|
|
425
|
+
this.jwtToken = validatedOptions.jwtToken;
|
|
426
|
+
this.insecureApiKey = validatedOptions.insecureApiKey;
|
|
427
|
+
this.directConnection = validatedOptions.directConnection;
|
|
428
|
+
this.modelName = validatedOptions.modelName;
|
|
429
|
+
this.modelVersion = "1.0.0";
|
|
430
|
+
}
|
|
431
|
+
// Event Emitter API
|
|
432
|
+
on(event, handler) {
|
|
433
|
+
if (!this.eventListeners.has(event)) {
|
|
434
|
+
this.eventListeners.set(event, /* @__PURE__ */ new Set());
|
|
435
|
+
}
|
|
436
|
+
this.eventListeners.get(event).add(handler);
|
|
437
|
+
}
|
|
438
|
+
off(event, handler) {
|
|
439
|
+
var _a;
|
|
440
|
+
(_a = this.eventListeners.get(event)) == null ? void 0 : _a.delete(handler);
|
|
441
|
+
}
|
|
442
|
+
emit(event, ...args) {
|
|
443
|
+
var _a;
|
|
444
|
+
(_a = this.eventListeners.get(event)) == null ? void 0 : _a.forEach((handler) => handler(...args));
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Public method to send a message to the machine.
|
|
448
|
+
* Automatically wraps the message in an application message.
|
|
449
|
+
* @param message The message to send to the machine.
|
|
450
|
+
* @throws Error if not in ready state
|
|
451
|
+
*/
|
|
452
|
+
sendMessage(message) {
|
|
453
|
+
var _a;
|
|
454
|
+
if (process.env.NODE_ENV !== "development" && this.status !== "ready") {
|
|
455
|
+
const errorMessage = `Cannot send message, status is ${this.status}`;
|
|
456
|
+
console.error("[Reactor] Not ready, cannot send message");
|
|
457
|
+
throw new Error(errorMessage);
|
|
458
|
+
}
|
|
459
|
+
try {
|
|
460
|
+
const applicationMessage = {
|
|
461
|
+
type: "application",
|
|
462
|
+
data: message
|
|
463
|
+
};
|
|
464
|
+
(_a = this.machineClient) == null ? void 0 : _a.sendMessage(applicationMessage);
|
|
465
|
+
} catch (error) {
|
|
466
|
+
console.error("[Reactor] Failed to send message:", error);
|
|
467
|
+
this.createError(
|
|
468
|
+
"MESSAGE_SEND_FAILED",
|
|
469
|
+
`Failed to send message: ${error}`,
|
|
470
|
+
"gpu",
|
|
471
|
+
true
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Connects to the machine via LiveKit and waits for the gpu machine to be ready.
|
|
477
|
+
* Once the machine is ready, the Reactor will establish the LiveKit connection.
|
|
478
|
+
* @param livekitJwtToken The JWT token for LiveKit authentication
|
|
479
|
+
* @param livekitWsUrl The WebSocket URL for LiveKit connection
|
|
480
|
+
*/
|
|
481
|
+
connectToGPUMachine(livekitJwtToken, livekitWsUrl) {
|
|
482
|
+
return __async(this, null, function* () {
|
|
483
|
+
console.debug("[Reactor] Connecting to machine room...");
|
|
484
|
+
try {
|
|
485
|
+
this.machineClient = new GPUMachineClient(livekitJwtToken, livekitWsUrl);
|
|
486
|
+
this.machineClient.on("application", (message) => {
|
|
487
|
+
this.emit("newMessage", message);
|
|
488
|
+
});
|
|
489
|
+
this.machineClient.on(
|
|
490
|
+
"statusChanged",
|
|
491
|
+
(status) => {
|
|
492
|
+
switch (status) {
|
|
493
|
+
case "connected":
|
|
494
|
+
this.setStatus("ready");
|
|
495
|
+
break;
|
|
496
|
+
case "disconnected":
|
|
497
|
+
this.disconnect();
|
|
498
|
+
break;
|
|
499
|
+
case "error":
|
|
500
|
+
this.createError(
|
|
501
|
+
"GPU_CONNECTION_ERROR",
|
|
502
|
+
"GPU machine connection failed",
|
|
503
|
+
"gpu",
|
|
504
|
+
true
|
|
505
|
+
);
|
|
506
|
+
this.disconnect();
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
);
|
|
511
|
+
this.machineClient.on("fps", (fps) => {
|
|
512
|
+
this.emit("fps", fps);
|
|
513
|
+
});
|
|
514
|
+
this.machineClient.on("streamChanged", (videoTrack) => {
|
|
515
|
+
this.emit("streamChanged", videoTrack);
|
|
516
|
+
});
|
|
517
|
+
console.debug("[Reactor] About to connect to machine");
|
|
518
|
+
yield this.machineClient.connect();
|
|
519
|
+
} catch (error) {
|
|
520
|
+
throw error;
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Connects to the coordinator and waits for a GPU to be assigned.
|
|
526
|
+
* Once a GPU is assigned, the Reactor will connect to the gpu machine via LiveKit.
|
|
527
|
+
*/
|
|
528
|
+
connect() {
|
|
529
|
+
return __async(this, null, function* () {
|
|
530
|
+
console.debug("[Reactor] Connecting, status:", this.status);
|
|
531
|
+
if (this.status !== "disconnected")
|
|
532
|
+
throw new Error("Already connected or connecting");
|
|
533
|
+
if (this.directConnection) {
|
|
534
|
+
return this.connectToGPUMachine(
|
|
535
|
+
this.directConnection.livekitJwtToken,
|
|
536
|
+
this.directConnection.livekitWsUrl
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
this.setStatus("connecting");
|
|
540
|
+
try {
|
|
541
|
+
console.debug(
|
|
542
|
+
"[Reactor] Connecting to coordinator with authenticated URL"
|
|
543
|
+
);
|
|
544
|
+
this.coordinatorClient = new CoordinatorClient({
|
|
545
|
+
wsUrl: this.coordinatorUrl,
|
|
546
|
+
jwtToken: this.jwtToken,
|
|
547
|
+
insecureApiKey: this.insecureApiKey,
|
|
548
|
+
modelName: this.modelName,
|
|
549
|
+
modelVersion: this.modelVersion
|
|
550
|
+
});
|
|
551
|
+
this.coordinatorClient.on(
|
|
552
|
+
"gpu-machine-assigned",
|
|
553
|
+
(assignmentData) => __async(this, null, function* () {
|
|
554
|
+
console.debug(
|
|
555
|
+
"[Reactor] GPU machine assigned by coordinator:",
|
|
556
|
+
assignmentData
|
|
557
|
+
);
|
|
558
|
+
try {
|
|
559
|
+
yield this.connectToGPUMachine(
|
|
560
|
+
assignmentData.livekitJwtToken,
|
|
561
|
+
assignmentData.livekitWsUrl
|
|
562
|
+
);
|
|
563
|
+
} catch (error) {
|
|
564
|
+
console.error("[Reactor] Failed to connect to GPU machine:", error);
|
|
565
|
+
this.createError(
|
|
566
|
+
"GPU_CONNECTION_FAILED",
|
|
567
|
+
`Failed to connect to GPU machine: ${error}`,
|
|
568
|
+
"gpu",
|
|
569
|
+
true
|
|
570
|
+
);
|
|
571
|
+
this.disconnect();
|
|
572
|
+
}
|
|
573
|
+
})
|
|
574
|
+
);
|
|
575
|
+
this.coordinatorClient.on(
|
|
576
|
+
"waiting-info",
|
|
577
|
+
(waitingData) => {
|
|
578
|
+
console.debug("[Reactor] Waiting info update received:", waitingData);
|
|
579
|
+
this.setWaitingInfo(__spreadValues(__spreadValues({}, this.waitingInfo), waitingData));
|
|
580
|
+
}
|
|
581
|
+
);
|
|
582
|
+
this.coordinatorClient.on(
|
|
583
|
+
"statusChanged",
|
|
584
|
+
(newStatus) => {
|
|
585
|
+
switch (newStatus) {
|
|
586
|
+
case "connected":
|
|
587
|
+
this.setStatus("waiting");
|
|
588
|
+
this.setWaitingInfo({
|
|
589
|
+
position: void 0,
|
|
590
|
+
estimatedWaitTime: void 0,
|
|
591
|
+
averageWaitTime: void 0
|
|
592
|
+
});
|
|
593
|
+
break;
|
|
594
|
+
case "disconnected":
|
|
595
|
+
this.disconnect();
|
|
596
|
+
break;
|
|
597
|
+
case "error":
|
|
598
|
+
this.createError(
|
|
599
|
+
"COORDINATOR_CONNECTION_ERROR",
|
|
600
|
+
"Coordinator connection failed",
|
|
601
|
+
"coordinator",
|
|
602
|
+
true
|
|
603
|
+
);
|
|
604
|
+
this.disconnect();
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
);
|
|
609
|
+
yield this.coordinatorClient.connect();
|
|
610
|
+
} catch (error) {
|
|
611
|
+
console.error("[Reactor] Authentication failed:", error);
|
|
612
|
+
this.createError(
|
|
613
|
+
"AUTHENTICATION_FAILED",
|
|
614
|
+
`Authentication failed: ${error}`,
|
|
615
|
+
"coordinator",
|
|
616
|
+
true
|
|
617
|
+
);
|
|
618
|
+
this.setStatus("disconnected");
|
|
619
|
+
throw error;
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Disconnects from the coordinator and the gpu machine.
|
|
625
|
+
* Ensures cleanup completes even if individual disconnections fail.
|
|
626
|
+
*/
|
|
627
|
+
disconnect() {
|
|
628
|
+
return __async(this, null, function* () {
|
|
629
|
+
if (this.status === "disconnected") return;
|
|
630
|
+
if (this.coordinatorClient) {
|
|
631
|
+
try {
|
|
632
|
+
this.coordinatorClient.disconnect();
|
|
633
|
+
} catch (error) {
|
|
634
|
+
console.error("[Reactor] Error disconnecting from coordinator:", error);
|
|
635
|
+
}
|
|
636
|
+
this.coordinatorClient = void 0;
|
|
637
|
+
}
|
|
638
|
+
if (this.machineClient) {
|
|
639
|
+
try {
|
|
640
|
+
yield this.machineClient.disconnect();
|
|
641
|
+
} catch (error) {
|
|
642
|
+
console.error("[Reactor] Error disconnecting from GPU machine:", error);
|
|
643
|
+
}
|
|
644
|
+
this.machineClient = void 0;
|
|
645
|
+
}
|
|
646
|
+
this.setStatus("disconnected");
|
|
647
|
+
this.waitingInfo = void 0;
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
setStatus(newStatus) {
|
|
651
|
+
console.debug("[Reactor] Setting status:", newStatus, "from", this.status);
|
|
652
|
+
if (this.status !== newStatus) {
|
|
653
|
+
this.status = newStatus;
|
|
654
|
+
this.emit("statusChanged", newStatus);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
setWaitingInfo(newWaitingInfo) {
|
|
658
|
+
console.debug(
|
|
659
|
+
"[Reactor] Setting waiting info:",
|
|
660
|
+
newWaitingInfo,
|
|
661
|
+
"from",
|
|
662
|
+
this.waitingInfo
|
|
663
|
+
);
|
|
664
|
+
if (this.waitingInfo !== newWaitingInfo) {
|
|
665
|
+
this.waitingInfo = newWaitingInfo;
|
|
666
|
+
this.emit("waitingInfoChanged", newWaitingInfo);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
getStatus() {
|
|
670
|
+
return this.status;
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Get the current state including status, error, and waiting info
|
|
674
|
+
*/
|
|
675
|
+
getState() {
|
|
676
|
+
return {
|
|
677
|
+
status: this.status,
|
|
678
|
+
waitingInfo: this.waitingInfo,
|
|
679
|
+
lastError: this.lastError
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Get waiting information when status is 'waiting'
|
|
684
|
+
*/
|
|
685
|
+
getWaitingInfo() {
|
|
686
|
+
return this.waitingInfo;
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Get the last error that occurred
|
|
690
|
+
*/
|
|
691
|
+
getLastError() {
|
|
692
|
+
return this.lastError;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Create and store an error
|
|
696
|
+
*/
|
|
697
|
+
createError(code, message, component, recoverable, retryAfter) {
|
|
698
|
+
this.lastError = {
|
|
699
|
+
code,
|
|
700
|
+
message,
|
|
701
|
+
timestamp: Date.now(),
|
|
702
|
+
recoverable,
|
|
703
|
+
component,
|
|
704
|
+
retryAfter
|
|
705
|
+
};
|
|
706
|
+
this.emit("error", this.lastError);
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
// src/react/ReactorProvider.tsx
|
|
711
|
+
var import_react3 = require("react");
|
|
712
|
+
|
|
713
|
+
// src/core/store.ts
|
|
714
|
+
var import_react = require("zustand/react");
|
|
715
|
+
var import_react2 = require("react");
|
|
716
|
+
var ReactorContext = (0, import_react2.createContext)(
|
|
717
|
+
void 0
|
|
718
|
+
);
|
|
719
|
+
var defaultInitState = {
|
|
720
|
+
status: "disconnected",
|
|
721
|
+
videoTrack: null,
|
|
722
|
+
fps: void 0,
|
|
723
|
+
waitingInfo: void 0,
|
|
724
|
+
lastError: void 0
|
|
725
|
+
};
|
|
726
|
+
var initReactorStore = (props) => {
|
|
727
|
+
return __spreadValues(__spreadValues({}, defaultInitState), props);
|
|
728
|
+
};
|
|
729
|
+
var createReactorStore = (initProps, publicState = defaultInitState) => {
|
|
730
|
+
console.debug("[ReactorStore] Creating store", {
|
|
731
|
+
coordinatorUrl: initProps.coordinatorUrl,
|
|
732
|
+
initialState: publicState
|
|
733
|
+
});
|
|
734
|
+
return (0, import_react.create)()((set, get) => {
|
|
735
|
+
const reactor = new Reactor(initProps);
|
|
736
|
+
console.debug("[ReactorStore] Setting up event listeners");
|
|
737
|
+
reactor.on("statusChanged", (newStatus) => {
|
|
738
|
+
console.debug("[ReactorStore] Status changed", {
|
|
739
|
+
oldStatus: get().status,
|
|
740
|
+
newStatus
|
|
741
|
+
});
|
|
742
|
+
set({ status: newStatus });
|
|
743
|
+
});
|
|
744
|
+
reactor.on("waitingInfoChanged", (newWaitingInfo) => {
|
|
745
|
+
console.debug("[ReactorStore] Waiting info changed", {
|
|
746
|
+
oldWaitingInfo: get().waitingInfo,
|
|
747
|
+
newWaitingInfo
|
|
748
|
+
});
|
|
749
|
+
set({ waitingInfo: newWaitingInfo });
|
|
750
|
+
});
|
|
751
|
+
reactor.on("streamChanged", (videoTrack) => {
|
|
752
|
+
console.debug("[ReactorStore] Stream changed", {
|
|
753
|
+
hasVideoTrack: !!videoTrack,
|
|
754
|
+
videoTrackKind: videoTrack == null ? void 0 : videoTrack.kind,
|
|
755
|
+
videoTrackSid: videoTrack == null ? void 0 : videoTrack.sid
|
|
756
|
+
});
|
|
757
|
+
set({ videoTrack });
|
|
758
|
+
});
|
|
759
|
+
reactor.on("fps", (fps) => {
|
|
760
|
+
console.debug("[ReactorStore] FPS updated", { fps });
|
|
761
|
+
set({ fps });
|
|
762
|
+
});
|
|
763
|
+
reactor.on("error", (error) => {
|
|
764
|
+
console.debug("[ReactorStore] Error occurred", error);
|
|
765
|
+
set({ lastError: error });
|
|
766
|
+
});
|
|
767
|
+
return __spreadProps(__spreadValues({}, publicState), {
|
|
768
|
+
internal: { reactor },
|
|
769
|
+
// actions
|
|
770
|
+
onMessage: (handler) => {
|
|
771
|
+
console.debug("[ReactorStore] Registering message handler");
|
|
772
|
+
get().internal.reactor.on("newMessage", handler);
|
|
773
|
+
return () => {
|
|
774
|
+
console.debug("[ReactorStore] Cleaning up message handler");
|
|
775
|
+
get().internal.reactor.off("newMessage", handler);
|
|
776
|
+
};
|
|
777
|
+
},
|
|
778
|
+
sendMessage: (mess) => {
|
|
779
|
+
console.debug("[ReactorStore] Sending message", { message: mess });
|
|
780
|
+
try {
|
|
781
|
+
get().internal.reactor.sendMessage(mess);
|
|
782
|
+
console.debug("[ReactorStore] Message sent successfully");
|
|
783
|
+
} catch (error) {
|
|
784
|
+
console.error("[ReactorStore] Failed to send message:", error);
|
|
785
|
+
throw error;
|
|
786
|
+
}
|
|
787
|
+
},
|
|
788
|
+
connect: () => __async(null, null, function* () {
|
|
789
|
+
console.debug("[ReactorStore] Connect called");
|
|
790
|
+
try {
|
|
791
|
+
yield get().internal.reactor.connect();
|
|
792
|
+
console.debug("[ReactorStore] Connect completed successfully");
|
|
793
|
+
} catch (error) {
|
|
794
|
+
console.error("[ReactorStore] Connect failed:", error);
|
|
795
|
+
throw error;
|
|
796
|
+
}
|
|
797
|
+
}),
|
|
798
|
+
disconnect: () => __async(null, null, function* () {
|
|
799
|
+
console.debug("[ReactorStore] Disconnect called", {
|
|
800
|
+
currentStatus: get().status
|
|
801
|
+
});
|
|
802
|
+
try {
|
|
803
|
+
yield get().internal.reactor.disconnect();
|
|
804
|
+
console.debug("[ReactorStore] Disconnect completed successfully");
|
|
805
|
+
} catch (error) {
|
|
806
|
+
console.error("[ReactorStore] Disconnect failed:", error);
|
|
807
|
+
throw error;
|
|
808
|
+
}
|
|
809
|
+
})
|
|
810
|
+
});
|
|
811
|
+
});
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
// src/react/ReactorProvider.tsx
|
|
815
|
+
var import_zustand = require("zustand");
|
|
816
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
817
|
+
function ReactorProvider(_a) {
|
|
818
|
+
var _b = _a, {
|
|
819
|
+
children,
|
|
820
|
+
autoConnect = true
|
|
821
|
+
} = _b, props = __objRest(_b, [
|
|
822
|
+
"children",
|
|
823
|
+
"autoConnect"
|
|
824
|
+
]);
|
|
825
|
+
const storeRef = (0, import_react3.useRef)(void 0);
|
|
826
|
+
const firstRender = (0, import_react3.useRef)(true);
|
|
827
|
+
function attemptAutoConnect() {
|
|
828
|
+
var _a2, _b2;
|
|
829
|
+
const status = (_a2 = storeRef.current) == null ? void 0 : _a2.getState().status;
|
|
830
|
+
if (autoConnect && status === "disconnected") {
|
|
831
|
+
console.debug("[ReactorProvider] Starting autoconnect...");
|
|
832
|
+
(_b2 = storeRef.current) == null ? void 0 : _b2.getState().connect().then(() => {
|
|
833
|
+
console.debug("[ReactorProvider] Autoconnect successful");
|
|
834
|
+
}).catch((error) => {
|
|
835
|
+
console.error("[ReactorProvider] Failed to autoconnect:", error);
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
if (storeRef.current === void 0) {
|
|
840
|
+
console.debug("[ReactorProvider] Creating new reactor store");
|
|
841
|
+
storeRef.current = createReactorStore(initReactorStore(props));
|
|
842
|
+
console.debug("[ReactorProvider] Reactor store created successfully");
|
|
843
|
+
attemptAutoConnect();
|
|
844
|
+
}
|
|
845
|
+
(0, import_react3.useEffect)(() => {
|
|
846
|
+
if (firstRender.current) {
|
|
847
|
+
firstRender.current = false;
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
console.debug("[ReactorProvider] Updating reactor store");
|
|
851
|
+
storeRef.current = createReactorStore(initReactorStore(props));
|
|
852
|
+
console.debug("[ReactorProvider] Reactor store updated successfully");
|
|
853
|
+
attemptAutoConnect();
|
|
854
|
+
}, [props, autoConnect]);
|
|
855
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ReactorContext.Provider, { value: storeRef.current, children });
|
|
856
|
+
}
|
|
857
|
+
function useReactorStore(selector) {
|
|
858
|
+
const ctx = (0, import_react3.useContext)(ReactorContext);
|
|
859
|
+
if (!ctx) {
|
|
860
|
+
throw new Error("useReactor must be used within a ReactorProvider");
|
|
861
|
+
}
|
|
862
|
+
return (0, import_zustand.useStore)(ctx, selector);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// src/react/hooks.ts
|
|
866
|
+
var import_shallow = require("zustand/shallow");
|
|
867
|
+
var import_react4 = require("react");
|
|
868
|
+
function useReactor(selector) {
|
|
869
|
+
return useReactorStore((0, import_shallow.useShallow)(selector));
|
|
870
|
+
}
|
|
871
|
+
function useReactorMessage(handler) {
|
|
872
|
+
const reactor = useReactor((state) => state.internal.reactor);
|
|
873
|
+
const handlerRef = (0, import_react4.useRef)(handler);
|
|
874
|
+
(0, import_react4.useEffect)(() => {
|
|
875
|
+
handlerRef.current = handler;
|
|
876
|
+
}, [handler]);
|
|
877
|
+
(0, import_react4.useEffect)(() => {
|
|
878
|
+
console.debug("[useReactorMessage] Setting up message subscription");
|
|
879
|
+
const stableHandler = (message) => {
|
|
880
|
+
console.debug("[useReactorMessage] Message received", { message });
|
|
881
|
+
handlerRef.current(message);
|
|
882
|
+
};
|
|
883
|
+
reactor.on("newMessage", stableHandler);
|
|
884
|
+
console.debug("[useReactorMessage] Message handler registered");
|
|
885
|
+
return () => {
|
|
886
|
+
console.debug("[useReactorMessage] Cleaning up message subscription");
|
|
887
|
+
reactor.off("newMessage", stableHandler);
|
|
888
|
+
};
|
|
889
|
+
}, [reactor]);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// src/react/ReactorView.tsx
|
|
893
|
+
var import_react5 = require("react");
|
|
894
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
895
|
+
function ReactorView({
|
|
896
|
+
width,
|
|
897
|
+
height,
|
|
898
|
+
className,
|
|
899
|
+
style
|
|
900
|
+
}) {
|
|
901
|
+
const { videoTrack, status } = useReactor((state) => ({
|
|
902
|
+
videoTrack: state.videoTrack,
|
|
903
|
+
status: state.status
|
|
904
|
+
}));
|
|
905
|
+
const videoRef = (0, import_react5.useRef)(null);
|
|
906
|
+
console.debug("[ReactorView] Render", {
|
|
907
|
+
hasVideoTrack: !!videoTrack,
|
|
908
|
+
status,
|
|
909
|
+
hasVideoElement: !!videoRef.current
|
|
910
|
+
});
|
|
911
|
+
(0, import_react5.useEffect)(() => {
|
|
912
|
+
console.debug("[ReactorView] Video track effect triggered", {
|
|
913
|
+
hasVideoElement: !!videoRef.current,
|
|
914
|
+
hasVideoTrack: !!videoTrack,
|
|
915
|
+
videoTrackKind: videoTrack == null ? void 0 : videoTrack.kind
|
|
916
|
+
});
|
|
917
|
+
if (videoRef.current && videoTrack) {
|
|
918
|
+
console.debug("[ReactorView] Attaching video track to element");
|
|
919
|
+
try {
|
|
920
|
+
videoTrack.attach(videoRef.current);
|
|
921
|
+
console.debug("[ReactorView] Video track attached successfully");
|
|
922
|
+
} catch (error) {
|
|
923
|
+
console.error("[ReactorView] Failed to attach video track:", error);
|
|
924
|
+
}
|
|
925
|
+
return () => {
|
|
926
|
+
console.debug("[ReactorView] Detaching video track from element");
|
|
927
|
+
if (videoRef.current) {
|
|
928
|
+
try {
|
|
929
|
+
videoTrack.detach(videoRef.current);
|
|
930
|
+
console.debug("[ReactorView] Video track detached successfully");
|
|
931
|
+
} catch (error) {
|
|
932
|
+
console.error("[ReactorView] Failed to detach video track:", error);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
} else {
|
|
937
|
+
console.debug("[ReactorView] No video track or element to attach");
|
|
938
|
+
}
|
|
939
|
+
}, [videoTrack]);
|
|
940
|
+
const showPlaceholder = !videoTrack;
|
|
941
|
+
console.debug("[ReactorView] Placeholder state", { showPlaceholder, status });
|
|
942
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
943
|
+
"div",
|
|
944
|
+
{
|
|
945
|
+
style: __spreadValues(__spreadValues(__spreadValues({
|
|
946
|
+
position: "relative",
|
|
947
|
+
background: "#000"
|
|
948
|
+
}, width && { width }), height && { height }), style),
|
|
949
|
+
className,
|
|
950
|
+
children: [
|
|
951
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
952
|
+
"video",
|
|
953
|
+
{
|
|
954
|
+
ref: videoRef,
|
|
955
|
+
style: {
|
|
956
|
+
width: "100%",
|
|
957
|
+
height: "100%",
|
|
958
|
+
objectFit: "contain",
|
|
959
|
+
display: showPlaceholder ? "none" : "block"
|
|
960
|
+
},
|
|
961
|
+
muted: true,
|
|
962
|
+
playsInline: true
|
|
963
|
+
}
|
|
964
|
+
),
|
|
965
|
+
showPlaceholder && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
966
|
+
"div",
|
|
967
|
+
{
|
|
968
|
+
style: {
|
|
969
|
+
position: "absolute",
|
|
970
|
+
top: 0,
|
|
971
|
+
left: 0,
|
|
972
|
+
width: "100%",
|
|
973
|
+
height: "100%",
|
|
974
|
+
color: "#fff",
|
|
975
|
+
display: "flex",
|
|
976
|
+
alignItems: "center",
|
|
977
|
+
justifyContent: "center",
|
|
978
|
+
fontSize: "16px",
|
|
979
|
+
fontFamily: "monospace",
|
|
980
|
+
textAlign: "center",
|
|
981
|
+
padding: "20px",
|
|
982
|
+
boxSizing: "border-box"
|
|
983
|
+
},
|
|
984
|
+
children: status
|
|
985
|
+
}
|
|
986
|
+
)
|
|
987
|
+
]
|
|
988
|
+
}
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
992
|
+
0 && (module.exports = {
|
|
993
|
+
Reactor,
|
|
994
|
+
ReactorProvider,
|
|
995
|
+
ReactorView,
|
|
996
|
+
useReactor,
|
|
997
|
+
useReactorMessage,
|
|
998
|
+
useReactorStore
|
|
999
|
+
});
|
|
1000
|
+
//# sourceMappingURL=index.js.map
|