using System; using System.Text.Json; using System.Threading; using xAiModels.Models; using xAiApi.Interfaces; using xCommons.Providers; using xCommons.Extensions; using xAiModels.Extensions; using xAiModels.Models.Dtos; using xExceptions.Constants; using System.Threading.Tasks; using xCommons.Configurations; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Http; using System.Collections.Generic; using xIdentityService.Interfaces; using Microsoft.Extensions.Logging; using xIdentityService.Controllers; using Microsoft.AspNetCore.Authorization; namespace xAiApi.Controllers { [Authorize] [Route("[controller]")] public abstract class XAiServiceControllerBase : XBaseIdentityApiV1Controller, IXAiServiceControllerBase where TAiService : IXAiServiceBase { private readonly TAiService aiService; protected XAiServiceControllerBase( ILogger logger, TAiService aiService, XAppConfiguration appConfiguration, IXIdentityProvider identityProvider, XValidationProvider validationProvider ) : base( logger, appConfiguration, identityProvider, validationProvider ) { this.aiService = aiService; } // #region Actions ... /// /// Ask Question and Get Response ... /// /// /// /// [AllowAnonymous] [HttpGet("AskText")] public virtual async Task> AskText( [FromQuery] string question, CancellationToken cancellationToken = default ) { // try { // // Validate ... ValidationProvider.NotEmpty(question); // var result = await aiService.AskTextAsync( prompt: question, cancellationToken: cancellationToken ); // return Ok(result); } catch (Exception ex) { // var result = GetExceptionActionResult(ex); return result; } } /// /// Ask Question and Get Response Stream ... /// /// /// /// [AllowAnonymous] [HttpGet("AskTextStream")] public virtual async Task AskTextStream( [FromQuery] string question, CancellationToken cancellationToken = default ) { // try { // // Validate ... ValidationProvider.NotEmpty(question); // // Configure SSE Response Headers ... Response.Headers["X-Accel-Buffering"] = "no"; Response.Headers["Connection"] = "keep-alive"; Response.Headers["Content-Type"] = "text/event-stream"; Response.Headers["Cache-Control"] = "no-cache, no-store"; // // Disable response buffering ... var responseBodyFeature = HttpContext.Features .Get(); responseBodyFeature?.DisableBuffering(); // // Stream Response ... var jsonOptions = new JsonSerializerOptions { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; // var enumerable = aiService.AskTextAsEnumerable( prompt: question, cancellationToken: cancellationToken ); // await foreach (var chunk in enumerable) { // // Skip empty chunks ... if (string.IsNullOrEmpty(chunk)) { continue; } // // Build SSE message ... // Format: data: \n\n // var payload = JsonSerializer.Serialize( // new { text = chunk }, // jsonOptions // ); var payload = chunk; // // Write SSE Event ... await Response.WriteAsync($"{payload}\n\n", cancellationToken); await Response.Body.FlushAsync(cancellationToken); } // await Response.Body.FlushAsync(cancellationToken); } catch (Exception) { await Response.Body.FlushAsync(); } } #endregion // #region Data Provided Actions ... /// /// Load AI Projects ... /// /// /// [HttpGet("LoadProjects")] public async Task>> LoadProjects( CancellationToken cancellationToken = default ) { // try { // // Validate ... // var userInfo = await GetUserInfo(); var connectionId = GetConnectionId(); // var result = await aiService.LoadProjects( ownerId: userInfo.UserId, connectionId: connectionId, cancellationToken: cancellationToken ); // return Ok(result); } catch (Exception ex) { // var result = GetExceptionActionResult(ex); return result; } } /// /// Ask Question and Get Response ... /// /// /// /// [HttpPost("Ask")] public async Task> Ask( [FromBody] XAiResponseRequest request, CancellationToken cancellationToken = default ) { // try { // // Validate ... if (!request.IsValid()) { XException.InvalidArgs.Throw(); } // var userInfo = await GetUserInfo(); var connectionId = GetConnectionId(); // // TODO: Reading Fiels Form Collection and Attach it ... var result = await aiService.AskAsync( files: null, prompt: request.Prompt, ownerId: userInfo.UserId, connectionId: connectionId, projectId: request.ProjectId, cancellationToken: cancellationToken, conversationId: request.ConversationId ); // return Ok(result); } catch (Exception ex) { // var result = GetExceptionActionResult(ex); return result; } } /// /// Ask Question and Get Response Stream ... /// /// /// /// [HttpPost("AskStream")] public async Task AskStream( [FromBody] XAiResponseRequest request, CancellationToken cancellationToken = default ) { // try { // // Validate ... if (!request.IsValid()) { XException.InvalidArgs.Throw(); } // var userInfo = await GetUserInfo(); var connectionId = GetConnectionId(); // // Configure SSE Response Headers ... Response.Headers["X-Accel-Buffering"] = "no"; Response.Headers["Connection"] = "keep-alive"; Response.Headers["Content-Type"] = "text/event-stream"; Response.Headers["Cache-Control"] = "no-cache, no-store"; // // Disable response buffering ... var responseBodyFeature = HttpContext.Features .Get(); responseBodyFeature?.DisableBuffering(); // // TODO: Reading Fiels Form Collection and Attach it ... var enumerable = aiService.AskAsEnumerable( files: null, prompt: request.Prompt, ownerId: userInfo.UserId, connectionId: connectionId, projectId: request.ProjectId, cancellationToken: cancellationToken, conversationId: request.ConversationId ); // await foreach (var resp in enumerable) { // // Skip Empty Responses ... if (resp.IsNullOrDefault()) { continue; } // var payload = resp.ToJSON(camelCase: true); // // Write SSE Event ... await Response.WriteAsync($"{payload}", cancellationToken); await Response.Body.FlushAsync(cancellationToken); } // await Response.Body.FlushAsync(cancellationToken); } catch (Exception) { await Response.Body.FlushAsync(); } } #endregion } }