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.
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.