using System.Net.WebSockets; using System.Text.Json; using System.Text; namespace BLAIzor.Services { public class OpenAiRealtimeService { private readonly IConfiguration _configuration; private readonly string _apiKey; private readonly Uri _openAiUri = new Uri("wss://api.openai.com/v1/realtime?model=gpt-4o-mini-realtime-preview"); private Action _callback; private string streamedHtmlContent = string.Empty; public OpenAiRealtimeService(IConfiguration configuration) { _configuration = configuration; _apiKey = GetApiKey(); } public void RegisterCallback(Action callback) { _callback = callback; } private string GetApiKey() { return _configuration?.GetSection("OpenAI")?.GetValue("ApiKey") ?? string.Empty; } public async Task GetFullChatGPTResponseAsync(string sessionId, string systemMessage, string userMessage, string? assistantMessage = null) { streamedHtmlContent = string.Empty; using (ClientWebSocket webSocket = new ClientWebSocket()) { webSocket.Options.SetRequestHeader("Authorization", $"Bearer {_apiKey}"); webSocket.Options.SetRequestHeader("OpenAI-Beta", "realtime=v1"); try { await webSocket.ConnectAsync(_openAiUri, CancellationToken.None); Console.Write("\n✅ Connected to OpenAI WebSocket..." + "\n"); // Step 1: Wait for session.created event (return immediately) string sessionResponse = await ReceiveSingleMessage(webSocket); Console.Write("\n🟢 Session Created: " + sessionResponse + "\n"); // Step 2: Send user message (conversation.item.create) string messageId = Guid.NewGuid().ToString("N").Substring(0, 32); var userMessagePayload = new { type = "conversation.item.create", previous_item_id = (string?)null, item = new { id = messageId, type = "message", role = "user", content = new[] { new { type = "input_text", text = userMessage } } } }; string userMessageJson = JsonSerializer.Serialize(userMessagePayload); Console.Write("\n📤 Sending user message: " + userMessageJson + "\n"); await webSocket.SendAsync(Encoding.UTF8.GetBytes(userMessageJson), WebSocketMessageType.Text, true, CancellationToken.None); // Step 3: Wait for message confirmation (return immediately) string messageResponse = await ReceiveSingleMessage(webSocket); Console.Write("\n🟢 Message Created: " + messageResponse + "\n"); // Step 4: Send response.create to trigger AI response var responseRequestPayload = new { type = "response.create", response = new { modalities = new[] { "text" }, instructions = systemMessage, temperature = 0.6, max_output_tokens = 4096 } }; string responseRequestJson = JsonSerializer.Serialize(responseRequestPayload); Console.Write("\n📤 Sending response.create: " + responseRequestJson + "\n"); await webSocket.SendAsync(Encoding.UTF8.GetBytes(responseRequestJson), WebSocketMessageType.Text, true, CancellationToken.None); // Step 5: Keep listening for AI's full response (STREAMING) string aiResponse = await ReceiveStreamingMessages(sessionId, webSocket, false); Console.Write("\n🔵 AI Final Response: " + aiResponse + "\n"); return aiResponse; } catch (Exception ex) { Console.Write($"❌ Error: {ex.Message}" + "\n"); return "Error communicating with OpenAI Realtime API"; } finally { await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing connection", CancellationToken.None); } } } public async Task GetChatGPTResponseAsync(string sessionId, string systemMessage, string userMessage, string? assistantMessage = null) { streamedHtmlContent = string.Empty; using (ClientWebSocket webSocket = new ClientWebSocket()) { webSocket.Options.SetRequestHeader("Authorization", $"Bearer {_apiKey}"); webSocket.Options.SetRequestHeader("OpenAI-Beta", "realtime=v1"); try { await webSocket.ConnectAsync(_openAiUri, CancellationToken.None); Console.Write("\n✅ Connected to OpenAI WebSocket..." + "\n"); // Step 1: Wait for session.created event (return immediately) string sessionResponse = await ReceiveSingleMessage(webSocket); Console.Write("\n🟢 Session Created: " + sessionResponse + "\n"); // Step 2: Send user message (conversation.item.create) string messageId = Guid.NewGuid().ToString("N").Substring(0, 32); var userMessagePayload = new { type = "conversation.item.create", previous_item_id = (string?)null, item = new { id = messageId, type = "message", role = "user", content = new[] { new { type = "input_text", text = userMessage } } } }; string userMessageJson = JsonSerializer.Serialize(userMessagePayload); Console.Write("\n📤 Sending user message: " + userMessageJson + "\n"); await webSocket.SendAsync(Encoding.UTF8.GetBytes(userMessageJson), WebSocketMessageType.Text, true, CancellationToken.None); // Step 3: Wait for message confirmation (return immediately) string messageResponse = await ReceiveSingleMessage(webSocket); Console.Write("\n🟢 Message Created: " + messageResponse + "\n"); // Step 4: Send response.create to trigger AI response var responseRequestPayload = new { type = "response.create", response = new { modalities = new[] { "text" }, instructions = systemMessage, temperature = 0.6, max_output_tokens = 4096 } }; string responseRequestJson = JsonSerializer.Serialize(responseRequestPayload); Console.Write("\n📤 Sending response.create: " + responseRequestJson + "\n"); await webSocket.SendAsync(Encoding.UTF8.GetBytes(responseRequestJson), WebSocketMessageType.Text, true, CancellationToken.None); // Step 5: Keep listening for AI's full response (STREAMING) string aiResponse = await ReceiveStreamingMessages(sessionId, webSocket); //Console.Write("\n🔵 AI Final Response: " + aiResponse + "\n"); //return aiResponse; } catch (Exception ex) { Console.Write($"❌ Error: {ex.Message}" + "\n"); _callback?.Invoke(sessionId, "Error communicating with OpenAI Realtime API"); //return "Error communicating with OpenAI Realtime API"; } finally { await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing connection", CancellationToken.None); } } } private async Task ReceiveSingleMessage(ClientWebSocket webSocket) { var buffer = new byte[8192]; var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); string message = Encoding.UTF8.GetString(buffer, 0, result.Count); Console.Write("\n 🔵 Received WebSocket Message: " + message); return message; } private async Task ReceiveStreamingMessages(string sessionId, ClientWebSocket webSocket, bool invokeCallback = true) { var buffer = new byte[8192]; var responseBuilder = new StringBuilder(); while (true) { var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); string message = Encoding.UTF8.GetString(buffer, 0, result.Count); Console.Write("\n 🔵 Received WebSocket Message: " + message); using JsonDocument doc = JsonDocument.Parse(message); JsonElement root = doc.RootElement; if (root.TryGetProperty("type", out JsonElement typeElement)) { string eventType = typeElement.GetString() ?? ""; switch (eventType) { case "conversation.item.created": Console.WriteLine("🟡 Assistant response item created, continuing..."); continue; case "response.output_item.added": Console.WriteLine("🟡 Assistant started responding..."); continue; case "response.text.delta": if (root.TryGetProperty("delta", out JsonElement deltaElement)) { string textPart = deltaElement.GetString() ?? ""; if (!string.IsNullOrEmpty(textPart)) { //Console.WriteLine($"🔠 Appending Text: {textPart}"); streamedHtmlContent += textPart; if (invokeCallback) _callback?.Invoke(sessionId, streamedHtmlContent); responseBuilder.Append(textPart); } } break; case "response.done": Console.WriteLine("✅ Assistant finished responding."); // Check if response failed if (root.TryGetProperty("response", out JsonElement responseElement) && responseElement.TryGetProperty("status", out JsonElement statusElement) && statusElement.GetString() == "failed") { // Extract error message if available string errorMessage = "Unknown error occurred."; if (responseElement.TryGetProperty("status_details", out JsonElement statusDetails) && statusDetails.TryGetProperty("error", out JsonElement errorElement)) { errorMessage = errorElement.GetProperty("message").GetString() ?? errorMessage; _callback?.Invoke(sessionId, errorMessage); } Console.WriteLine($"❌ Error: {errorMessage}"); return $"Error: {errorMessage}"; } return responseBuilder.ToString(); } } } } } }