- Update WellNuoLite submodule with Julia AI race condition fix - Add ultravoxService for voice call handling - Update voice.tsx with improved call flow - Update equipment tracking in beneficiary details - Clean up old data files - Add react-native-base64 type definitions - Add debug tools 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
246 lines
6.2 KiB
TypeScript
246 lines
6.2 KiB
TypeScript
/**
|
|
* Ultravox Voice AI Service
|
|
* Creates calls via Ultravox API and manages voice configuration
|
|
*/
|
|
|
|
// API Configuration
|
|
const ULTRAVOX_API_URL = 'https://api.ultravox.ai/api';
|
|
const ULTRAVOX_API_KEY = '4miSVLym.HF3lV9y4euiuzcEbPPTLHEugrOu4jpNU';
|
|
|
|
// Available voices
|
|
export interface UltravoxVoice {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
language: string;
|
|
gender: 'female' | 'male';
|
|
flag: string;
|
|
}
|
|
|
|
export const ULTRAVOX_VOICES: UltravoxVoice[] = [
|
|
{
|
|
id: 'Sarah',
|
|
name: 'Sarah',
|
|
description: 'Warm and friendly',
|
|
language: 'English',
|
|
gender: 'female',
|
|
flag: '🇺🇸',
|
|
},
|
|
{
|
|
id: 'Wendy',
|
|
name: 'Wendy',
|
|
description: 'Professional',
|
|
language: 'English',
|
|
gender: 'female',
|
|
flag: '🇺🇸',
|
|
},
|
|
{
|
|
id: 'Timothy',
|
|
name: 'Timothy',
|
|
description: 'Calm and clear',
|
|
language: 'English',
|
|
gender: 'male',
|
|
flag: '🇺🇸',
|
|
},
|
|
{
|
|
id: 'Larisa',
|
|
name: 'Larisa',
|
|
description: 'Russian voice',
|
|
language: 'Russian',
|
|
gender: 'female',
|
|
flag: '🇷🇺',
|
|
},
|
|
];
|
|
|
|
// Default voice
|
|
export const DEFAULT_VOICE = ULTRAVOX_VOICES[0]; // Sarah
|
|
|
|
// Tool definitions for function calling
|
|
export interface UltravoxTool {
|
|
temporaryTool: {
|
|
modelToolName: string;
|
|
description: string;
|
|
dynamicParameters?: Array<{
|
|
name: string;
|
|
location: string;
|
|
schema: {
|
|
type: string;
|
|
description: string;
|
|
};
|
|
required: boolean;
|
|
}>;
|
|
client?: Record<string, unknown>;
|
|
};
|
|
}
|
|
|
|
export const ULTRAVOX_TOOLS: UltravoxTool[] = [
|
|
{
|
|
temporaryTool: {
|
|
modelToolName: 'navigateToBeneficiaries',
|
|
description: 'Navigate to the beneficiaries list screen when user wants to see or manage their loved ones',
|
|
client: {},
|
|
},
|
|
},
|
|
{
|
|
temporaryTool: {
|
|
modelToolName: 'navigateToChat',
|
|
description: 'Navigate to the chat screen for text-based conversation',
|
|
client: {},
|
|
},
|
|
},
|
|
{
|
|
temporaryTool: {
|
|
modelToolName: 'navigateToProfile',
|
|
description: 'Navigate to the user profile settings screen',
|
|
client: {},
|
|
},
|
|
},
|
|
{
|
|
temporaryTool: {
|
|
modelToolName: 'checkBeneficiaryStatus',
|
|
description: 'Check the current wellness status of a specific beneficiary',
|
|
dynamicParameters: [
|
|
{
|
|
name: 'beneficiaryName',
|
|
location: 'PARAMETER_LOCATION_BODY',
|
|
schema: {
|
|
type: 'string',
|
|
description: 'The name of the beneficiary to check',
|
|
},
|
|
required: true,
|
|
},
|
|
],
|
|
client: {},
|
|
},
|
|
},
|
|
];
|
|
|
|
// System prompt generator
|
|
export function getSystemPrompt(beneficiaryName: string): string {
|
|
return `You are Julia, a compassionate and knowledgeable AI wellness assistant for WellNuo app.
|
|
Your role is to help caregivers monitor and understand the wellbeing of their loved ones.
|
|
|
|
Current beneficiary: ${beneficiaryName}
|
|
|
|
Guidelines:
|
|
- Be warm, empathetic, and supportive in your responses
|
|
- Provide clear, concise information about wellness metrics
|
|
- Offer practical suggestions for improving wellbeing
|
|
- If asked about specific health concerns, recommend consulting healthcare professionals
|
|
- You can navigate the app using available tools when the user requests it
|
|
- Keep responses conversational and natural for voice interaction
|
|
- Speak in a calm, reassuring tone
|
|
|
|
Remember: You're speaking with a caregiver who wants the best for their loved one.
|
|
Be supportive and helpful while maintaining appropriate boundaries about medical advice.`;
|
|
}
|
|
|
|
// API Response types
|
|
export interface CreateCallResponse {
|
|
callId: string;
|
|
joinUrl: string;
|
|
created: string;
|
|
ended?: string;
|
|
model: string;
|
|
voice: string;
|
|
firstSpeaker: string;
|
|
transcriptOptional: boolean;
|
|
recordingEnabled: boolean;
|
|
}
|
|
|
|
export interface UltravoxError {
|
|
error: string;
|
|
message: string;
|
|
}
|
|
|
|
/**
|
|
* Create a new Ultravox call
|
|
*/
|
|
export async function createCall(options: {
|
|
systemPrompt: string;
|
|
voice?: string;
|
|
tools?: UltravoxTool[];
|
|
firstSpeaker?: 'FIRST_SPEAKER_AGENT' | 'FIRST_SPEAKER_USER';
|
|
}): Promise<{ success: true; data: CreateCallResponse } | { success: false; error: string }> {
|
|
try {
|
|
const response = await fetch(`${ULTRAVOX_API_URL}/calls`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-API-Key': ULTRAVOX_API_KEY,
|
|
},
|
|
body: JSON.stringify({
|
|
systemPrompt: options.systemPrompt,
|
|
model: 'fixie-ai/ultravox',
|
|
voice: options.voice || 'Sarah',
|
|
firstSpeaker: options.firstSpeaker || 'FIRST_SPEAKER_AGENT',
|
|
selectedTools: options.tools || ULTRAVOX_TOOLS,
|
|
medium: { webRtc: {} },
|
|
recordingEnabled: false,
|
|
maxDuration: '1800s', // 30 minutes max
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json().catch(() => ({}));
|
|
console.error('[Ultravox] API error:', response.status, errorData);
|
|
return {
|
|
success: false,
|
|
error: errorData.message || `API error: ${response.status}`,
|
|
};
|
|
}
|
|
|
|
const data: CreateCallResponse = await response.json();
|
|
console.log('[Ultravox] Call created:', data.callId);
|
|
return { success: true, data };
|
|
} catch (error) {
|
|
console.error('[Ultravox] Create call error:', error);
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Failed to create call',
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get call details
|
|
*/
|
|
export async function getCall(callId: string): Promise<CreateCallResponse | null> {
|
|
try {
|
|
const response = await fetch(`${ULTRAVOX_API_URL}/calls/${callId}`, {
|
|
method: 'GET',
|
|
headers: {
|
|
'X-API-Key': ULTRAVOX_API_KEY,
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
return null;
|
|
}
|
|
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error('[Ultravox] Get call error:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* End a call
|
|
*/
|
|
export async function endCall(callId: string): Promise<boolean> {
|
|
try {
|
|
const response = await fetch(`${ULTRAVOX_API_URL}/calls/${callId}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'X-API-Key': ULTRAVOX_API_KEY,
|
|
},
|
|
});
|
|
|
|
return response.ok;
|
|
} catch (error) {
|
|
console.error('[Ultravox] End call error:', error);
|
|
return false;
|
|
}
|
|
}
|