WellNuo/services/ultravoxService.ts
Sergei d453126c89 feat: Room location picker + robster credentials
- Backend: Update Legacy API credentials to robster/rob2
- Frontend: ROOM_LOCATIONS with icons and legacyCode mapping
- Device Settings: Modal picker for room selection
- api.ts: Bidirectional conversion (code ↔ name)
- Various UI/UX improvements across screens

PRD-DEPLOYMENT.md completed (Score: 9/10)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-24 15:22:40 -08:00

241 lines
5.9 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(() => ({}));
return {
success: false,
error: errorData.message || `API error: ${response.status}`,
};
}
const data: CreateCallResponse = await response.json();
return { success: true, data };
} catch (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) {
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) {
return false;
}
}