Voice call improvements: single call limit, hide debug tab, remove speaker toggle
Changes: - Add CallManager singleton to ensure only 1 call per device at a time - Hide Debug tab from production (href: null) - Remove speaker/earpiece toggle button (always use speaker) - Agent uses voice_ask API (fast ~1 sec latency) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
57577b42c9
commit
e3192ead12
@ -72,14 +72,11 @@ export default function TabLayout() {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{/* Debug tab - for testing audio/voice */}
|
||||
{/* Debug tab - hidden in production */}
|
||||
<Tabs.Screen
|
||||
name="debug"
|
||||
options={{
|
||||
title: 'Debug',
|
||||
tabBarIcon: ({ color, size }) => (
|
||||
<Feather name="terminal" size={22} color={color} />
|
||||
),
|
||||
href: null,
|
||||
}}
|
||||
/>
|
||||
{/* Hide explore tab */}
|
||||
|
||||
@ -288,19 +288,8 @@ export default function VoiceCallScreen() {
|
||||
<Ionicons name="call" size={32} color={AppColors.white} />
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Speaker/Earpiece toggle */}
|
||||
<TouchableOpacity
|
||||
style={[styles.controlButton, isSpeakerOn && styles.controlButtonActive]}
|
||||
onPress={handleToggleSpeaker}
|
||||
disabled={!isActive}
|
||||
>
|
||||
<Ionicons
|
||||
name={isSpeakerOn ? 'volume-high' : 'ear'}
|
||||
size={28}
|
||||
color={isSpeakerOn ? AppColors.success : AppColors.white}
|
||||
/>
|
||||
<Text style={styles.controlLabel}>{isSpeakerOn ? 'Speaker' : 'Earpiece'}</Text>
|
||||
</TouchableOpacity>
|
||||
{/* Empty placeholder for layout balance */}
|
||||
<View style={styles.controlButton} />
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
|
||||
@ -26,6 +26,7 @@ import {
|
||||
stopAudioSession,
|
||||
reconfigureAudioForPlayback,
|
||||
} from '@/utils/audioSession';
|
||||
import { callManager } from '@/services/callManager';
|
||||
|
||||
// Connection states
|
||||
export type ConnectionState =
|
||||
@ -103,6 +104,7 @@ export function useLiveKitRoom(options: UseLiveKitRoomOptions): UseLiveKitRoomRe
|
||||
const connectionIdRef = useRef(0);
|
||||
const isUnmountingRef = useRef(false);
|
||||
const appStateRef = useRef<AppStateStatus>(AppState.currentState);
|
||||
const callIdRef = useRef<string | null>(null);
|
||||
|
||||
// ===================
|
||||
// LOGGING FUNCTIONS
|
||||
@ -158,10 +160,27 @@ export function useLiveKitRoom(options: UseLiveKitRoomOptions): UseLiveKitRoomRe
|
||||
// Prevent multiple concurrent connection attempts
|
||||
const currentConnectionId = ++connectionIdRef.current;
|
||||
|
||||
// Generate unique call ID for this session
|
||||
const callId = `call-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||
callIdRef.current = callId;
|
||||
|
||||
logInfo('========== STARTING VOICE CALL ==========');
|
||||
logInfo(`User ID: ${userId}`);
|
||||
logInfo(`Platform: ${Platform.OS}`);
|
||||
logInfo(`Connection ID: ${currentConnectionId}`);
|
||||
logInfo(`Call ID: ${callId}`);
|
||||
|
||||
// Register with CallManager - this will disconnect any existing call
|
||||
logInfo('Registering call with CallManager...');
|
||||
await callManager.registerCall(callId, async () => {
|
||||
logInfo('CallManager requested disconnect (another call starting)');
|
||||
if (roomRef.current) {
|
||||
await roomRef.current.disconnect();
|
||||
roomRef.current = null;
|
||||
}
|
||||
await stopAudioSession();
|
||||
});
|
||||
logSuccess('Call registered with CallManager');
|
||||
|
||||
// Check if already connected
|
||||
if (roomRef.current) {
|
||||
@ -505,6 +524,13 @@ export function useLiveKitRoom(options: UseLiveKitRoomOptions): UseLiveKitRoomRe
|
||||
logInfo('========== DISCONNECTING ==========');
|
||||
setState('disconnecting');
|
||||
|
||||
// Unregister from CallManager
|
||||
if (callIdRef.current) {
|
||||
logInfo(`Unregistering call: ${callIdRef.current}`);
|
||||
callManager.unregisterCall(callIdRef.current);
|
||||
callIdRef.current = null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (roomRef.current) {
|
||||
logInfo('Disconnecting from room...');
|
||||
@ -603,6 +629,12 @@ export function useLiveKitRoom(options: UseLiveKitRoomOptions): UseLiveKitRoomRe
|
||||
|
||||
// Cleanup
|
||||
const cleanup = async () => {
|
||||
// Unregister from CallManager
|
||||
if (callIdRef.current) {
|
||||
callManager.unregisterCall(callIdRef.current);
|
||||
callIdRef.current = null;
|
||||
}
|
||||
|
||||
if (roomRef.current) {
|
||||
try {
|
||||
await roomRef.current.disconnect();
|
||||
|
||||
@ -191,9 +191,10 @@ class WellNuoLLM(llm.LLM):
|
||||
token = await self._ensure_token()
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
# Using ask_wellnuo_ai instead of voice_ask (same params, same response)
|
||||
# Using voice_ask - MUCH faster than ask_wellnuo_ai (1s vs 27s)
|
||||
# ask_wellnuo_ai has 20x higher latency and causes timeouts
|
||||
data = {
|
||||
"function": "ask_wellnuo_ai",
|
||||
"function": "voice_ask",
|
||||
"clientId": "MA_001",
|
||||
"user_name": WELLNUO_USER,
|
||||
"token": token,
|
||||
|
||||
111
services/callManager.ts
Normal file
111
services/callManager.ts
Normal file
@ -0,0 +1,111 @@
|
||||
/**
|
||||
* CallManager - Singleton to manage active voice calls
|
||||
*
|
||||
* Ensures only ONE voice call can be active at a time per device.
|
||||
* If a new call is started while another is active, the old one is disconnected first.
|
||||
*
|
||||
* This addresses the LiveKit concurrent agent jobs limit (5 per project).
|
||||
*/
|
||||
|
||||
type DisconnectCallback = () => Promise<void>;
|
||||
|
||||
class CallManager {
|
||||
private static instance: CallManager;
|
||||
private activeCallId: string | null = null;
|
||||
private disconnectCallback: DisconnectCallback | null = null;
|
||||
|
||||
private constructor() {
|
||||
// Singleton
|
||||
}
|
||||
|
||||
static getInstance(): CallManager {
|
||||
if (!CallManager.instance) {
|
||||
CallManager.instance = new CallManager();
|
||||
}
|
||||
return CallManager.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new call. If there's an existing call, disconnect it first.
|
||||
* @param callId Unique ID for this call
|
||||
* @param onDisconnect Callback to disconnect this call
|
||||
* @returns true if this call can proceed
|
||||
*/
|
||||
async registerCall(
|
||||
callId: string,
|
||||
onDisconnect: DisconnectCallback
|
||||
): Promise<boolean> {
|
||||
console.log(`[CallManager] Registering call: ${callId}`);
|
||||
|
||||
// If there's an active call, disconnect it first
|
||||
if (this.activeCallId && this.activeCallId !== callId) {
|
||||
console.log(
|
||||
`[CallManager] Active call exists (${this.activeCallId}), disconnecting...`
|
||||
);
|
||||
|
||||
if (this.disconnectCallback) {
|
||||
try {
|
||||
await this.disconnectCallback();
|
||||
console.log(`[CallManager] Previous call disconnected`);
|
||||
} catch (err) {
|
||||
console.error(`[CallManager] Error disconnecting previous call:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register the new call
|
||||
this.activeCallId = callId;
|
||||
this.disconnectCallback = onDisconnect;
|
||||
console.log(`[CallManager] Call ${callId} is now active`);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a call when it ends
|
||||
* @param callId The call ID to unregister
|
||||
*/
|
||||
unregisterCall(callId: string): void {
|
||||
if (this.activeCallId === callId) {
|
||||
console.log(`[CallManager] Unregistering call: ${callId}`);
|
||||
this.activeCallId = null;
|
||||
this.disconnectCallback = null;
|
||||
} else {
|
||||
console.log(
|
||||
`[CallManager] Call ${callId} is not active, ignoring unregister`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there's an active call
|
||||
*/
|
||||
hasActiveCall(): boolean {
|
||||
return this.activeCallId !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current active call ID
|
||||
*/
|
||||
getActiveCallId(): string | null {
|
||||
return this.activeCallId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force disconnect the active call (if any)
|
||||
*/
|
||||
async forceDisconnect(): Promise<void> {
|
||||
if (this.activeCallId && this.disconnectCallback) {
|
||||
console.log(`[CallManager] Force disconnecting call: ${this.activeCallId}`);
|
||||
try {
|
||||
await this.disconnectCallback();
|
||||
} catch (err) {
|
||||
console.error(`[CallManager] Error force disconnecting:`, err);
|
||||
}
|
||||
this.activeCallId = null;
|
||||
this.disconnectCallback = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const callManager = CallManager.getInstance();
|
||||
Loading…
x
Reference in New Issue
Block a user