import React, { createContext, useCallback, useContext, useEffect, useRef, useState, } from "react"; import { Message, WebSocketMessage } from "../types"; import { useConnection } from "./ConnectionContext"; import { playAudio } from "../services/audio"; function generateId(): string { return Date.now().toString(36) + Math.random().toString(36).slice(2); } interface ChatContextValue { messages: Message[]; sendTextMessage: (text: string) => void; sendVoiceMessage: (audioUri: string, durationMs?: number) => void; clearMessages: () => void; } const ChatContext = createContext(null); export function ChatProvider({ children }: { children: React.ReactNode }) { const [messages, setMessages] = useState([]); const { sendTextMessage: wsSend, sendVoiceMessage: wsVoice, onMessageReceived } = useConnection(); const addMessage = useCallback((msg: Message) => { setMessages((prev) => [...prev, msg]); }, []); const updateMessageStatus = useCallback( (id: string, status: Message["status"]) => { setMessages((prev) => prev.map((m) => (m.id === id ? { ...m, status } : m)) ); }, [] ); // Handle incoming WebSocket messages useEffect(() => { onMessageReceived.current = (data: WebSocketMessage) => { if (data.type === "text") { const msg: Message = { id: generateId(), role: "assistant", type: "text", content: data.content, timestamp: Date.now(), status: "sent", }; setMessages((prev) => [...prev, msg]); } else if (data.type === "voice") { const msg: Message = { id: generateId(), role: "assistant", type: "voice", content: data.content ?? "", audioUri: data.audioBase64 ? `data:audio/mp4;base64,${data.audioBase64}` : undefined, timestamp: Date.now(), status: "sent", }; setMessages((prev) => [...prev, msg]); // Auto-play incoming voice messages if (msg.audioUri) { playAudio(msg.audioUri).catch(() => {}); } } }; return () => { onMessageReceived.current = null; }; }, [onMessageReceived]); const sendTextMessage = useCallback( (text: string) => { const id = generateId(); const msg: Message = { id, role: "user", type: "text", content: text, timestamp: Date.now(), status: "sending", }; addMessage(msg); const sent = wsSend(text); updateMessageStatus(id, sent ? "sent" : "error"); }, [wsSend, addMessage, updateMessageStatus] ); const sendVoiceMessage = useCallback( (audioUri: string, durationMs?: number) => { const id = generateId(); const msg: Message = { id, role: "user", type: "voice", content: "", audioUri, timestamp: Date.now(), status: "sending", duration: durationMs, }; addMessage(msg); // For now, send with empty base64 since we'd need expo-file-system to encode const sent = wsVoice("", "Voice message"); updateMessageStatus(id, sent ? "sent" : "error"); }, [wsVoice, addMessage, updateMessageStatus] ); const clearMessages = useCallback(() => { setMessages([]); }, []); return ( {children} ); } export function useChat() { const ctx = useContext(ChatContext); if (!ctx) throw new Error("useChat must be used within ChatProvider"); return ctx; }