diff --git a/packages/sdk/index.html b/packages/sdk/index.html index dcc9b62..c17d138 100644 --- a/packages/sdk/index.html +++ b/packages/sdk/index.html @@ -273,6 +273,7 @@

Prompt Control

+
@@ -386,6 +387,7 @@

Console Logs

remoteVideo: document.getElementById('remote-video'), promptInput: document.getElementById('prompt-input'), sendPrompt: document.getElementById('send-prompt'), + resetInput: document.getElementById('reset-input'), enrichToggle: document.getElementById('enrich-toggle'), // Promise-based prompt elements promisePromptInput: document.getElementById('promise-prompt-input'), @@ -546,6 +548,7 @@

Console Logs

elements.disconnectBtn.disabled = false; elements.promptInput.disabled = false; elements.sendPrompt.disabled = false; + elements.resetInput.disabled = false; elements.promisePromptInput.disabled = false; elements.sendPromisePrompt.disabled = false; elements.referenceImage.disabled = false; @@ -599,6 +602,7 @@

Console Logs

elements.disconnectBtn.disabled = true; elements.promptInput.disabled = true; elements.sendPrompt.disabled = true; + elements.resetInput.disabled = true; elements.promptInput.value = ''; elements.promisePromptInput.disabled = true; elements.sendPromisePrompt.disabled = true; @@ -641,6 +645,25 @@

Console Logs

// Send prompt on button click elements.sendPrompt.addEventListener('click', sendPrompt); + // Reset input (back to passthrough) + elements.resetInput.addEventListener('click', async () => { + if (!decartRealtime || !isConnected) { + addLog('Not connected to Decart', 'error'); + return; + } + try { + elements.resetInput.disabled = true; + addLog('Resetting input to passthrough...', 'info'); + await decartRealtime.resetInput(); + addLog('Reset to passthrough mode', 'success'); + elements.promptInput.value = ''; + } catch (error) { + addLog(`Failed to reset input: ${error.message}`, 'error'); + } finally { + elements.resetInput.disabled = false; + } + }); + // Send prompt on Enter key elements.promptInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !elements.sendPrompt.disabled) { diff --git a/packages/sdk/src/realtime/client.ts b/packages/sdk/src/realtime/client.ts index f1869b0..f31f6e8 100644 --- a/packages/sdk/src/realtime/client.ts +++ b/packages/sdk/src/realtime/client.ts @@ -109,6 +109,7 @@ export type Events = { export type RealTimeClient = { set: (input: SetInput) => Promise; setPrompt: (prompt: string, { enhance }?: { enhance?: boolean }) => Promise; + resetInput: () => Promise; isConnected: () => boolean; getConnectionState: () => ConnectionState; disconnect: () => void; @@ -118,7 +119,7 @@ export type RealTimeClient = { subscribeToken: string | null; setImage: ( image: Blob | File | string | null, - options?: { prompt?: string; enhance?: boolean; timeout?: number }, + options?: { prompt?: string | null; enhance?: boolean; timeout?: number }, ) => Promise; playAudio?: (audio: Blob | File | ArrayBuffer) => Promise; }; @@ -329,6 +330,7 @@ export const createRealTimeClient = (opts: RealTimeClientOptions) => { const client: RealTimeClient = { set: methods.set, setPrompt: methods.setPrompt, + resetInput: methods.resetInput, isConnected: () => manager.isConnected(), getConnectionState: () => manager.getConnectionState(), disconnect: () => { @@ -348,7 +350,7 @@ export const createRealTimeClient = (opts: RealTimeClientOptions) => { }, setImage: async ( image: Blob | File | string | null, - options?: { prompt?: string; enhance?: boolean; timeout?: number }, + options?: { prompt?: string | null; enhance?: boolean; timeout?: number }, ) => { if (image === null) { return manager.setImage(null, options); diff --git a/packages/sdk/src/realtime/methods.ts b/packages/sdk/src/realtime/methods.ts index 6755d41..39b6cde 100644 --- a/packages/sdk/src/realtime/methods.ts +++ b/packages/sdk/src/realtime/methods.ts @@ -7,7 +7,7 @@ const UPDATE_TIMEOUT_MS = 30 * 1000; const setInputSchema = z .object({ - prompt: z.string().min(1).optional(), + prompt: z.union([z.string().min(1), z.null()]).optional(), enhance: z.boolean().optional().default(true), image: z.union([z.instanceof(Blob), z.instanceof(File), z.string(), z.null()]).optional(), }) @@ -109,8 +109,14 @@ export const realtimeMethods = ( } }; + const resetInput = async (): Promise => { + assertConnected(); + await webrtcManager.setImage(null, { prompt: null, timeout: UPDATE_TIMEOUT_MS }); + }; + return { set, setPrompt, + resetInput, }; }; diff --git a/packages/sdk/src/realtime/webrtc-manager.ts b/packages/sdk/src/realtime/webrtc-manager.ts index 71408fb..1efca07 100644 --- a/packages/sdk/src/realtime/webrtc-manager.ts +++ b/packages/sdk/src/realtime/webrtc-manager.ts @@ -246,7 +246,7 @@ export class WebRTCManager { setImage( imageBase64: string | null, - options?: { prompt?: string; enhance?: boolean; timeout?: number }, + options?: { prompt?: string | null; enhance?: boolean; timeout?: number }, ): Promise { return this.connection.setImageBase64(imageBase64, options); } diff --git a/packages/sdk/tests/unit.test.ts b/packages/sdk/tests/unit.test.ts index 627303e..aca56aa 100644 --- a/packages/sdk/tests/unit.test.ts +++ b/packages/sdk/tests/unit.test.ts @@ -1422,6 +1422,28 @@ describe("set()", () => { timeout: 30000, }); }); + + it("set({ prompt: null }) resets to passthrough", async () => { + await methods.set({ prompt: null }); + expect(mockManager.setImage).toHaveBeenCalledWith(null, { + prompt: null, + enhance: true, + timeout: 30000, + }); + }); + + it("resetInput sends passthrough signal", async () => { + await methods.resetInput(); + expect(mockManager.setImage).toHaveBeenCalledWith(null, { + prompt: null, + timeout: 30000, + }); + }); + + it("resetInput rejects when not connected", async () => { + mockManager.getConnectionState.mockReturnValue("disconnected"); + await expect(methods.resetInput()).rejects.toThrow("Cannot send message: connection is disconnected"); + }); }); describe("Subscribe Token", () => {