using System.Net.Http.Headers; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using Meezi.Core.Interfaces; using Meezi.Core.Platform; namespace Meezi.API.Services; public interface IOpenAiChatService { Task IsConfiguredForCoffeeAdvisorAsync(CancellationToken cancellationToken = default); Task CompleteJsonAsync(string systemPrompt, string userPrompt, CancellationToken cancellationToken = default); } public class OpenAiChatService : IOpenAiChatService { private static readonly JsonSerializerOptions JsonOpts = new() { PropertyNameCaseInsensitive = true, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; private readonly IHttpClientFactory _httpClientFactory; private readonly IPlatformRuntimeConfig _platform; private readonly IConfiguration _configuration; private readonly ILogger _logger; public OpenAiChatService( IHttpClientFactory httpClientFactory, IPlatformRuntimeConfig platform, IConfiguration configuration, ILogger logger) { _httpClientFactory = httpClientFactory; _platform = platform; _configuration = configuration; _logger = logger; } public async Task IsConfiguredForCoffeeAdvisorAsync(CancellationToken cancellationToken = default) { if (!await IsCoffeeAdvisorEnabledAsync(cancellationToken)) return false; return !string.IsNullOrWhiteSpace(await GetApiKeyAsync(cancellationToken)); } public async Task CompleteJsonAsync( string systemPrompt, string userPrompt, CancellationToken cancellationToken = default) { var apiKey = await GetApiKeyAsync(cancellationToken); if (string.IsNullOrWhiteSpace(apiKey)) return null; var model = await GetModelAsync(cancellationToken); var body = JsonSerializer.Serialize(new { model, temperature = 0.4, response_format = new { type = "json_object" }, messages = new object[] { new { role = "system", content = systemPrompt }, new { role = "user", content = userPrompt } } }, JsonOpts); var client = _httpClientFactory.CreateClient("OpenAi"); using var req = new HttpRequestMessage(HttpMethod.Post, "https://api.openai.com/v1/chat/completions") { Content = new StringContent(body, Encoding.UTF8, "application/json") }; req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); using var res = await client.SendAsync(req, cancellationToken); if (!res.IsSuccessStatusCode) { _logger.LogWarning("OpenAI chat failed with status {Status}", res.StatusCode); return null; } await using var stream = await res.Content.ReadAsStreamAsync(cancellationToken); var doc = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken); return doc.RootElement .GetProperty("choices")[0] .GetProperty("message") .GetProperty("content") .GetString(); } private async Task IsCoffeeAdvisorEnabledAsync(CancellationToken cancellationToken) { var enabled = await _platform.GetAsync(PlatformIntegrationKeys.OpenAiEnabled, cancellationToken); if (enabled is "false") return false; var feature = await _platform.GetAsync(PlatformIntegrationKeys.OpenAiCoffeeAdvisorEnabled, cancellationToken); return feature is not "false"; } private async Task GetApiKeyAsync(CancellationToken cancellationToken) { var fromDb = await _platform.GetAsync(PlatformIntegrationKeys.OpenAiApiKey, cancellationToken); if (!string.IsNullOrWhiteSpace(fromDb)) return fromDb.Trim(); return _configuration["OpenAI:ApiKey"]?.Trim(); } private async Task GetModelAsync(CancellationToken cancellationToken) { var fromDb = await _platform.GetAsync(PlatformIntegrationKeys.OpenAiModel, cancellationToken); if (!string.IsNullOrWhiteSpace(fromDb)) return fromDb.Trim(); return _configuration["OpenAI:Model"]?.Trim() ?? "gpt-4o-mini"; } }