@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.
- package/LICENSE +21 -0
- package/README.md +200 -0
- package/dist/ElevenLabsProvider.d.ts +22 -0
- package/dist/ElevenLabsProvider.test.d.ts +1 -0
- package/dist/components/LiveKitRoomWrapper.d.ts +20 -0
- package/dist/components/MessageHandler.d.ts +12 -0
- package/dist/hooks/useConversationCallbacks.d.ts +5 -0
- package/dist/hooks/useConversationSession.d.ts +28 -0
- package/dist/hooks/useLiveKitRoom.d.ts +12 -0
- package/dist/hooks/useMessageSending.d.ts +7 -0
- package/dist/index.d.ts +2 -0
- package/dist/lib.js +2 -0
- package/dist/lib.js.map +1 -0
- package/dist/lib.modern.js +2 -0
- package/dist/lib.modern.js.map +1 -0
- package/dist/lib.module.js +2 -0
- package/dist/lib.module.js.map +1 -0
- package/dist/lib.umd.js +2 -0
- package/dist/lib.umd.js.map +1 -0
- package/dist/types.d.ts +109 -0
- package/dist/utils/constants.d.ts +1 -0
- package/dist/utils/overrides.d.ts +2 -0
- package/dist/utils/tokenUtils.d.ts +2 -0
- package/dist/version.d.ts +1 -0
- package/eslint.config.cjs +1 -0
- package/jest.config.cjs +263 -0
- package/jest.rn-mock.js +15 -0
- package/package.json +64 -0
- package/src/ElevenLabsProvider.test.tsx +187 -0
- package/src/ElevenLabsProvider.tsx +259 -0
- package/src/components/LiveKitRoomWrapper.tsx +63 -0
- package/src/components/MessageHandler.tsx +127 -0
- package/src/hooks/useConversationCallbacks.ts +15 -0
- package/src/hooks/useConversationSession.ts +88 -0
- package/src/hooks/useLiveKitRoom.ts +62 -0
- package/src/hooks/useMessageSending.ts +33 -0
- package/src/index.ts +9 -0
- package/src/types.ts +150 -0
- package/src/utils/constants.ts +1 -0
- package/src/utils/overrides.ts +40 -0
- package/src/utils/tokenUtils.ts +31 -0
- package/src/version.ts +2 -0
- 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
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
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
|
+
}
|