@reactor-team/js-sdk 1.0.6 → 1.0.8

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/src/core/store.ts DELETED
@@ -1,163 +0,0 @@
1
- import { StoreApi } from "zustand";
2
- import type { ReactorStatus, ReactorWaitingInfo, ReactorError } from "../types";
3
- import { Reactor, type Options as ReactorOptions } from "./Reactor";
4
- import { create } from "zustand/react";
5
- import { createContext } from "react";
6
- import type { RemoteVideoTrack } from "livekit-client";
7
-
8
- export type ReactorStoreApi = ReturnType<typeof createReactorStore>;
9
-
10
- export interface ReactorState {
11
- status: ReactorStatus;
12
- videoTrack: RemoteVideoTrack | null;
13
- //These are the machine FPS. The machine internally reports to the client the rate at which the frames are
14
- //being generated. This can be useful for the client to calcolate at which rate to run commands.
15
- fps?: number;
16
- waitingInfo?: ReactorWaitingInfo;
17
- lastError?: ReactorError;
18
- }
19
-
20
- export interface ReactorActions {
21
- sendMessage(message: any): void;
22
- connect(): Promise<void>;
23
- disconnect(): Promise<void>;
24
- }
25
-
26
- // Internal state not exposed to components
27
- interface ReactorInternalState {
28
- reactor: Reactor;
29
- }
30
-
31
- export const ReactorContext = createContext<ReactorStoreApi | undefined>(
32
- undefined
33
- );
34
-
35
- export type ReactorStore = ReactorState &
36
- ReactorActions & {
37
- internal: ReactorInternalState;
38
- };
39
-
40
- export const defaultInitState: ReactorState = {
41
- status: "disconnected",
42
- videoTrack: null,
43
- fps: undefined,
44
- waitingInfo: undefined,
45
- lastError: undefined,
46
- };
47
-
48
- export interface ReactorInitializationProps extends ReactorOptions {}
49
-
50
- export const initReactorStore = (
51
- props: ReactorInitializationProps
52
- ): ReactorState & ReactorInitializationProps => {
53
- return {
54
- ...defaultInitState,
55
- // These are only used for dev initialization, not exposed in the store
56
- ...props,
57
- };
58
- };
59
-
60
- export const createReactorStore = (
61
- initProps: ReactorInitializationProps,
62
- publicState: ReactorState = defaultInitState
63
- ): StoreApi<ReactorStore> => {
64
- console.debug("[ReactorStore] Creating store", {
65
- coordinatorUrl: initProps.coordinatorUrl,
66
- initialState: publicState,
67
- });
68
-
69
- return create<ReactorStore>()((set, get) => {
70
- const reactor = new Reactor(initProps);
71
-
72
- console.debug("[ReactorStore] Setting up event listeners");
73
-
74
- reactor.on("statusChanged", (newStatus: ReactorStatus) => {
75
- console.debug("[ReactorStore] Status changed", {
76
- oldStatus: get().status,
77
- newStatus,
78
- });
79
- set({ status: newStatus });
80
- });
81
-
82
- reactor.on("waitingInfoChanged", (newWaitingInfo: ReactorWaitingInfo) => {
83
- console.debug("[ReactorStore] Waiting info changed", {
84
- oldWaitingInfo: get().waitingInfo,
85
- newWaitingInfo,
86
- });
87
- set({ waitingInfo: newWaitingInfo });
88
- });
89
-
90
- reactor.on("streamChanged", (videoTrack: RemoteVideoTrack | null) => {
91
- console.debug("[ReactorStore] Stream changed", {
92
- hasVideoTrack: !!videoTrack,
93
- videoTrackKind: videoTrack?.kind,
94
- videoTrackSid: videoTrack?.sid,
95
- });
96
- set({ videoTrack: videoTrack });
97
- });
98
-
99
- reactor.on("fps", (fps: number) => {
100
- console.debug("[ReactorStore] FPS updated", { fps });
101
- set({ fps: fps });
102
- });
103
-
104
- reactor.on("error", (error: ReactorError) => {
105
- console.debug("[ReactorStore] Error occurred", error);
106
- set({ lastError: error });
107
- });
108
-
109
- return {
110
- ...publicState,
111
- internal: { reactor },
112
-
113
- // actions
114
- onMessage: (handler: (message: any) => void) => {
115
- console.debug("[ReactorStore] Registering message handler");
116
-
117
- // Simply register the handler
118
- get().internal.reactor.on("newMessage", handler);
119
-
120
- // Return a cleanup function that can be called to unregister
121
- return () => {
122
- console.debug("[ReactorStore] Cleaning up message handler");
123
- get().internal.reactor.off("newMessage", handler);
124
- };
125
- },
126
- sendMessage: (mess: any) => {
127
- console.debug("[ReactorStore] Sending message", { message: mess });
128
-
129
- try {
130
- get().internal.reactor.sendMessage(mess);
131
- console.debug("[ReactorStore] Message sent successfully");
132
- } catch (error) {
133
- console.error("[ReactorStore] Failed to send message:", error);
134
- throw error;
135
- }
136
- },
137
- connect: async () => {
138
- console.debug("[ReactorStore] Connect called");
139
-
140
- try {
141
- await get().internal.reactor.connect();
142
- console.debug("[ReactorStore] Connect completed successfully");
143
- } catch (error) {
144
- console.error("[ReactorStore] Connect failed:", error);
145
- throw error;
146
- }
147
- },
148
- disconnect: async () => {
149
- console.debug("[ReactorStore] Disconnect called", {
150
- currentStatus: get().status,
151
- });
152
-
153
- try {
154
- await get().internal.reactor.disconnect();
155
- console.debug("[ReactorStore] Disconnect completed successfully");
156
- } catch (error) {
157
- console.error("[ReactorStore] Disconnect failed:", error);
158
- throw error;
159
- }
160
- },
161
- };
162
- });
163
- };
package/src/core/types.ts DELETED
@@ -1,99 +0,0 @@
1
- /**
2
- * Internal types for the Reactor SDK.
3
- */
4
-
5
- import { z } from "zod";
6
-
7
- // Base message schemas corresponding to Python Pydantic models
8
-
9
- export const ApplicationMessageSchema = z.object({
10
- type: z.literal("application"),
11
- data: z.any(), // Can be any JSON-serializable data
12
- });
13
-
14
- // Internal message that notifies the client of the current push rate of the machine
15
- export const FPSMessageSchema = z.object({
16
- type: z.literal("fps"),
17
- data: z.number(),
18
- });
19
-
20
- export const GPUMachineReceiveMessageSchema = z.discriminatedUnion("type", [
21
- ApplicationMessageSchema,
22
- FPSMessageSchema,
23
- ]);
24
-
25
- export const WelcomeMessageSchema = z.object({
26
- type: z.literal("welcome"),
27
- data: z.record(z.string(), z.any()),
28
- });
29
-
30
- export const GPUMachineAssignmentDataSchema = z.object({
31
- livekitWsUrl: z.string(),
32
- livekitJwtToken: z.string(),
33
- });
34
-
35
- export const GPUMachineAssignmentMessageSchema = z.object({
36
- type: z.literal("gpu-machine-assigned"),
37
- data: GPUMachineAssignmentDataSchema,
38
- });
39
-
40
- export const EchoMessageSchema = z.object({
41
- type: z.literal("echo"),
42
- data: z.record(z.string(), z.any()),
43
- });
44
-
45
- export const WaitingInfoDataSchema = z.object({
46
- position: z.number().optional(),
47
- estimatedWaitTime: z.number().optional(),
48
- averageWaitTime: z.number().optional(),
49
- });
50
-
51
- export const WaitingInfoMessageSchema = z.object({
52
- type: z.literal("waiting-info"),
53
- data: WaitingInfoDataSchema,
54
- });
55
-
56
- export const CoordinatorMessageSchema = z.discriminatedUnion("type", [
57
- WelcomeMessageSchema,
58
- GPUMachineAssignmentMessageSchema,
59
- EchoMessageSchema,
60
- WaitingInfoMessageSchema,
61
- ]);
62
-
63
- export const GPUMachineSendMessageSchema = z.discriminatedUnion("type", [
64
- ApplicationMessageSchema,
65
- ]);
66
-
67
- export const SessionSetupMessageSchema = z.object({
68
- type: z.literal("sessionSetup"),
69
- data: z.object({
70
- modelName: z.string(),
71
- modelVersion: z.string().default("latest"),
72
- }),
73
- });
74
-
75
- // TypeScript types derived from schemas
76
- export type ApplicationMessage = z.infer<typeof ApplicationMessageSchema>;
77
- export type GPUMachineReceiveMessageSchema = z.infer<
78
- typeof GPUMachineReceiveMessageSchema
79
- >;
80
- export type WelcomeMessage = z.infer<typeof WelcomeMessageSchema>;
81
- export type GPUMachineAssignmentData = z.infer<
82
- typeof GPUMachineAssignmentDataSchema
83
- >;
84
- export type GPUMachineAssignmentMessage = z.infer<
85
- typeof GPUMachineAssignmentMessageSchema
86
- >;
87
- export type EchoMessage = z.infer<typeof EchoMessageSchema>;
88
- export type WaitingInfoData = z.infer<typeof WaitingInfoDataSchema>;
89
- export type WaitingInfoMessage = z.infer<typeof WaitingInfoMessageSchema>;
90
- export type CoordinatorMessage = z.infer<typeof CoordinatorMessageSchema>;
91
- export type GPUMachineSendMessage = z.infer<typeof GPUMachineSendMessageSchema>;
92
- export type SessionSetupMessage = z.infer<typeof SessionSetupMessageSchema>;
93
-
94
- // Internal connection status for individual components
95
- export type InternalConnectionStatus =
96
- | "disconnected"
97
- | "connecting"
98
- | "connected"
99
- | "error";
package/src/index.ts DELETED
@@ -1,5 +0,0 @@
1
- export * from "./core/Reactor";
2
- export * from "./react/ReactorProvider";
3
- export * from "./react/ReactorView";
4
- export * from "./react/hooks";
5
- export * from "./types";
@@ -1,86 +0,0 @@
1
- "use client";
2
-
3
- import { ReactNode, useContext, useEffect, useRef } from "react";
4
- import {
5
- createReactorStore,
6
- initReactorStore,
7
- ReactorContext,
8
- ReactorStore,
9
- ReactorStoreApi,
10
- type ReactorInitializationProps,
11
- } from "../core/store";
12
- import { useStore } from "zustand";
13
-
14
- // Provider props
15
- interface ReactorProviderProps extends ReactorInitializationProps {
16
- autoConnect?: boolean;
17
- children: ReactNode;
18
- }
19
-
20
- // tsx component
21
- export function ReactorProvider({
22
- children,
23
- autoConnect = true,
24
- ...props
25
- }: ReactorProviderProps) {
26
- // Stable Reactor instance
27
- const storeRef = useRef<ReactorStoreApi | undefined>(undefined);
28
- const firstRender = useRef(true);
29
-
30
- /**
31
- * Attempts connecting to Reactor, if the autoConnect flag is set.
32
- */
33
- function attemptAutoConnect() {
34
- const status = storeRef.current?.getState().status;
35
-
36
- if (autoConnect && status === "disconnected") {
37
- console.debug("[ReactorProvider] Starting autoconnect...");
38
- storeRef.current
39
- ?.getState()
40
- .connect()
41
- .then(() => {
42
- console.debug("[ReactorProvider] Autoconnect successful");
43
- })
44
- .catch((error) => {
45
- console.error("[ReactorProvider] Failed to autoconnect:", error);
46
- });
47
- }
48
- }
49
-
50
- if (storeRef.current === undefined) {
51
- console.debug("[ReactorProvider] Creating new reactor store");
52
- // We create the store without autoconnecting, to avoid duplicate connections.
53
- // We actually connect when the component is mounted, to be on sync with the react component lifecycle.
54
- storeRef.current = createReactorStore(initReactorStore(props));
55
- console.debug("[ReactorProvider] Reactor store created successfully");
56
- attemptAutoConnect();
57
- }
58
-
59
- useEffect(() => {
60
- if (firstRender.current) {
61
- firstRender.current = false;
62
- return;
63
- }
64
- console.debug("[ReactorProvider] Updating reactor store");
65
- storeRef.current = createReactorStore(initReactorStore(props));
66
- console.debug("[ReactorProvider] Reactor store updated successfully");
67
- attemptAutoConnect();
68
- }, [props, autoConnect]);
69
-
70
- return (
71
- <ReactorContext.Provider value={storeRef.current}>
72
- {children}
73
- </ReactorContext.Provider>
74
- );
75
- }
76
-
77
- export function useReactorStore<T = ReactorStore>(
78
- selector: (state: ReactorStore) => T
79
- ): T {
80
- const ctx = useContext(ReactorContext);
81
- if (!ctx) {
82
- throw new Error("useReactor must be used within a ReactorProvider");
83
- }
84
-
85
- return useStore(ctx, selector);
86
- }
@@ -1,116 +0,0 @@
1
- "use client";
2
-
3
- import { useReactor } from "./hooks";
4
- import { useEffect, useRef } from "react";
5
- import React from "react";
6
-
7
- export interface ReactorViewProps {
8
- width?: number;
9
- height?: number;
10
- className?: string;
11
- style?: React.CSSProperties;
12
- }
13
-
14
- export function ReactorView({
15
- width,
16
- height,
17
- className,
18
- style,
19
- }: ReactorViewProps) {
20
- const { videoTrack, status } = useReactor((state) => ({
21
- videoTrack: state.videoTrack,
22
- status: state.status,
23
- }));
24
-
25
- const videoRef = useRef<HTMLVideoElement>(null);
26
-
27
- console.debug("[ReactorView] Render", {
28
- hasVideoTrack: !!videoTrack,
29
- status,
30
- hasVideoElement: !!videoRef.current,
31
- });
32
-
33
- useEffect(() => {
34
- console.debug("[ReactorView] Video track effect triggered", {
35
- hasVideoElement: !!videoRef.current,
36
- hasVideoTrack: !!videoTrack,
37
- videoTrackKind: videoTrack?.kind,
38
- });
39
-
40
- if (videoRef.current && videoTrack) {
41
- console.debug("[ReactorView] Attaching video track to element");
42
- try {
43
- // Attach the LiveKit track to the video element
44
- videoTrack.attach(videoRef.current);
45
- console.debug("[ReactorView] Video track attached successfully");
46
- } catch (error) {
47
- console.error("[ReactorView] Failed to attach video track:", error);
48
- }
49
-
50
- // Cleanup: detach when track changes or component unmounts
51
- return () => {
52
- console.debug("[ReactorView] Detaching video track from element");
53
- if (videoRef.current) {
54
- try {
55
- videoTrack.detach(videoRef.current);
56
- console.debug("[ReactorView] Video track detached successfully");
57
- } catch (error) {
58
- console.error("[ReactorView] Failed to detach video track:", error);
59
- }
60
- }
61
- };
62
- } else {
63
- console.debug("[ReactorView] No video track or element to attach");
64
- }
65
- }, [videoTrack]);
66
-
67
- const showPlaceholder = !videoTrack;
68
- console.debug("[ReactorView] Placeholder state", { showPlaceholder, status });
69
-
70
- return (
71
- <div
72
- style={{
73
- position: "relative",
74
- background: "#000",
75
- ...(width && { width }),
76
- ...(height && { height }),
77
- ...style,
78
- }}
79
- className={className}
80
- >
81
- <video
82
- ref={videoRef}
83
- style={{
84
- width: "100%",
85
- height: "100%",
86
- objectFit: "contain",
87
- display: showPlaceholder ? "none" : "block",
88
- }}
89
- muted
90
- playsInline
91
- />
92
- {showPlaceholder && (
93
- <div
94
- style={{
95
- position: "absolute",
96
- top: 0,
97
- left: 0,
98
- width: "100%",
99
- height: "100%",
100
- color: "#fff",
101
- display: "flex",
102
- alignItems: "center",
103
- justifyContent: "center",
104
- fontSize: "16px",
105
- fontFamily: "monospace",
106
- textAlign: "center",
107
- padding: "20px",
108
- boxSizing: "border-box",
109
- }}
110
- >
111
- {status}
112
- </div>
113
- )}
114
- </div>
115
- );
116
- }
@@ -1,50 +0,0 @@
1
- import { useReactorStore } from "./ReactorProvider";
2
- import type { ReactorStore } from "../core/store";
3
- import { useShallow } from "zustand/shallow";
4
- import { useEffect, useRef } from "react";
5
-
6
- /**
7
- * Generic hook for accessing selected parts of the Reactor store.
8
- *
9
- * @param selector - A function that selects part of the store state.
10
- * @returns The selected slice from the store.
11
- */
12
- export function useReactor<T>(selector: (state: ReactorStore) => T): T {
13
- return useReactorStore(useShallow(selector));
14
- }
15
-
16
- /**
17
- * Hook for handling message subscriptions with proper React lifecycle management.
18
- *
19
- * @param handler - The message handler function
20
- */
21
- export function useReactorMessage(handler: (message: any) => void): void {
22
- const reactor = useReactor((state) => state.internal.reactor);
23
- const handlerRef = useRef(handler);
24
-
25
- // Update the ref when handler changes
26
- useEffect(() => {
27
- handlerRef.current = handler;
28
- }, [handler]);
29
-
30
- useEffect(() => {
31
- console.debug("[useReactorMessage] Setting up message subscription");
32
-
33
- // Create a stable handler that calls the current ref
34
- const stableHandler = (message: any) => {
35
- console.debug("[useReactorMessage] Message received", { message });
36
- handlerRef.current(message);
37
- };
38
-
39
- // Register the handler and get the cleanup function
40
- reactor.on("newMessage", stableHandler);
41
-
42
- console.debug("[useReactorMessage] Message handler registered");
43
-
44
- // Return the cleanup function
45
- return () => {
46
- console.debug("[useReactorMessage] Cleaning up message subscription");
47
- reactor.off("newMessage", stableHandler);
48
- };
49
- }, [reactor]);
50
- }
package/src/types.ts DELETED
@@ -1,37 +0,0 @@
1
- export type ReactorStatus =
2
- | "disconnected" // Not connected to anything
3
- | "connecting" // Establishing connection to coordinator
4
- | "waiting" // Connected to coordinator, waiting for GPU assignment
5
- | "ready"; // Connected to GPU machine, can send/receive messages
6
-
7
- // Queue information when in waiting state
8
- export interface ReactorWaitingInfo {
9
- position?: number; // Position in queue
10
- estimatedWaitTime?: number; // Seconds
11
- averageWaitTime?: number; // Historical data
12
- }
13
-
14
- // Error information
15
- export interface ReactorError {
16
- code: string;
17
- message: string;
18
- timestamp: number;
19
- recoverable: boolean;
20
- component: "coordinator" | "gpu" | "livekit";
21
- retryAfter?: number; // Suggested retry delay in seconds
22
- }
23
-
24
- // Enhanced state with metadata
25
- export interface ReactorState {
26
- status: ReactorStatus;
27
- waitingInfo?: ReactorWaitingInfo; // When status is 'waiting'
28
- lastError?: ReactorError; // Most recent error
29
- }
30
-
31
- export type ReactorEvent =
32
- | "statusChanged" //updates on the reactor status
33
- | "waitingInfoChanged" //updates on the waiting info
34
- | "newMessage" //new messages from the machine (coordinator handled internally)
35
- | "fps" //update of the fps rate of the machine
36
- | "streamChanged" //video stream has changed (LiveKit)
37
- | "error"; //error events with ReactorError details
package/tsconfig.json DELETED
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES6",
4
- "module": "ESNext",
5
- "lib": ["DOM", "ESNext"],
6
- "moduleResolution": "Node",
7
- "declaration": true,
8
- "emitDeclarationOnly": true,
9
- "declarationMap": true,
10
- "outDir": "./dist",
11
- "jsx": "react-jsx",
12
- "esModuleInterop": true,
13
- "skipLibCheck": true,
14
- "forceConsistentCasingInFileNames": true,
15
- "strict": true,
16
- "stripInternal": true
17
- },
18
- "include": ["src", "bin"]
19
- }
package/tsup.config.ts DELETED
@@ -1,15 +0,0 @@
1
- import { defineConfig } from "tsup";
2
-
3
- export default defineConfig({
4
- entry: ["src/index.ts"],
5
- format: ["cjs", "esm"],
6
- dts: {
7
- entry: ["src/index.ts"],
8
- resolve: true,
9
- },
10
- splitting: false,
11
- sourcemap: true,
12
- clean: true,
13
- outDir: "dist",
14
- tsconfig: "tsconfig.json",
15
- });