@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.
- package/frontend-app-example/README.md +83 -0
- package/frontend-app-example/index.html +113 -0
- package/frontend-app-example/index.ts +120 -0
- package/frontend-app-example/package.json +18 -0
- package/frontend-app-example/pnpm-lock.yaml +556 -0
- package/frontend-app-example/tsconfig.json +27 -0
- package/package.json +1 -1
- package/src/core/Reactor.ts +2 -3
- package/dist/index.d.mts +0 -151
- package/dist/index.d.ts +0 -151
- package/dist/index.js +0 -1000
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -971
- package/dist/index.mjs.map +0 -1
|
@@ -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
|
+
}
|