refactor: optimize audio initialization and fix agent interactions

- Lazy initialize audio (on-demand instead of mount) in both player and Speechmatics hooks
- Add audio session teardown on stop for proper cleanup
- Implement agent audio recording with sendAgentAudio in AgentInputArea
- Fix Android navigation race condition in CreateAgentModal (navigate before closing)
- Add animations to global footer and agent screen (FadeIn/FadeOut)
- Auto-cancel stuck agents when sending new messages
- Fix permission request ID propagation from AgentManager to Session
- Apply theme background color via AppContainer wrapper

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2025-10-23 13:15:18 +02:00
co-authored by Claude
parent 10189514dd
commit 74fffcd8a3
11 changed files with 139 additions and 76 deletions
@@ -13,7 +13,7 @@ interface AgentInputAreaProps {
export function AgentInputArea({ agentId }: AgentInputAreaProps) {
const { theme } = useUnistyles();
const { agents, ws, sendAgentMessage, setAgentMode } = useSession();
const { agents, ws, sendAgentMessage, sendAgentAudio, setAgentMode } = useSession();
const { isRealtimeMode, startRealtime } = useRealtime();
const audioRecorder = useAudioRecorder();
@@ -54,10 +54,17 @@ export function AgentInputArea({ agentId }: AgentInputAreaProps) {
if (audioData) {
setIsProcessing(true);
// TODO: Send audio to agent
// For now, just log it
console.log("[AgentInput] Audio recorded:", audioData.size, "bytes");
setIsProcessing(false);
try {
// Send audio to agent for transcription and processing
await sendAgentAudio(agentId, audioData);
console.log("[AgentInput] Audio sent to agent");
} catch (error) {
console.error("[AgentInput] Failed to send audio:", error);
} finally {
setIsProcessing(false);
}
}
} catch (error) {
console.error("[AgentInput] Failed to stop recording:", error);
@@ -131,10 +131,14 @@ export function CreateAgentModal({
console.log("[CreateAgentModal] Agent created:", agentId);
setIsLoading(false);
setPendingRequestId(null);
handleClose();
// Navigate to the agent page
// Navigate to the agent page BEFORE closing modal
// This prevents race condition on Android where router.push() happens
// while the modal is unmounting, causing NullPointerException
router.push(`/agent/${agentId}`);
// Close modal after navigation starts
handleClose();
}
});
+25 -12
View File
@@ -1,42 +1,55 @@
import { View, Pressable } from "react-native";
import { usePathname, useLocalSearchParams } from "expo-router";
import { usePathname } from "expo-router";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { AudioLines } from "lucide-react-native";
import { useRealtime } from "@/contexts/realtime-context";
import { useSession } from "@/contexts/session-context";
import { RealtimeControls } from "./realtime-controls";
import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
export function GlobalFooter() {
const { theme } = useUnistyles();
const insets = useSafeAreaInsets();
const pathname = usePathname();
const params = useLocalSearchParams();
const { isRealtimeMode, startRealtime } = useRealtime();
const { ws } = useSession();
// Determine current screen type
const isAgentScreen = pathname?.startsWith("/agent/");
const isOrchestratorScreen = pathname === "/orchestrator";
const isHomeScreen = pathname === "/";
// If realtime is active, always show realtime controls
// Determine if footer should be visible
// Hidden when: on agent screen AND realtime is off
const shouldHide = isAgentScreen && !isRealtimeMode;
// If realtime is active, show realtime controls
if (isRealtimeMode) {
return (
<View style={[styles.container, { paddingBottom: insets.bottom }]}>
<Animated.View
entering={FadeIn.duration(400)}
exiting={FadeOut.duration(250)}
style={[styles.container, { paddingBottom: insets.bottom }]}
>
<RealtimeControls />
</View>
</Animated.View>
);
}
// If on agent screen and realtime is off, hide footer (agent has its own controls)
if (isAgentScreen) {
// For home and orchestrator screens, show centered realtime button
// On agent screens, don't render at all
if (shouldHide) {
return null;
}
// For home and orchestrator screens, show centered realtime button
return (
<View style={[styles.container, { paddingBottom: insets.bottom }]}>
<Animated.View
entering={FadeIn.duration(250)}
exiting={FadeOut.duration(250)}
style={[
styles.container,
{ paddingBottom: insets.bottom }
]}
>
<View style={styles.centeredButtonContainer}>
<Pressable
onPress={startRealtime}
@@ -49,7 +62,7 @@ export function GlobalFooter() {
<AudioLines size={24} color="white" />
</Pressable>
</View>
</View>
</Animated.View>
);
}