275 lines
12 KiB
C#
275 lines
12 KiB
C#
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<string, string> _callback;
|
|
private string streamedHtmlContent = string.Empty;
|
|
|
|
public OpenAiRealtimeService(IConfiguration configuration)
|
|
{
|
|
_configuration = configuration;
|
|
_apiKey = GetApiKey();
|
|
}
|
|
|
|
public void RegisterCallback(Action<string, string> callback)
|
|
{
|
|
_callback = callback;
|
|
}
|
|
|
|
private string GetApiKey()
|
|
{
|
|
return _configuration?.GetSection("OpenAI")?.GetValue<string>("ApiKey") ?? string.Empty;
|
|
}
|
|
|
|
public async Task<string> 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<string> ReceiveSingleMessage(ClientWebSocket webSocket)
|
|
{
|
|
var buffer = new byte[8192];
|
|
|
|
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
|
|
string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
|
|
|
|
Console.Write("\n 🔵 Received WebSocket Message: " + message);
|
|
return message;
|
|
}
|
|
|
|
private async Task<string> 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<byte>(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();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|