Spaces:
Running
Running
debugging the connect error on deployment
Browse files
src/components/control-tray/ControlTray.tsx
CHANGED
|
@@ -84,6 +84,7 @@ function ControlTray({
|
|
| 84 |
|
| 85 |
useEffect(() => {
|
| 86 |
if (!connected && connectButtonRef.current) {
|
|
|
|
| 87 |
connectButtonRef.current.focus();
|
| 88 |
}
|
| 89 |
}, [connected]);
|
|
@@ -91,13 +92,17 @@ function ControlTray({
|
|
| 91 |
// Add iOS volume simulation effect
|
| 92 |
useEffect(() => {
|
| 93 |
if (isIOSDevice && connected && !muted) {
|
|
|
|
| 94 |
const interval = setInterval(() => {
|
| 95 |
// Create a smooth pulsing effect
|
| 96 |
const pulse = (Math.sin(Date.now() / 500) + 1) / 2; // Values between 0 and 1
|
| 97 |
setSimulatedVolume(0.02 + pulse * 0.03); // Small range for subtle effect
|
| 98 |
}, 50);
|
| 99 |
|
| 100 |
-
return () =>
|
|
|
|
|
|
|
|
|
|
| 101 |
}
|
| 102 |
}, [connected, muted, isIOSDevice]);
|
| 103 |
|
|
@@ -110,6 +115,7 @@ function ControlTray({
|
|
| 110 |
|
| 111 |
useEffect(() => {
|
| 112 |
const onData = (base64: string) => {
|
|
|
|
| 113 |
client.sendRealtimeInput([
|
| 114 |
{
|
| 115 |
mimeType: "audio/pcm;rate=16000",
|
|
@@ -119,18 +125,22 @@ function ControlTray({
|
|
| 119 |
};
|
| 120 |
|
| 121 |
if (connected && !muted && audioRecorder) {
|
|
|
|
| 122 |
audioRecorder.on("data", onData).on("volume", setInVolume).start();
|
| 123 |
} else {
|
|
|
|
| 124 |
audioRecorder.stop();
|
| 125 |
}
|
| 126 |
|
| 127 |
return () => {
|
|
|
|
| 128 |
audioRecorder.off("data", onData).off("volume", setInVolume);
|
| 129 |
};
|
| 130 |
}, [connected, client, muted, audioRecorder]);
|
| 131 |
|
| 132 |
useEffect(() => {
|
| 133 |
if (videoRef.current) {
|
|
|
|
| 134 |
videoRef.current.srcObject = activeVideoStream;
|
| 135 |
}
|
| 136 |
|
|
@@ -141,6 +151,7 @@ function ControlTray({
|
|
| 141 |
const canvas = renderCanvasRef.current;
|
| 142 |
|
| 143 |
if (!video || !canvas) {
|
|
|
|
| 144 |
return;
|
| 145 |
}
|
| 146 |
|
|
@@ -151,6 +162,7 @@ function ControlTray({
|
|
| 151 |
ctx.drawImage(videoRef.current, 0, 0, canvas.width, canvas.height);
|
| 152 |
const base64 = canvas.toDataURL("image/jpeg", 1.0);
|
| 153 |
const data = base64.slice(base64.indexOf(",") + 1, Infinity);
|
|
|
|
| 154 |
client.sendRealtimeInput([{ mimeType: "image/jpeg", data }]);
|
| 155 |
}
|
| 156 |
if (connected) {
|
|
@@ -158,9 +170,11 @@ function ControlTray({
|
|
| 158 |
}
|
| 159 |
}
|
| 160 |
if (connected && activeVideoStream !== null) {
|
|
|
|
| 161 |
requestAnimationFrame(sendVideoFrame);
|
| 162 |
}
|
| 163 |
return () => {
|
|
|
|
| 164 |
clearTimeout(timeoutId);
|
| 165 |
};
|
| 166 |
}, [connected, activeVideoStream, client, videoRef]);
|
|
@@ -168,20 +182,20 @@ function ControlTray({
|
|
| 168 |
//handler for swapping from one video-stream to the next
|
| 169 |
const changeStreams = (next?: UseMediaStreamResult) => async () => {
|
| 170 |
if (next) {
|
|
|
|
| 171 |
const mediaStream = await next.start();
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
onVideoStreamChange(mediaStream);
|
| 175 |
-
} else {
|
| 176 |
-
setActiveVideoStream(null);
|
| 177 |
-
onVideoStreamChange(null);
|
| 178 |
-
}
|
| 179 |
} else {
|
|
|
|
| 180 |
setActiveVideoStream(null);
|
| 181 |
onVideoStreamChange(null);
|
| 182 |
}
|
| 183 |
|
| 184 |
-
videoStreams.filter((msr) => msr !== next).forEach((msr) =>
|
|
|
|
|
|
|
|
|
|
| 185 |
};
|
| 186 |
|
| 187 |
return (
|
|
|
|
| 84 |
|
| 85 |
useEffect(() => {
|
| 86 |
if (!connected && connectButtonRef.current) {
|
| 87 |
+
console.log('π― Setting focus on connect button - connection status:', connected);
|
| 88 |
connectButtonRef.current.focus();
|
| 89 |
}
|
| 90 |
}, [connected]);
|
|
|
|
| 92 |
// Add iOS volume simulation effect
|
| 93 |
useEffect(() => {
|
| 94 |
if (isIOSDevice && connected && !muted) {
|
| 95 |
+
console.log('π± Starting iOS volume simulation');
|
| 96 |
const interval = setInterval(() => {
|
| 97 |
// Create a smooth pulsing effect
|
| 98 |
const pulse = (Math.sin(Date.now() / 500) + 1) / 2; // Values between 0 and 1
|
| 99 |
setSimulatedVolume(0.02 + pulse * 0.03); // Small range for subtle effect
|
| 100 |
}, 50);
|
| 101 |
|
| 102 |
+
return () => {
|
| 103 |
+
console.log('π Stopping iOS volume simulation');
|
| 104 |
+
clearInterval(interval);
|
| 105 |
+
};
|
| 106 |
}
|
| 107 |
}, [connected, muted, isIOSDevice]);
|
| 108 |
|
|
|
|
| 115 |
|
| 116 |
useEffect(() => {
|
| 117 |
const onData = (base64: string) => {
|
| 118 |
+
console.log('π€ Sending audio data chunk');
|
| 119 |
client.sendRealtimeInput([
|
| 120 |
{
|
| 121 |
mimeType: "audio/pcm;rate=16000",
|
|
|
|
| 125 |
};
|
| 126 |
|
| 127 |
if (connected && !muted && audioRecorder) {
|
| 128 |
+
console.log('ποΈ Starting audio recorder');
|
| 129 |
audioRecorder.on("data", onData).on("volume", setInVolume).start();
|
| 130 |
} else {
|
| 131 |
+
console.log('βΉοΈ Stopping audio recorder');
|
| 132 |
audioRecorder.stop();
|
| 133 |
}
|
| 134 |
|
| 135 |
return () => {
|
| 136 |
+
console.log('π§Ή Cleaning up audio recorder listeners');
|
| 137 |
audioRecorder.off("data", onData).off("volume", setInVolume);
|
| 138 |
};
|
| 139 |
}, [connected, client, muted, audioRecorder]);
|
| 140 |
|
| 141 |
useEffect(() => {
|
| 142 |
if (videoRef.current) {
|
| 143 |
+
console.log('π₯ Setting video stream:', activeVideoStream ? 'active' : 'null');
|
| 144 |
videoRef.current.srcObject = activeVideoStream;
|
| 145 |
}
|
| 146 |
|
|
|
|
| 151 |
const canvas = renderCanvasRef.current;
|
| 152 |
|
| 153 |
if (!video || !canvas) {
|
| 154 |
+
console.log('β οΈ Missing video or canvas reference');
|
| 155 |
return;
|
| 156 |
}
|
| 157 |
|
|
|
|
| 162 |
ctx.drawImage(videoRef.current, 0, 0, canvas.width, canvas.height);
|
| 163 |
const base64 = canvas.toDataURL("image/jpeg", 1.0);
|
| 164 |
const data = base64.slice(base64.indexOf(",") + 1, Infinity);
|
| 165 |
+
console.log('πΈ Sending video frame');
|
| 166 |
client.sendRealtimeInput([{ mimeType: "image/jpeg", data }]);
|
| 167 |
}
|
| 168 |
if (connected) {
|
|
|
|
| 170 |
}
|
| 171 |
}
|
| 172 |
if (connected && activeVideoStream !== null) {
|
| 173 |
+
console.log('π¬ Starting video frame capture');
|
| 174 |
requestAnimationFrame(sendVideoFrame);
|
| 175 |
}
|
| 176 |
return () => {
|
| 177 |
+
console.log('βΉοΈ Stopping video frame capture');
|
| 178 |
clearTimeout(timeoutId);
|
| 179 |
};
|
| 180 |
}, [connected, activeVideoStream, client, videoRef]);
|
|
|
|
| 182 |
//handler for swapping from one video-stream to the next
|
| 183 |
const changeStreams = (next?: UseMediaStreamResult) => async () => {
|
| 184 |
if (next) {
|
| 185 |
+
console.log('π Starting new video stream');
|
| 186 |
const mediaStream = await next.start();
|
| 187 |
+
setActiveVideoStream(mediaStream);
|
| 188 |
+
onVideoStreamChange(mediaStream);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
} else {
|
| 190 |
+
console.log('βΉοΈ Stopping video stream');
|
| 191 |
setActiveVideoStream(null);
|
| 192 |
onVideoStreamChange(null);
|
| 193 |
}
|
| 194 |
|
| 195 |
+
videoStreams.filter((msr) => msr !== next).forEach((msr) => {
|
| 196 |
+
console.log('π Stopping other video streams');
|
| 197 |
+
msr.stop();
|
| 198 |
+
});
|
| 199 |
};
|
| 200 |
|
| 201 |
return (
|
src/contexts/LiveAPIContext.tsx
CHANGED
|
@@ -28,6 +28,7 @@ export const LiveAPIProvider: FC<LiveAPIProviderProps> = ({
|
|
| 28 |
url = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws`,
|
| 29 |
children,
|
| 30 |
}) => {
|
|
|
|
| 31 |
const liveAPI = useLiveAPI({ url });
|
| 32 |
|
| 33 |
return (
|
|
@@ -40,7 +41,9 @@ export const LiveAPIProvider: FC<LiveAPIProviderProps> = ({
|
|
| 40 |
export const useLiveAPIContext = () => {
|
| 41 |
const context = useContext(LiveAPIContext);
|
| 42 |
if (!context) {
|
|
|
|
| 43 |
throw new Error("useLiveAPIContext must be used within a LiveAPIProvider");
|
| 44 |
}
|
|
|
|
| 45 |
return context;
|
| 46 |
};
|
|
|
|
| 28 |
url = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws`,
|
| 29 |
children,
|
| 30 |
}) => {
|
| 31 |
+
console.log('π Initializing LiveAPIProvider with URL:', url);
|
| 32 |
const liveAPI = useLiveAPI({ url });
|
| 33 |
|
| 34 |
return (
|
|
|
|
| 41 |
export const useLiveAPIContext = () => {
|
| 42 |
const context = useContext(LiveAPIContext);
|
| 43 |
if (!context) {
|
| 44 |
+
console.error('β LiveAPIContext used outside of LiveAPIProvider');
|
| 45 |
throw new Error("useLiveAPIContext must be used within a LiveAPIProvider");
|
| 46 |
}
|
| 47 |
+
console.log('β
LiveAPIContext successfully retrieved');
|
| 48 |
return context;
|
| 49 |
};
|
src/hooks/use-media-stream-mux.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
| 16 |
|
| 17 |
export type UseMediaStreamResult = {
|
| 18 |
type: "webcam" | "screen";
|
| 19 |
-
start: () => Promise<MediaStream
|
| 20 |
stop: () => void;
|
| 21 |
isStreaming: boolean;
|
| 22 |
stream: MediaStream | null;
|
|
|
|
| 16 |
|
| 17 |
export type UseMediaStreamResult = {
|
| 18 |
type: "webcam" | "screen";
|
| 19 |
+
start: () => Promise<MediaStream>;
|
| 20 |
stop: () => void;
|
| 21 |
isStreaming: boolean;
|
| 22 |
stream: MediaStream | null;
|
src/hooks/use-screen-capture.ts
CHANGED
|
@@ -41,20 +41,15 @@ export function useScreenCapture(): UseMediaStreamResult {
|
|
| 41 |
}, [stream]);
|
| 42 |
|
| 43 |
const start = async () => {
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
return mediaStream;
|
| 54 |
-
} catch (err) {
|
| 55 |
-
console.error('Failed to start screen capture:', err);
|
| 56 |
-
return null;
|
| 57 |
-
}
|
| 58 |
};
|
| 59 |
|
| 60 |
const stop = () => {
|
|
|
|
| 41 |
}, [stream]);
|
| 42 |
|
| 43 |
const start = async () => {
|
| 44 |
+
// const controller = new CaptureController();
|
| 45 |
+
// controller.setFocusBehavior("no-focus-change");
|
| 46 |
+
const mediaStream = await navigator.mediaDevices.getDisplayMedia({
|
| 47 |
+
video: true,
|
| 48 |
+
// controller
|
| 49 |
+
});
|
| 50 |
+
setStream(mediaStream);
|
| 51 |
+
setIsStreaming(true);
|
| 52 |
+
return mediaStream;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
};
|
| 54 |
|
| 55 |
const stop = () => {
|
src/hooks/use-webcam.ts
CHANGED
|
@@ -20,30 +20,6 @@ import { UseMediaStreamResult } from "./use-media-stream-mux";
|
|
| 20 |
export function useWebcam(): UseMediaStreamResult {
|
| 21 |
const [stream, setStream] = useState<MediaStream | null>(null);
|
| 22 |
const [isStreaming, setIsStreaming] = useState(false);
|
| 23 |
-
const [availableCameras, setAvailableCameras] = useState<MediaDeviceInfo[]>([]);
|
| 24 |
-
const [currentCameraIndex, setCurrentCameraIndex] = useState(-1);
|
| 25 |
-
|
| 26 |
-
// Get list of available cameras on mount
|
| 27 |
-
useEffect(() => {
|
| 28 |
-
async function getCameras() {
|
| 29 |
-
try {
|
| 30 |
-
// First request permission to ensure we can enumerate video devices
|
| 31 |
-
await navigator.mediaDevices.getUserMedia({ video: true })
|
| 32 |
-
.then(stream => {
|
| 33 |
-
// Stop the stream immediately, we just needed permission
|
| 34 |
-
stream.getTracks().forEach(track => track.stop());
|
| 35 |
-
});
|
| 36 |
-
|
| 37 |
-
const devices = await navigator.mediaDevices.enumerateDevices();
|
| 38 |
-
const videoDevices = devices.filter(device => device.kind === 'videoinput');
|
| 39 |
-
setAvailableCameras(videoDevices);
|
| 40 |
-
console.log('Available cameras:', videoDevices);
|
| 41 |
-
} catch (err) {
|
| 42 |
-
console.error('Error getting cameras:', err);
|
| 43 |
-
}
|
| 44 |
-
}
|
| 45 |
-
getCameras();
|
| 46 |
-
}, []);
|
| 47 |
|
| 48 |
useEffect(() => {
|
| 49 |
const handleStreamEnded = () => {
|
|
@@ -65,41 +41,12 @@ export function useWebcam(): UseMediaStreamResult {
|
|
| 65 |
}, [stream]);
|
| 66 |
|
| 67 |
const start = async () => {
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
if (stream) {
|
| 75 |
-
stream.getTracks().forEach((track) => track.stop());
|
| 76 |
-
}
|
| 77 |
-
|
| 78 |
-
// If we've cycled through all cameras, stop streaming
|
| 79 |
-
if (nextIndex === 0) {
|
| 80 |
-
setStream(null);
|
| 81 |
-
setIsStreaming(false);
|
| 82 |
-
return null;
|
| 83 |
-
}
|
| 84 |
-
|
| 85 |
-
const deviceId = availableCameras[nextIndex].deviceId;
|
| 86 |
-
const mediaStream = await navigator.mediaDevices.getUserMedia({
|
| 87 |
-
video: { deviceId: { exact: deviceId } }
|
| 88 |
-
});
|
| 89 |
-
setStream(mediaStream);
|
| 90 |
-
setIsStreaming(true);
|
| 91 |
-
return mediaStream;
|
| 92 |
-
} else {
|
| 93 |
-
// Start with first camera
|
| 94 |
-
setCurrentCameraIndex(0);
|
| 95 |
-
const deviceId = availableCameras[0]?.deviceId;
|
| 96 |
-
const mediaStream = await navigator.mediaDevices.getUserMedia({
|
| 97 |
-
video: deviceId ? { deviceId: { exact: deviceId } } : true
|
| 98 |
-
});
|
| 99 |
-
setStream(mediaStream);
|
| 100 |
-
setIsStreaming(true);
|
| 101 |
-
return mediaStream;
|
| 102 |
-
}
|
| 103 |
};
|
| 104 |
|
| 105 |
const stop = () => {
|
|
@@ -107,7 +54,6 @@ export function useWebcam(): UseMediaStreamResult {
|
|
| 107 |
stream.getTracks().forEach((track) => track.stop());
|
| 108 |
setStream(null);
|
| 109 |
setIsStreaming(false);
|
| 110 |
-
setCurrentCameraIndex(-1);
|
| 111 |
}
|
| 112 |
};
|
| 113 |
|
|
|
|
| 20 |
export function useWebcam(): UseMediaStreamResult {
|
| 21 |
const [stream, setStream] = useState<MediaStream | null>(null);
|
| 22 |
const [isStreaming, setIsStreaming] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
useEffect(() => {
|
| 25 |
const handleStreamEnded = () => {
|
|
|
|
| 41 |
}, [stream]);
|
| 42 |
|
| 43 |
const start = async () => {
|
| 44 |
+
const mediaStream = await navigator.mediaDevices.getUserMedia({
|
| 45 |
+
video: true,
|
| 46 |
+
});
|
| 47 |
+
setStream(mediaStream);
|
| 48 |
+
setIsStreaming(true);
|
| 49 |
+
return mediaStream;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
};
|
| 51 |
|
| 52 |
const stop = () => {
|
|
|
|
| 54 |
stream.getTracks().forEach((track) => track.stop());
|
| 55 |
setStream(null);
|
| 56 |
setIsStreaming(false);
|
|
|
|
| 57 |
}
|
| 58 |
};
|
| 59 |
|
src/lib/multimodal-live-client.ts
CHANGED
|
@@ -72,6 +72,7 @@ export class MultimodalLiveClient extends EventEmitter<MultimodalLiveClientEvent
|
|
| 72 |
|
| 73 |
constructor({ url, apiKey }: MultimodalLiveAPIClientConnection = {}) {
|
| 74 |
super();
|
|
|
|
| 75 |
this.url = url || `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws`;
|
| 76 |
this.send = this.send.bind(this);
|
| 77 |
}
|
|
@@ -86,19 +87,22 @@ export class MultimodalLiveClient extends EventEmitter<MultimodalLiveClientEvent
|
|
| 86 |
}
|
| 87 |
|
| 88 |
connect(config: LiveConfig): Promise<boolean> {
|
|
|
|
| 89 |
this.config = config;
|
| 90 |
|
| 91 |
const ws = new WebSocket(this.url);
|
| 92 |
|
| 93 |
ws.addEventListener("message", async (evt: MessageEvent) => {
|
|
|
|
| 94 |
if (evt.data instanceof Blob) {
|
| 95 |
this.receive(evt.data);
|
| 96 |
} else {
|
| 97 |
-
console.log("
|
| 98 |
}
|
| 99 |
});
|
| 100 |
return new Promise((resolve, reject) => {
|
| 101 |
const onError = (ev: Event) => {
|
|
|
|
| 102 |
this.disconnect(ws);
|
| 103 |
const message = `Could not connect to "${this.url}"`;
|
| 104 |
this.log(`server.${ev.type}`, message);
|
|
@@ -106,7 +110,9 @@ export class MultimodalLiveClient extends EventEmitter<MultimodalLiveClientEvent
|
|
| 106 |
};
|
| 107 |
ws.addEventListener("error", onError);
|
| 108 |
ws.addEventListener("open", (ev: Event) => {
|
|
|
|
| 109 |
if (!this.config) {
|
|
|
|
| 110 |
reject("Invalid config sent to `connect(config)`");
|
| 111 |
return;
|
| 112 |
}
|
|
@@ -118,12 +124,13 @@ export class MultimodalLiveClient extends EventEmitter<MultimodalLiveClientEvent
|
|
| 118 |
const setupMessage: SetupMessage = {
|
| 119 |
setup: this.config,
|
| 120 |
};
|
|
|
|
| 121 |
this._sendDirect(setupMessage);
|
| 122 |
this.log("client.send", "setup");
|
| 123 |
|
| 124 |
ws.removeEventListener("error", onError);
|
| 125 |
ws.addEventListener("close", (ev: CloseEvent) => {
|
| 126 |
-
console.log(ev);
|
| 127 |
this.disconnect(ws);
|
| 128 |
let reason = ev.reason || "";
|
| 129 |
if (reason.toLowerCase().includes("error")) {
|
|
@@ -136,6 +143,7 @@ export class MultimodalLiveClient extends EventEmitter<MultimodalLiveClientEvent
|
|
| 136 |
);
|
| 137 |
}
|
| 138 |
}
|
|
|
|
| 139 |
this.log(
|
| 140 |
`server.${ev.type}`,
|
| 141 |
`disconnected ${reason ? `with reason: ${reason}` : ``}`,
|
|
@@ -148,14 +156,17 @@ export class MultimodalLiveClient extends EventEmitter<MultimodalLiveClientEvent
|
|
| 148 |
}
|
| 149 |
|
| 150 |
disconnect(ws?: WebSocket) {
|
|
|
|
| 151 |
// could be that this is an old websocket and theres already a new instance
|
| 152 |
// only close it if its still the correct reference
|
| 153 |
if ((!ws || this.ws === ws) && this.ws) {
|
|
|
|
| 154 |
this.ws.close();
|
| 155 |
this.ws = null;
|
| 156 |
this.log("client.close", `Disconnected`);
|
| 157 |
return true;
|
| 158 |
}
|
|
|
|
| 159 |
return false;
|
| 160 |
}
|
| 161 |
|
|
@@ -163,6 +174,7 @@ export class MultimodalLiveClient extends EventEmitter<MultimodalLiveClientEvent
|
|
| 163 |
const response: LiveIncomingMessage = (await blobToJSON(
|
| 164 |
blob,
|
| 165 |
)) as LiveIncomingMessage;
|
|
|
|
| 166 |
if (isToolCallMessage(response)) {
|
| 167 |
this.log("server.toolCall", response);
|
| 168 |
this.emit("toolcall", response.toolCall);
|
|
|
|
| 72 |
|
| 73 |
constructor({ url, apiKey }: MultimodalLiveAPIClientConnection = {}) {
|
| 74 |
super();
|
| 75 |
+
console.log('π§ Initializing MultimodalLiveClient with URL:', url || `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws`);
|
| 76 |
this.url = url || `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.host}/ws`;
|
| 77 |
this.send = this.send.bind(this);
|
| 78 |
}
|
|
|
|
| 87 |
}
|
| 88 |
|
| 89 |
connect(config: LiveConfig): Promise<boolean> {
|
| 90 |
+
console.log('π Attempting WebSocket connection to:', this.url);
|
| 91 |
this.config = config;
|
| 92 |
|
| 93 |
const ws = new WebSocket(this.url);
|
| 94 |
|
| 95 |
ws.addEventListener("message", async (evt: MessageEvent) => {
|
| 96 |
+
console.log('π¨ Received WebSocket message:', evt.data instanceof Blob ? 'Blob data' : evt.data);
|
| 97 |
if (evt.data instanceof Blob) {
|
| 98 |
this.receive(evt.data);
|
| 99 |
} else {
|
| 100 |
+
console.log("π¦ Non-blob message received:", evt);
|
| 101 |
}
|
| 102 |
});
|
| 103 |
return new Promise((resolve, reject) => {
|
| 104 |
const onError = (ev: Event) => {
|
| 105 |
+
console.error('β WebSocket connection error:', ev);
|
| 106 |
this.disconnect(ws);
|
| 107 |
const message = `Could not connect to "${this.url}"`;
|
| 108 |
this.log(`server.${ev.type}`, message);
|
|
|
|
| 110 |
};
|
| 111 |
ws.addEventListener("error", onError);
|
| 112 |
ws.addEventListener("open", (ev: Event) => {
|
| 113 |
+
console.log('β
WebSocket connection opened successfully');
|
| 114 |
if (!this.config) {
|
| 115 |
+
console.error('β Invalid config provided to connect()');
|
| 116 |
reject("Invalid config sent to `connect(config)`");
|
| 117 |
return;
|
| 118 |
}
|
|
|
|
| 124 |
const setupMessage: SetupMessage = {
|
| 125 |
setup: this.config,
|
| 126 |
};
|
| 127 |
+
console.log('π€ Sending setup message:', setupMessage);
|
| 128 |
this._sendDirect(setupMessage);
|
| 129 |
this.log("client.send", "setup");
|
| 130 |
|
| 131 |
ws.removeEventListener("error", onError);
|
| 132 |
ws.addEventListener("close", (ev: CloseEvent) => {
|
| 133 |
+
console.log('π WebSocket connection closed:', ev);
|
| 134 |
this.disconnect(ws);
|
| 135 |
let reason = ev.reason || "";
|
| 136 |
if (reason.toLowerCase().includes("error")) {
|
|
|
|
| 143 |
);
|
| 144 |
}
|
| 145 |
}
|
| 146 |
+
console.log('π Close reason:', reason || 'No reason provided');
|
| 147 |
this.log(
|
| 148 |
`server.${ev.type}`,
|
| 149 |
`disconnected ${reason ? `with reason: ${reason}` : ``}`,
|
|
|
|
| 156 |
}
|
| 157 |
|
| 158 |
disconnect(ws?: WebSocket) {
|
| 159 |
+
console.log('π Attempting to disconnect WebSocket');
|
| 160 |
// could be that this is an old websocket and theres already a new instance
|
| 161 |
// only close it if its still the correct reference
|
| 162 |
if ((!ws || this.ws === ws) && this.ws) {
|
| 163 |
+
console.log('π Closing WebSocket connection');
|
| 164 |
this.ws.close();
|
| 165 |
this.ws = null;
|
| 166 |
this.log("client.close", `Disconnected`);
|
| 167 |
return true;
|
| 168 |
}
|
| 169 |
+
console.log('β οΈ No active WebSocket to disconnect');
|
| 170 |
return false;
|
| 171 |
}
|
| 172 |
|
|
|
|
| 174 |
const response: LiveIncomingMessage = (await blobToJSON(
|
| 175 |
blob,
|
| 176 |
)) as LiveIncomingMessage;
|
| 177 |
+
console.log('π₯ Received message:', response);
|
| 178 |
if (isToolCallMessage(response)) {
|
| 179 |
this.log("server.toolCall", response);
|
| 180 |
this.emit("toolcall", response.toolCall);
|