@reactor-team/js-sdk 1.0.1 → 1.0.2

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.
@@ -0,0 +1,83 @@
1
+ # Reactor Frontend SDK - Imperative LiveKit Example
2
+
3
+ This example demonstrates how to use the Reactor Frontend SDK in an imperative way with pure TypeScript/JavaScript and HTML, connecting to GPU machines via LiveKit.
4
+
5
+ ## Running the Example
6
+
7
+ ### Prerequisites
8
+
9
+ 1. **Build the frontend SDK first:**
10
+
11
+ ```bash
12
+ cd ../../frontend-sdk && pnpm run build
13
+ ```
14
+
15
+ 2. **Start the backend services:**
16
+
17
+ The example requires backend services (coordinator and machine) to be running. Start them using Docker Compose:
18
+
19
+ ```bash
20
+ cd ../../frontend-sdk/example/backend && docker compose up --build
21
+ ```
22
+
23
+ This will start both:
24
+ - The coordinator at `ws://localhost:8080/ws`
25
+ - The machine at `ws://localhost:8081/ws`
26
+
27
+ ### Running the Frontend
28
+
29
+ 1. **Install dependencies:**
30
+
31
+ ```bash
32
+ cd frontend-sdk/frontend-app-example && pnpm install
33
+ ```
34
+
35
+ 2. **Start development server:**
36
+
37
+ ```bash
38
+ pnpm run dev
39
+ ```
40
+
41
+ 3. **Open in browser:**
42
+ Navigate to http://localhost:3002
43
+
44
+ The development server will automatically reload when you make changes to `index.ts` or `index.html`.
45
+
46
+ ## Usage
47
+
48
+ 1. **Connect**: Click the "Connect" button to connect to the coordinator
49
+ 2. **Wait for GPU**: The status will show "queued" while waiting for a GPU machine
50
+ 3. **Video Stream**: Once connected ("running"), you'll see the video stream
51
+ 4. **Send Messages**: Use the slider to send values to the remote machine
52
+ 5. **View Responses**: Messages from the machine will appear below the slider
53
+ 6. **Disconnect**: Click "Disconnect" to close the connection
54
+
55
+ ## Implementation Details
56
+
57
+ The example uses the imperative `Reactor` class from `reactor-client-sdk`:
58
+
59
+ ```typescript
60
+ import { Reactor } from "reactor-client-sdk";
61
+
62
+ const reactor = new Reactor("ws://localhost:8080/ws");
63
+
64
+ // Event listeners
65
+ reactor.on("statusChanged", (status) => {
66
+ /* update UI */
67
+ });
68
+ reactor.on("streamChanged", (videoTrack) => {
69
+ if (videoTrack) {
70
+ videoTrack.attach(videoElement);
71
+ }
72
+ });
73
+ reactor.onMessage((message) => {
74
+ /* display message */
75
+ });
76
+
77
+ // Actions
78
+ await reactor.connect("LIVEKIT_TEMP_TOKEN", "LIVEKIT_GPU_MACHINE_ROOM");
79
+ reactor.sendMessage({ value: 0.5 });
80
+ await reactor.disconnect();
81
+ ```
82
+
83
+ > **Note:** You must provide a valid LiveKit token and GPU machine room when connecting.
@@ -0,0 +1,113 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Reactor Demo - Imperative</title>
7
+ <style>
8
+ body {
9
+ font-family: Arial, sans-serif;
10
+ max-width: 800px;
11
+ margin: 0 auto;
12
+ padding: 20px;
13
+ }
14
+
15
+ #videoStream {
16
+ width: 100%;
17
+ max-width: 640px;
18
+ height: 480px;
19
+ background-color: #f0f0f0;
20
+ border: 1px solid #ccc;
21
+ display: block;
22
+ margin-bottom: 20px;
23
+ }
24
+
25
+ .control-panel {
26
+ border: 1px solid #ccc;
27
+ padding: 15px;
28
+ margin-bottom: 20px;
29
+ }
30
+
31
+ .control-row {
32
+ margin-bottom: 10px;
33
+ }
34
+
35
+ button {
36
+ padding: 8px 16px;
37
+ margin-right: 10px;
38
+ cursor: pointer;
39
+ }
40
+
41
+ button:disabled {
42
+ opacity: 0.5;
43
+ cursor: not-allowed;
44
+ }
45
+
46
+ #valueSlider {
47
+ width: 100%;
48
+ margin: 10px 0;
49
+ }
50
+
51
+ #messageDisplay {
52
+ background-color: #f9f9f9;
53
+ border: 1px solid #ddd;
54
+ padding: 10px;
55
+ margin-top: 10px;
56
+ word-break: break-all;
57
+ }
58
+
59
+ #errorDisplay {
60
+ background-color: #fff5f5;
61
+ border: 1px solid #fed7d7;
62
+ padding: 10px;
63
+ margin-top: 10px;
64
+ display: none;
65
+ }
66
+
67
+ #errorText {
68
+ background-color: #f7fafc;
69
+ border: 1px solid #e2e8f0;
70
+ padding: 8px;
71
+ margin: 0;
72
+ font-family: 'Courier New', monospace;
73
+ font-size: 12px;
74
+ white-space: pre-wrap;
75
+ overflow-x: auto;
76
+ }
77
+ </style>
78
+ </head>
79
+ <body>
80
+ <h1>Reactor Demo - Imperative</h1>
81
+
82
+ <video id="videoStream" autoplay muted></video>
83
+
84
+ <div class="control-panel">
85
+ <h3>Connection</h3>
86
+ <div class="control-row">
87
+ <div>Status: <span id="statusText">idle</span></div>
88
+ </div>
89
+ <div class="control-row">
90
+ <button id="connectBtn">Connect</button>
91
+ <button id="disconnectBtn" disabled>Disconnect</button>
92
+ </div>
93
+ </div>
94
+
95
+ <div class="control-panel">
96
+ <h3>Message Control</h3>
97
+ <div class="control-row">
98
+ <input type="range" id="valueSlider" min="0" max="1" step="0.1" value="0">
99
+ <div>Value: <span id="sliderValue">0</span></div>
100
+ </div>
101
+ <div id="messageDisplay">
102
+ <strong>Latest Message:</strong> <span id="messageText">None</span>
103
+ </div>
104
+ </div>
105
+
106
+ <div id="errorDisplay">
107
+ <h3>Error Details</h3>
108
+ <pre id="errorText"></pre>
109
+ </div>
110
+
111
+ <script type="module" src="/index.ts"></script>
112
+ </body>
113
+ </html>
@@ -0,0 +1,120 @@
1
+ import { Reactor } from "reactor-client-sdk";
2
+
3
+ // Initialize the Reactor with localhost coordinator and API key
4
+ const reactor = new Reactor({
5
+ coordinatorUrl: "ws://localhost:8080/ws",
6
+ insecureApiKey: "PUT_A_KEY_HERE",
7
+ modelName: "test-model",
8
+ });
9
+
10
+ // Get DOM element references
11
+ const videoElement = document.getElementById("videoStream") as HTMLVideoElement;
12
+ const statusText = document.getElementById("statusText") as HTMLSpanElement;
13
+ const connectBtn = document.getElementById("connectBtn") as HTMLButtonElement;
14
+ const disconnectBtn = document.getElementById(
15
+ "disconnectBtn"
16
+ ) as HTMLButtonElement;
17
+ const valueSlider = document.getElementById("valueSlider") as HTMLInputElement;
18
+ const sliderValue = document.getElementById("sliderValue") as HTMLSpanElement;
19
+ const messageText = document.getElementById("messageText") as HTMLSpanElement;
20
+ const errorDisplay = document.getElementById("errorDisplay") as HTMLDivElement;
21
+ const errorText = document.getElementById("errorText") as HTMLPreElement;
22
+
23
+ // Track current slider value
24
+ let currentSliderValue = 0;
25
+
26
+ // Set up SDK event listeners
27
+ reactor.on("statusChanged", (status) => {
28
+ console.log("[App] Status changed:", status);
29
+ statusText.textContent = status;
30
+
31
+ // Only clear error display on successful connections, not on failures
32
+ if (status === "ready" || status === "waiting") {
33
+ errorDisplay.style.display = "none";
34
+ }
35
+
36
+ // Update button states based on status
37
+ switch (status) {
38
+ case "disconnected":
39
+ connectBtn.disabled = false;
40
+ disconnectBtn.disabled = true;
41
+ // Clear message display on disconnect
42
+ messageText.textContent = "None";
43
+ break;
44
+ case "connecting":
45
+ case "waiting":
46
+ connectBtn.disabled = true;
47
+ disconnectBtn.disabled = false;
48
+ break;
49
+ case "ready":
50
+ connectBtn.disabled = true;
51
+ disconnectBtn.disabled = false;
52
+ break;
53
+ }
54
+ });
55
+
56
+ // Listen for error events
57
+ reactor.on("error", (error) => {
58
+ console.error("[App] Reactor error:", error);
59
+
60
+ // Display error JSON on page
61
+ errorText.textContent = JSON.stringify(error, null, 2);
62
+ errorDisplay.style.display = "block";
63
+
64
+ // Also show brief error in message area
65
+ messageText.textContent = `Error: ${error.message}`;
66
+ });
67
+
68
+ reactor.on("streamChanged", (videoTrack) => {
69
+ console.log("[App] Stream changed:", videoTrack);
70
+ if (videoTrack) {
71
+ videoTrack.attach(videoElement);
72
+ } else {
73
+ videoElement.srcObject = null;
74
+ }
75
+ });
76
+
77
+ reactor.on("newMessage", (message) => {
78
+ console.log("[App] Received message:", message);
79
+ messageText.textContent = JSON.stringify(message);
80
+ });
81
+
82
+ // Set up DOM event listeners
83
+ connectBtn.addEventListener("click", async () => {
84
+ try {
85
+ console.log("[App] Connecting...");
86
+ await reactor.connect();
87
+ } catch (error) {
88
+ console.error("[App] Failed to connect:", error);
89
+ }
90
+ });
91
+
92
+ disconnectBtn.addEventListener("click", async () => {
93
+ try {
94
+ console.log("[App] Disconnecting...");
95
+ await reactor.disconnect();
96
+ } catch (error) {
97
+ console.error("[App] Failed to disconnect:", error);
98
+ }
99
+ });
100
+
101
+ valueSlider.addEventListener("input", (event) => {
102
+ const target = event.target as HTMLInputElement;
103
+ currentSliderValue = parseFloat(target.value);
104
+ sliderValue.textContent = currentSliderValue.toFixed(1);
105
+
106
+ // Only send messages if we're in ready state
107
+ if (reactor.getStatus() === "ready") {
108
+ try {
109
+ console.log("[App] Sending message with value:", currentSliderValue);
110
+ reactor.sendMessage({ value: currentSliderValue });
111
+ } catch (error) {
112
+ console.error("[App] Failed to send message:", error);
113
+ }
114
+ }
115
+ });
116
+
117
+ // Initialize slider display
118
+ sliderValue.textContent = currentSliderValue.toFixed(1);
119
+
120
+ console.log("[App] Reactor demo initialized");
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "reactor-impl",
3
+ "module": "index.ts",
4
+ "type": "module",
5
+ "dependencies": {
6
+ "reactor-client-sdk": "link:../../frontend-sdk"
7
+ },
8
+ "devDependencies": {
9
+ "@types/node": "^20.10.0",
10
+ "typescript": "^5.8.3",
11
+ "vite": "^5.0.0"
12
+ },
13
+ "scripts": {
14
+ "dev": "vite --port 3002",
15
+ "build": "vite build",
16
+ "preview": "vite preview --port 3002"
17
+ }
18
+ }