@elevenlabs/react-native 0.1.0

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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +200 -0
  3. package/dist/ElevenLabsProvider.d.ts +22 -0
  4. package/dist/ElevenLabsProvider.test.d.ts +1 -0
  5. package/dist/components/LiveKitRoomWrapper.d.ts +20 -0
  6. package/dist/components/MessageHandler.d.ts +12 -0
  7. package/dist/hooks/useConversationCallbacks.d.ts +5 -0
  8. package/dist/hooks/useConversationSession.d.ts +28 -0
  9. package/dist/hooks/useLiveKitRoom.d.ts +12 -0
  10. package/dist/hooks/useMessageSending.d.ts +7 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/lib.js +2 -0
  13. package/dist/lib.js.map +1 -0
  14. package/dist/lib.modern.js +2 -0
  15. package/dist/lib.modern.js.map +1 -0
  16. package/dist/lib.module.js +2 -0
  17. package/dist/lib.module.js.map +1 -0
  18. package/dist/lib.umd.js +2 -0
  19. package/dist/lib.umd.js.map +1 -0
  20. package/dist/types.d.ts +109 -0
  21. package/dist/utils/constants.d.ts +1 -0
  22. package/dist/utils/overrides.d.ts +2 -0
  23. package/dist/utils/tokenUtils.d.ts +2 -0
  24. package/dist/version.d.ts +1 -0
  25. package/eslint.config.cjs +1 -0
  26. package/jest.config.cjs +263 -0
  27. package/jest.rn-mock.js +15 -0
  28. package/package.json +64 -0
  29. package/src/ElevenLabsProvider.test.tsx +187 -0
  30. package/src/ElevenLabsProvider.tsx +259 -0
  31. package/src/components/LiveKitRoomWrapper.tsx +63 -0
  32. package/src/components/MessageHandler.tsx +127 -0
  33. package/src/hooks/useConversationCallbacks.ts +15 -0
  34. package/src/hooks/useConversationSession.ts +88 -0
  35. package/src/hooks/useLiveKitRoom.ts +62 -0
  36. package/src/hooks/useMessageSending.ts +33 -0
  37. package/src/index.ts +9 -0
  38. package/src/types.ts +150 -0
  39. package/src/utils/constants.ts +1 -0
  40. package/src/utils/overrides.ts +40 -0
  41. package/src/utils/tokenUtils.ts +31 -0
  42. package/src/version.ts +2 -0
  43. package/tsconfig.json +18 -0
