Expo React Native के साथ क्रॉस-प्लैटफ़ॉर्म वॉइस एजेंट

WebRTC सपोर्ट वाले Expo और ElevenLabs React Native SDK का उपयोग करके iOS और Android पर काम करने वाले ElevenLabs एजेंट बनाएं।

ट्यूटोरियल · इसमें माना गया है कि आपने ElevenAgents क्विकस्टार्ट पूरा कर लिया है और आपका Expo डेवलपमेंट एनवायरनमेंट सेट अप है।

परिचय

इस ट्यूटोरियल में आप जानेंगे कि Expo React Native और WebRTC सपोर्ट वाले ElevenLabs React Native SDK का उपयोग करके iOS और Android पर काम करने वाला वॉइस एजेंट कैसे बनाएं।

आवश्यकताएं

  • API कुंजी वाले ElevenLabs अकाउंट की आवश्यकता होगी: API key।
  • आपके सिस्टम पर Node.js v18 या इसके बाद का वर्ज़न इंस्टॉल होना चाहिए।

सेटअप

नया Expo प्रोजेक्ट बनाएं

create-expo-app का उपयोग करके नया खाली Expo प्रोजेक्ट बनाएं:

npx create-expo-app@latest --template blank-typescript

डिपेंडेंसी इंस्टॉल करें

ElevenLabs React Native SDK और उसकी डिपेंडेंसी इंस्टॉल करें:

npx expo install @elevenlabs/react-native @livekit/react-native @livekit/react-native-webrtc @config-plugins/react-native-webrtc @livekit/react-native-expo-plugin @livekit/react-native-expo-plugin livekit-client

अगर आपको peer dependencies से जुड़ी समस्या आ रही है, तो प्रोजेक्ट की रूट डायरेक्टरी में .npmrc फ़ाइल जोड़ें और उसमें यह कंटेंट डालें: legacy-peer-deps=true।

माइक्रोफ़ोन अनुमतियां चालू करें और Expo प्लगइन जोड़ें

app.json फ़ाइल में ये अनुमतियां जोड़ें:

app.json
{
"expo": {
"scheme": "elevenlabs",
// ...
"ios": {
"infoPlist": {
"NSMicrophoneUsageDescription": "This app uses the microphone to record audio."
},
"supportsTablet": true,
"bundleIdentifier": "YOUR.BUNDLE.ID"
},
"android": {
"permissions": [
"android.permission.RECORD_AUDIO",
"android.permission.ACCESS_NETWORK_STATE",
"android.permission.CAMERA",
"android.permission.INTERNET",
"android.permission.MODIFY_AUDIO_SETTINGS",
"android.permission.SYSTEM_ALERT_WINDOW",
"android.permission.WAKE_LOCK",
"android.permission.BLUETOOTH"
],
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "YOUR.PACKAGE.ID"
},
"plugins": ["@livekit/react-native-expo-plugin", "@config-plugins/react-native-webrtc"]
// ...
}
}

इससे बातचीत शुरू होने पर React Native माइक्रोफ़ोन की अनुमति मांग सकेगा।

नोट

Android एम्युलेटर के लिए, एम्युलेटर माइक्रोफ़ोन सेटिंग में आपको “Virtual microphone uses host audio input” चालू करना होगा।

अपने ऐप में ElevenLabs Agents जोड़ें

अपने ./App.tsx फ़ाइल में यह कोड जोड़कर अपने ऐप में ElevenLabs Agents जोड़ें:

