Skip to main content

React bindings

The /react sub-path exposes a provider plus a small set of hooks. They share one DelphiClient per tree, deduplicate sessions across components, and subscribe to state via useSyncExternalStore.

Imports

import {
DelphiClientProvider,
DelphiConfigInit,
useDelphiClientContext,
useDelphiClientState,
useDelphiSession,
useBrowserAction,
useSelectionTracking,
} from '@ki-kombinat/delphi-client-js-sdk/react';

Provider

Wrap your tree once. All hooks below resolve the client via context.

<DelphiClientProvider config={{apiDomain, apiKey}}>
<App />
</DelphiClientProvider>

If config is only known after auth, omit it on the provider and push it later deeper in the tree:

<DelphiClientProvider>
<AuthGate>
<DelphiConfigInit config={resolvedConfig} />
<App />
</AuthGate>
</DelphiClientProvider>

useDelphiSession

Find-or-create a session for an endpoint and subscribe to its state. Multiple components asking for the same endpointId + mode share one WebSocket.

function ReadAloudWidget({endpointId}: {endpointId: string}) {
const {connected, sendReadAloud, audioDone} = useDelphiSession({
endpointId,
mode: 'audio_playback',
});

return (
<button
disabled={!connected}
onClick={async () => {
sendReadAloud('Hello!');
await audioDone();
}}
>
Speak
</button>
);
}

The hook returns the full SessionClient surface plus connected, serverReady, and a stable client reference.

Mode awareness

useDelphiSession({ endpointId, mode }) is mode-keyed under the hood (SDK 0.1.3+). You can mount two hooks against the same endpoint with different modes and they will not collide — for instance a voice_conversation speaker and a listen subscriber in the same component tree.

useBrowserAction

Compose a BrowserAction handler with sensible defaults plus app-specific custom handlers. Pass it into useDelphiSession via onAction.

function CallButton() {
const handleBrowserAction = useBrowserAction({
onNavigate: (path) => router.push(path),
customHandlers: {
fill_invoice_form: async (params) => ({
success: true,
data: await fillInvoice(params),
}),
},
});

const {sendReadAloud} = useDelphiSession({
endpointId: '24599c70-1e79-4e52-9819-e2d97acf45a5',
mode: 'voice_conversation',
onAction: handleBrowserAction,
});

return <button onClick={() => sendReadAloud('Hi!')}>Read aloud</button>;
}

See Browser actions for the full handler shape and the standard action names.

useSelectionTracking

Tracks window.getSelection() so the user can trigger read-aloud on the currently highlighted text.

const {sendReadAloud, connected} = useDelphiSession({
endpointId: '24599c70-1e79-4e52-9819-e2d97acf45a5',
mode: 'audio_playback',
});

const {selectedText, handleReadAloudSelected, showReadAloudFab} = useSelectionTracking({
sendReadAloud,
channelConnected: connected,
forceEnable: true, // disable the in-call gating
});

return (
<>
<article></article>
{showReadAloudFab && <button onClick={handleReadAloudSelected}>Read selected</button>}
</>
);

useDelphiClientState

Read the orchestrator's aggregated state directly (voice-call status, every open session, currently selected text):

const {state, client} = useDelphiClientState();

state.sessions;
// [{ endpointId, sessionId, mode, connected, lastActivityAt }]

state.voiceCall;
// { inCall, calling, registered, telproDomain, ... }

useDelphiClientContext() is the underlying primitive when you need the bare DelphiClient instance.

Cleanup

The provider cleans up the client on unmount. Individual useDelphiSession callers do not end sessions when they unmount — the session map is owned by the client so navigating between components mid-call doesn't drop the call. Use client.endSession(endpointId, mode) (or endAllSessions()) when you explicitly want a session torn down.