@@ -0,0 +1,15 @@
1
+ import { useRef, useCallback } from "react";
2
+ import type { Callbacks } from "../types";
3
+
4
+ export const useConversationCallbacks = () => {
5
+ const callbacksRef = useRef<Callbacks>({});
6
+
7
+ const setCallbacks = useCallback((callbacks: Callbacks) => {
8
+ callbacksRef.current = callbacks;
9
+ }, []);
10
+
11
+ return {
12
+ callbacksRef,
13
+ setCallbacks,
14
+ };
15
+ };
@@ -0,0 +1,88 @@
1
+ import { useState, useCallback } from "react";
2
+ import type {
3
+ ConversationConfig,
4
+ ConversationStatus,
5
+ Callbacks,
6
+ } from "../types";
7
+ import {
8
+ getConversationToken,
9
+ extractRoomIdFromToken,
10
+ } from "../utils/tokenUtils";
11
+
12
+ export const useConversationSession = (
13
+ callbacksRef: { current: Callbacks },
14
+ setStatus: (status: ConversationStatus) => void,
15
+ setConnect: (connect: boolean) => void,
16
+ setToken: (token: string) => void,
17
+ setRoomId: (roomId: string) => void
18
+ ) => {
19
+ const [overrides, setOverrides] = useState<ConversationConfig["overrides"]>(
20
+ {}
21
+ );
22
+ const [customLlmExtraBody, setCustomLlmExtraBody] =
23
+ useState<ConversationConfig["customLlmExtraBody"]>(null);
24
+ const [dynamicVariables, setDynamicVariables] = useState<
25
+ ConversationConfig["dynamicVariables"]
26
+ >({});
27
+
28
+ const startSession = useCallback(
29
+ async (config: ConversationConfig) => {
30
+ try {
31
+ setStatus("connecting");
32
+ callbacksRef.current.onStatusChange?.({ status: "connecting" });
33
+
34
+ setOverrides(config.overrides || {});
35
+ setCustomLlmExtraBody(config.customLlmExtraBody || null);
36
+ setDynamicVariables(config.dynamicVariables || {});
37
+
38
+ let conversationToken: string;
39
+
40
+ if (config.conversationToken) {
41
+ conversationToken = config.conversationToken;
42
+ } else if (config.agentId) {
43
+ console.info(
44
+ "Getting conversation token for agentId:",
45
+ config.agentId
46
+ );
47
+ conversationToken = await getConversationToken(config.agentId);
48
+ } else {
49
+ throw new Error("Either conversationToken or agentId is required");
50
+ }
51
+
52
+ // Extract room ID from token
53
+ const extractedRoomId = extractRoomIdFromToken(conversationToken);
54
+ setRoomId(extractedRoomId);
55
+
56
+ setToken(conversationToken);
57
+ setConnect(true);
58
+ } catch (error) {
59
+ setStatus("disconnected");
60
+ callbacksRef.current.onStatusChange?.({ status: "disconnected" });
61
+ callbacksRef.current.onError?.(error as string);
62
+ throw error;
63
+ }
64
+ },
65
+ [callbacksRef, setStatus, setConnect, setToken, setRoomId]
66
+ );
67
+
68
+ const endSession = useCallback(async () => {
69
+ try {
70
+ setConnect(false);
71
+ setToken("");
72
+ setStatus("disconnected");
73
+ callbacksRef.current.onStatusChange?.({ status: "disconnected" });
74
+ callbacksRef.current.onDisconnect?.("User ended conversation");
75
+ } catch (error) {
76
+ callbacksRef.current.onError?.(error as string);
77
+ throw error;
78
+ }
79
+ }, [callbacksRef, setConnect, setToken, setStatus]);
80
+
81
+ return {
82
+ startSession,
83
+ endSession,
84
+ overrides,
85
+ customLlmExtraBody,
86
+ dynamicVariables,
87
+ };
88
+ };
@@ -0,0 +1,62 @@
1
+ import { useState, useEffect, useCallback } from "react";
2
+ import { AudioSession } from "@livekit/react-native";
3
+ import type { LocalParticipant } from "livekit-client";
4
+ import type { ConversationStatus, Callbacks } from "../types";
5
+
6
+ export const useLiveKitRoom = (
7
+ callbacksRef: { current: Callbacks },
8
+ setStatus: (status: ConversationStatus) => void,
9
+ roomId: string
10
+ ) => {
11
+ const [roomConnected, setRoomConnected] = useState(false);
12
+ const [localParticipant, setLocalParticipant] =
13
+ useState<LocalParticipant | null>(null);
14
+
15
+ useEffect(() => {
16
+ const start = async () => {
17
+ await AudioSession.startAudioSession();
18
+ };
19
+
20
+ start();
21
+ return () => {
22
+ AudioSession.stopAudioSession();
23
+ };
24
+ }, []);
25
+
26
+ const handleParticipantReady = useCallback(
27
+ (participant: LocalParticipant) => {
28
+ setLocalParticipant(participant);
29
+ },
30
+ []
31
+ );
32
+
33
+ const handleConnected = useCallback(() => {
34
+ setRoomConnected(true);
35
+ setStatus("connected");
36
+ callbacksRef.current.onConnect?.({ conversationId: roomId });
37
+ }, [roomId, callbacksRef, setStatus]);
38
+
39
+ const handleDisconnected = useCallback(() => {
40
+ setRoomConnected(false);
41
+ setStatus("disconnected");
42
+ setLocalParticipant(null);
43
+ callbacksRef.current.onDisconnect?.("disconnected");
44
+ }, [callbacksRef, setStatus]);
45
+
46
+ const handleError = useCallback(
47
+ (error: Error) => {
48
+ console.error("LiveKit error:", error);
49
+ callbacksRef.current.onError?.(error.message, undefined);
50
+ },
51
+ [callbacksRef]
52
+ );
53
+
54
+ return {
55
+ roomConnected,
56
+ localParticipant,
57
+ handleParticipantReady,
58
+ handleConnected,
59
+ handleDisconnected,
60
+ handleError,
61
+ };
62
+ };
@@ -0,0 +1,33 @@
1
+ import { useCallback } from "react";
2
+ import type { LocalParticipant } from "livekit-client";
3
+ import type { ConversationStatus, Callbacks } from "../types";
4
+
5
+ export const useMessageSending = (
6
+ status: ConversationStatus,
7
+ localParticipant: LocalParticipant | null,
8
+ callbacksRef: { current: Callbacks }
9
+ ) => {
10
+ const sendMessage = useCallback(
11
+ async (message: unknown) => {
12
+ if (status !== "connected" || !localParticipant) {
13
+ console.warn(
14
+ "Cannot send message: room not connected or no local participant"
15
+ );
16
+ return;
17
+ }
18
+ try {
19
+ const encoder = new TextEncoder();
20
+ const data = encoder.encode(JSON.stringify(message));
21
+
22
+ await localParticipant.publishData(data, { reliable: true });
23
+ } catch (error) {
24
+ console.error("Failed to send message via WebRTC:", error);
25
+ console.error("Error details:", error);
26
+ callbacksRef.current.onError?.(error as string);
27
+ }
28
+ },
29
+ [status, localParticipant, callbacksRef]
30
+ );
31
+
32
+ return { sendMessage };
33
+ };
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ export { ElevenLabsProvider, useConversation } from "./ElevenLabsProvider";
2
+
3
+ export type {
4
+ ConversationStatus,
5
+ Mode,
6
+ Role,
7
+ Callbacks,
8
+ ConversationOptions,
9
+ } from "./types";
package/src/types.ts ADDED
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Role in the conversation
3
+ */
4
+ export type Role = "user" | "ai";
5
+
6
+ /**
7
+ * Current mode of the conversation
8
+ */
9
+ export type Mode = "speaking" | "listening";
10
+
11
+ export type ConversationStatus = "disconnected" | "connecting" | "connected";
12
+
13
+ /**
14
+ * Language support
15
+ */
16
+ export type Language =
17
+ | "en"
18
+ | "ja"
19
+ | "zh"
20
+ | "de"
21
+ | "hi"
22
+ | "fr"
23
+ | "ko"
24
+ | "pt"
25
+ | "pt-br"
26
+ | "it"
27
+ | "es"
28
+ | "id"
29
+ | "nl"
30
+ | "tr"
31
+ | "pl"
32
+ | "sv"
33
+ | "bg"
34
+ | "ro"
35
+ | "ar"
36
+ | "cs"
37
+ | "el"
38
+ | "fi"
39
+ | "ms"
40
+ | "da"
41
+ | "ta"
42
+ | "uk"
43
+ | "ru"
44
+ | "hu"
45
+ | "hr"
46
+ | "sk"
47
+ | "no"
48
+ | "vi";
49
+
50
+ /**
51
+ * Client tool call event structure
52
+ */
53
+ export type ClientToolCallEvent = {
54
+ tool_name: string;
55
+ tool_call_id: string;
56
+ parameters: unknown;
57
+ expects_response: boolean;
58
+ };
59
+
60
+ /**
61
+ * Client tools configuration
62
+ */
63
+ export type ClientToolsConfig = {
64
+ clientTools: Record<
65
+ string,
66
+ (
67
+ parameters: unknown
68
+ ) => Promise<string | number | undefined> | string | number | undefined
69
+ >;
70
+ };
71
+
72
+ /**
73
+ * Options for useConversation hook
74
+ */
75
+ export type ConversationOptions = {
76
+ serverUrl?: string;
77
+ clientTools?: Record<
78
+ string,
79
+ (
80
+ parameters: unknown
81
+ ) => Promise<string | number | undefined> | string | number | undefined
82
+ >;
83
+ } & Partial<Callbacks>;
84
+
85
+ /**
86
+ * Callbacks configuration
87
+ */
88
+ export type Callbacks = {
89
+ onConnect?: (props: { conversationId: string }) => void;
90
+ // internal debug events, not to be used
91
+ onDebug?: (props: unknown) => void;
92
+ onDisconnect?: (details: string) => void;
93
+ onError?: (message: string, context?: Record<string, unknown>) => void;
94
+ onMessage?: (props: { message: string; source: Role }) => void;
95
+ onModeChange?: (prop: { mode: Mode }) => void;
96
+ onStatusChange?: (prop: { status: ConversationStatus }) => void;
97
+ onCanSendFeedbackChange?: (prop: { canSendFeedback: boolean }) => void;
98
+ onUnhandledClientToolCall?: (params: ClientToolCallEvent) => void;
99
+ };
100
+
101
+ export type ConversationConfig = {
102
+ agentId?: string;
103
+ conversationToken?: string;
104
+ overrides?: {
105
+ agent?: {
106
+ prompt?: {
107
+ prompt?: string;
108
+ };
109
+ firstMessage?: string;
110
+ language?: Language;
111
+ };
112
+ tts?: {
113
+ voiceId?: string;
114
+ };
115
+ conversation?: {
116
+ textOnly?: boolean;
117
+ };
118
+ client?: {
119
+ source?: string;
120
+ version?: string;
121
+ };
122
+ };
123
+ customLlmExtraBody?: unknown;
124
+ dynamicVariables?: Record<string, string | number | boolean>;
125
+ };
126
+
127
+ export type InitiationClientDataEvent = {
128
+ type: "conversation_initiation_client_data";
129
+ conversation_config_override?: {
130
+ agent?: {
131
+ prompt?: {
132
+ prompt?: string;
133
+ };
134
+ first_message?: string;
135
+ language?: Language;
136
+ };
137
+ tts?: {
138
+ voice_id?: string;
139
+ };
140
+ conversation?: {
141
+ text_only?: boolean;
142
+ };
143
+ client?: {
144
+ source?: string;
145
+ version?: string;
146
+ };
147
+ };
148
+ custom_llm_extra_body?: unknown;
149
+ dynamic_variables?: Record<string, string | number | boolean>;
150
+ };
@@ -0,0 +1 @@
1
+ export const DEFAULT_SERVER_URL = "wss://livekit.rtc.elevenlabs.io";
@@ -0,0 +1,40 @@
1
+ import type { ConversationConfig, InitiationClientDataEvent } from "../types";
2
+ import { PACKAGE_VERSION } from "../version";
3
+
4
+ export function constructOverrides(
5
+ config: ConversationConfig
6
+ ): InitiationClientDataEvent {
7
+ const overridesEvent: InitiationClientDataEvent = {
8
+ type: "conversation_initiation_client_data",
9
+ };
10
+
11
+ if (config.overrides) {
12
+ overridesEvent.conversation_config_override = {
13
+ agent: {
14
+ prompt: config.overrides.agent?.prompt,
15
+ first_message: config.overrides.agent?.firstMessage,
16
+ language: config.overrides.agent?.language,
17
+ },
18
+ tts: {
19
+ voice_id: config.overrides.tts?.voiceId,
20
+ },
21
+ conversation: {
22
+ text_only: config.overrides.conversation?.textOnly,
23
+ },
24
+ client: {
25
+ source: config.overrides?.client?.source || "react_native_sdk",
26
+ version: config.overrides?.client?.version || PACKAGE_VERSION,
27
+ },
28
+ };
29
+ }
30
+
31
+ if (config.customLlmExtraBody) {
32
+ overridesEvent.custom_llm_extra_body = config.customLlmExtraBody;
33
+ }
34
+
35
+ if (config.dynamicVariables) {
36
+ overridesEvent.dynamic_variables = config.dynamicVariables;
37
+ }
38
+
39
+ return overridesEvent;
40
+ }
@@ -0,0 +1,31 @@
1
+ import { PACKAGE_VERSION } from "../version";
2
+
3
+ export const extractRoomIdFromToken = (token: string): string => {
4
+ try {
5
+ const tokenPayload = JSON.parse(atob(token.split(".")[1]));
6
+ return tokenPayload.video?.room || "";
7
+ } catch (error) {
8
+ console.warn("Could not extract room ID from token");
9
+ return "";
10
+ }
11
+ };
12
+
13
+ export const getConversationToken = async (
14
+ agentId: string
15
+ ): Promise<string> => {
16
+ const response = await fetch(
17
+ `https://api.elevenlabs.io/v1/convai/conversation/token?agent_id=${agentId}&source=react_native_sdk&version=${PACKAGE_VERSION}`
18
+ );
19
+
20
+ const data = await response.json();
21
+
22
+ if (!response.ok) {
23
+ throw new Error(`Failed to get conversation token: ${data.detail.message}`);
24
+ }
25
+
26
+ if (!data.token) {
27
+ throw new Error("No conversation token received from API");
28
+ }
29
+
30
+ return data.token;
31
+ };
package/src/version.ts ADDED
@@ -0,0 +1,2 @@
1
+ // This file is auto-generated during build
2
+ export const PACKAGE_VERSION = "0.1.0";
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["es2015", "dom"],
4
+ "target": "es5",
5
+ "module": "esnext",
6
+ "jsx": "react",
7
+ "sourceMap": true,
8
+ "outDir": "dist",
9
+ "strict": true,
10
+ "moduleResolution": "node",
11
+ "allowSyntheticDefaultImports": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "resolveJsonModule": true
16
+ },
17
+ "include": ["src/**/*"]
18
+ }