./App.tsx
import { ConversationProvider, useConversation } from "@elevenlabs/react-native";
import type { ConversationStatus, ConversationEvent, Role } from "@elevenlabs/react-native";
import React, { useState } from "react";
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Keyboard,
TouchableWithoutFeedback,
Platform,
} from "react-native";
import { TextInput } from "react-native";
import { getBatteryLevel, changeBrightness, flashScreen } from "./utils/tools";
const ConversationScreen = () => {
const conversation = useConversation({
clientTools: {
getBatteryLevel,
changeBrightness,
flashScreen,
},
onConnect: ({ conversationId }: { conversationId: string }) => {
console.log("✅ Connected to conversation", conversationId);
},
onDisconnect: (details: string) => {
console.log("❌ Disconnected from conversation", details);
},
onError: (message: string, context?: Record<string, unknown>) => {
console.error("❌ Conversation error:", message, context);
},
onMessage: ({ message, source }: { message: ConversationEvent; source: Role }) => {
console.log(`💬 Message from ${source}:`, message);
},
onModeChange: ({ mode }: { mode: "speaking" | "listening" }) => {
console.log(`🔊 Mode: ${mode}`);
},
onStatusChange: ({ status }: { status: ConversationStatus }) => {
console.log(`📡 Status: ${status}`);
},
onCanSendFeedbackChange: ({ canSendFeedback }: { canSendFeedback: boolean }) => {
console.log(`🔊 Can send feedback: ${canSendFeedback}`);
},
});
const [isStarting, setIsStarting] = useState(false);
const [textInput, setTextInput] = useState("");
const handleSubmitText = () => {
if (textInput.trim()) {
conversation.sendUserMessage(textInput.trim());
setTextInput("");
Keyboard.dismiss();
}
};
const startConversation = async () => {
if (isStarting) return;
setIsStarting(true);
try {
await conversation.startSession({
agentId: process.env.EXPO_PUBLIC_AGENT_ID,
dynamicVariables: {
platform: Platform.OS,
},
});
} catch (error) {
console.error("Failed to start conversation:", error);
} finally {
setIsStarting(false);
}
};
const endConversation = async () => {
try {
await conversation.endSession();
} catch (error) {
console.error("Failed to end conversation:", error);
}
};
const getStatusColor = (status: ConversationStatus): string => {
switch (status) {
case "connected":
return "#10B981";
case "connecting":
return "#F59E0B";
case "disconnected":
return "#EF4444";
default:
return "#6B7280";
}
};
const getStatusText = (status: ConversationStatus): string => {
return status[0].toUpperCase() + status.slice(1);
};
const canStart = conversation.status === "disconnected" && !isStarting;
const canEnd = conversation.status === "connected";
return (
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
<View style={styles.container}>
<Text style={styles.title}>ElevenLabs React Native Example</Text>
<Text style={styles.subtitle}>Remember to set the agentId in the .env file!</Text>
<View style={styles.statusContainer}>
<View
style={[styles.statusDot, { backgroundColor: getStatusColor(conversation.status) }]}
/>
<Text style={styles.statusText}>{getStatusText(conversation.status)}</Text>
</View>
{/* Speaking Indicator */}
{conversation.status === "connected" && (
<View style={styles.speakingContainer}>
<View
style={[
styles.speakingDot,
{
backgroundColor: conversation.isSpeaking ? "#8B5CF6" : "#D1D5DB",
},
]}
/>
<Text
style={[
styles.speakingText,
{ color: conversation.isSpeaking ? "#8B5CF6" : "#9CA3AF" },
]}
>
{conversation.isSpeaking ? "🎤 AI Speaking" : "👂 AI Listening"}
</Text>
</View>
)}
<View style={styles.buttonContainer}>
<TouchableOpacity
style={[styles.button, styles.startButton, !canStart && styles.disabledButton]}
onPress={startConversation}
disabled={!canStart}
>
<Text style={styles.buttonText}>
{isStarting ? "Starting..." : "Start Conversation"}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.endButton, !canEnd && styles.disabledButton]}
onPress={endConversation}
disabled={!canEnd}
>
<Text style={styles.buttonText}>End Conversation</Text>
</TouchableOpacity>
</View>
{/* Feedback Buttons */}
{conversation.status === "connected" && conversation.canSendFeedback && (
<View style={styles.feedbackContainer}>
<Text style={styles.feedbackLabel}>How was that response?</Text>
<View style={styles.feedbackButtons}>
<TouchableOpacity
style={[styles.button, styles.likeButton]}
onPress={() => conversation.sendFeedback(true)}
>
<Text style={styles.buttonText}>👍 Like</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.dislikeButton]}
onPress={() => conversation.sendFeedback(false)}
>
<Text style={styles.buttonText}>👎 Dislike</Text>
</TouchableOpacity>
</View>
</View>
)}
{/* Text Input and Messaging */}
{conversation.status === "connected" && (
<View style={styles.messagingContainer}>
<Text style={styles.messagingLabel}>Send Text Message</Text>
<TextInput
style={styles.textInput}
value={textInput}
onChangeText={(text) => {
setTextInput(text);
// Prevent agent from interrupting while user is typing
if (text.length > 0) {
conversation.sendUserActivity();
}
}}
placeholder="Type your message or context... (Press Enter to send)"
multiline
onSubmitEditing={handleSubmitText}
returnKeyType="send"
blurOnSubmit={true}
/>
<View style={styles.messageButtons}>
<TouchableOpacity
style={[styles.button, styles.messageButton]}
onPress={handleSubmitText}
disabled={!textInput.trim()}
>
<Text style={styles.buttonText}>💬 Send Message</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.contextButton]}
onPress={() => {
if (textInput.trim()) {
conversation.sendContextualUpdate(textInput.trim());
setTextInput("");
Keyboard.dismiss();
}
}}
disabled={!textInput.trim()}
>
<Text style={styles.buttonText}>📝 Send Context</Text>
</TouchableOpacity>
</View>
</View>
)}
</View>
</TouchableWithoutFeedback>
);
};
export default function App() {
return (
<ConversationProvider>
<ConversationScreen />
</ConversationProvider>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#F3F4F6",
padding: 20,
},
title: {
fontSize: 24,
fontWeight: "bold",
marginBottom: 8,
color: "#1F2937",
},
subtitle: {
fontSize: 16,
color: "#6B7280",
marginBottom: 32,
},
statusContainer: {
flexDirection: "row",
alignItems: "center",
marginBottom: 24,
},
statusDot: {
width: 12,
height: 12,
borderRadius: 6,
marginRight: 8,
},
statusText: {
fontSize: 16,
fontWeight: "500",
color: "#374151",
},
speakingContainer: {
flexDirection: "row",
alignItems: "center",
marginBottom: 24,
},
speakingDot: {
width: 12,
height: 12,
borderRadius: 6,
marginRight: 8,
},
speakingText: {
fontSize: 14,
fontWeight: "500",
},
toolsContainer: {
backgroundColor: "#E5E7EB",
padding: 16,
borderRadius: 8,
marginBottom: 24,
width: "100%",
},
toolsTitle: {
fontSize: 14,
fontWeight: "600",
color: "#374151",
marginBottom: 8,
},
toolItem: {
fontSize: 12,
color: "#6B7280",
fontFamily: "monospace",
marginBottom: 4,
},
buttonContainer: {
width: "100%",
gap: 16,
},
button: {
backgroundColor: "#3B82F6",
paddingVertical: 16,
paddingHorizontal: 32,
borderRadius: 8,
alignItems: "center",
},
startButton: {
backgroundColor: "#10B981",
},
endButton: {
backgroundColor: "#EF4444",
},
disabledButton: {
backgroundColor: "#9CA3AF",
},
buttonText: {
color: "white",
fontSize: 16,
fontWeight: "600",
},
instructions: {
marginTop: 24,
fontSize: 14,
color: "#6B7280",
textAlign: "center",
lineHeight: 20,
},
feedbackContainer: {
marginTop: 24,
alignItems: "center",
},
feedbackLabel: {
fontSize: 16,
fontWeight: "500",
color: "#374151",
marginBottom: 12,
},
feedbackButtons: {
flexDirection: "row",
gap: 16,
},
likeButton: {
backgroundColor: "#10B981",
},
dislikeButton: {
backgroundColor: "#EF4444",
},
messagingContainer: {
marginTop: 24,
width: "100%",
},
messagingLabel: {
fontSize: 16,
fontWeight: "500",
color: "#374151",
marginBottom: 8,
},
textInput: {
backgroundColor: "#FFFFFF",
borderRadius: 8,
padding: 16,
minHeight: 100,
textAlignVertical: "top",
borderWidth: 1,
borderColor: "#D1D5DB",
marginBottom: 16,
},
messageButtons: {
flexDirection: "row",
gap: 16,
},
messageButton: {
backgroundColor: "#3B82F6",
flex: 1,
},
contextButton: {
backgroundColor: "#4F46E5",
flex: 1,
},
activityContainer: {
marginTop: 24,
alignItems: "center",
},
activityLabel: {
fontSize: 14,
color: "#6B7280",
marginBottom: 8,
textAlign: "center",
},
activityButton: {
backgroundColor: "#F59E0B",
},
});

