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:
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user