नेटिव क्लाइंट टूल्स

ElevenLabs एजेंट बनाने का एक अहम हिस्सा एजेंट को फ़ंक्शनैलिटी का ऐक्सेस देना और उन्हें डाइनैमिक रूप से चलाने की अनुमति देना है। यह क्लाइंट टूल्स के ज़रिए किया जा सकता है।

अपने क्लाइंट टूल्स के लिए नई फ़ाइल बनाएं: ./utils/tools.ts और इसमें यह कोड जोड़ें:

./utils/tools.ts
import * as Battery from "expo-battery";
import * as Brightness from "expo-brightness";
const getBatteryLevel = async () => {
const batteryLevel = await Battery.getBatteryLevelAsync();
console.log("batteryLevel", batteryLevel);
if (batteryLevel === -1) {
return "Error: Device does not support retrieving the battery level.";
}
return batteryLevel;
};
const changeBrightness = ({ brightness }: { brightness: number }) => {
console.log("changeBrightness", brightness);
Brightness.setSystemBrightnessAsync(brightness);
return brightness;
};
const flashScreen = () => {
Brightness.setSystemBrightnessAsync(1);
setTimeout(() => {
Brightness.setSystemBrightnessAsync(0);
}, 200);
return "Successfully flashed the screen.";
};
export { getBatteryLevel, changeBrightness, flashScreen };

डाइनैमिक वेरिएबल्स

क्लाइंट टूल्स के अलावा, हम प्लेटफ़ॉर्म (web, iOS, Android) को पहले मैसेज और प्रॉम्प्ट, दोनों में डाइनैमिक वेरिएबल के रूप में भी डाल रहे हैं:

./App.tsx
// ...
const startConversation = async () => {
if (isStarting) return;
setIsStarting(true);
try {
await conversation.startSession({
agentId: process.env.EXPO_PUBLIC_AGENT_ID,
dynamicVariables: {
platform: Platform.OS,
},
});
} catch (error) {
console.error("Failed to start conversation:", error);
} finally {
setIsStarting(false);
}
};
// ...

एजेंट कॉन्फ़िगरेशन

1

ElevenLabs में साइन इन करें

elevenlabs.io पर जाएं और अपने अकाउंट में साइन इन करें।

2

नया एजेंट बनाएं

Agents Platform > Agents पर जाएं और खाली टेम्पलेट से नया एजेंट बनाएं।

3

पहला मैसेज सेट करें

पहला मैसेज सेट करें और प्लेटफ़ॉर्म के लिए डाइनैमिक वेरिएबल तय करें।

Hi there, woah, so cool that I'm running on {{platform}}. What can I help you with?
4

सिस्टम प्रॉम्प्ट सेट करें

सिस्टम प्रॉम्प्ट सेट करें। यहां भी डाइनैमिक वेरिएबल शामिल किए जा सकते हैं।

You are a helpful assistant running on {{platform}}. You have access to certain tools that allow you to check the user device battery level and change the display brightness. Use these tools if the user asks about them. Otherwise, just answer the question.
5

क्लाइंट टूल्स सेट अप करें

ये क्लाइंट टूल्स सेट अप करें:

  • नाम: getBatteryLevel
    • विवरण: डिवाइस की बैटरी का स्तर दशमलव प्रतिशत के रूप में प्राप्त करता है।
    • जवाब का इंतज़ार करें: true
    • रिस्पॉन्स टाइमआउट (सेकंड): 3
  • नाम: changeBrightness
    • विवरण: डिवाइस स्क्रीन की ब्राइटनेस बदलता है।
    • जवाब का इंतज़ार करें: true
    • रिस्पॉन्स टाइमआउट (सेकंड): 3
    • पैरामीटर:
      • डेटा टाइप: number
      • आइडेंटिफ़ायर: brightness
      • आवश्यक: true
      • वैल्यू टाइप: LLM Prompt
      • विवरण: 0 से 1 के बीच की संख्या, दोनों शामिल हैं, जो स्क्रीन की वांछित ब्राइटनेस दर्शाती है।
  • नाम: flashScreen
    • विवरण: स्क्रीन को तेज़ी से ऑन और ऑफ़ करता है।
    • जवाब का इंतज़ार करें: true
    • रिस्पॉन्स टाइमआउट (सेकंड): 3

ऐप चलाएं

इस ऐप के लिए कुछ नेटिव डिपेंडेंसी चाहिए, जो Expo Go में सपोर्ट नहीं होतीं। इसलिए आपको पहले ऐप को prebuild करना होगा और फिर उसे नेटिव डिवाइस पर चलाना होगा।

  • टर्मिनल 1:
    • npx expo prebuild --clean चलाएं
npx expo prebuild --clean
  • https पर Expo डेवलपमेंट सर्वर शुरू करने के लिए npx expo start --tunnel चलाएं।
npx expo start --tunnel
  • टर्मिनल 2:
    • अपने iOS डिवाइस पर ऐप चलाने के लिए npx expo run:ios --device चलाएं।
npx expo run:ios --device

अगले चरण