using System; using xModels.Dtos; 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; using System.Linq; 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 Loaders ... /// /// Retrieve AI Projects ... /// /// /// /// [HttpGet("Projects")] public async Task>> LoadProjects( [FromQuery] bool includeDefaults = false, CancellationToken cancellationToken = default ) { // try { // var userInfo = await GetUserInfo(); var connectionId = GetConnectionId(); // var result = await aiService.LoadProjects( ownerId: userInfo.UserId, connectionId: connectionId, includeDefaults: includeDefaults, cancellationToken: cancellationToken ); // return Ok(result); } catch (Exception ex) { // var result = GetExceptionActionResult(ex); return result; } } /// /// Retrieve Specified AI Project's Conversations ... /// /// /// /// [HttpGet("Projects/{projectId}/Conversations")] public async Task>> LoadConversations( [FromRoute] Guid projectId, CancellationToken cancellationToken = default ) { // try { // // Validate ... if (projectId.IsDefaultGuid()) { XException.InvalidArgs.Throw(); } // var userInfo = await GetUserInfo(); var connectionId = GetConnectionId(); // var result = await aiService.LoadConversations( projectId: projectId, connectionId: connectionId, cancellationToken: cancellationToken ); // return Ok(result); } catch (Exception ex) { // var result = GetExceptionActionResult(ex); return result; } } /// /// Retrieve Specified AI Conversation's Messages ... /// /// /// /// [HttpGet("Conversations/{conversationId}/Messages")] public async Task>> LoadMessages( [FromRoute] Guid conversationId, CancellationToken cancellationToken = default ) { // try { // // Validate ... if (conversationId.IsDefaultGuid()) { XException.InvalidArgs.Throw(); } // var result = await aiService.LoadMessages( conversationId: conversationId, cancellationToken: cancellationToken ); // return Ok(result); } catch (Exception ex) { // var result = GetExceptionActionResult(ex); return result; } } #endregion // #region Queries ... /// /// Query Projects ... /// /// /// /// /// /// [HttpGet("Projects/Query")] public async Task>> QueryProjects( [FromQuery] XQuery query, CancellationToken cancellationToken = default ) { // try { // var userInfo = await GetUserInfo(); var connectionId = GetConnectionId(); // var result = await aiService.QueryProjects( query: query, ownerId: userInfo.UserId, cancellationToken: cancellationToken ); // return Ok(result); } catch (Exception ex) { // var result = GetExceptionActionResult(ex); return result; } } /// /// Query Conversations ... /// /// /// /// /// [HttpGet("Projects/{projectId}/Conversations/Query")] public async Task>> QueryConversations( [FromRoute] Guid projectId, [FromQuery] XQuery query, CancellationToken cancellationToken = default ) { // try { // var userInfo = await GetUserInfo(); var connectionId = GetConnectionId(); // var result = await aiService.QueryConversations( query: query, projectId: projectId, ownerId: userInfo.UserId, cancellationToken: cancellationToken ); // return Ok(result); } catch (Exception ex) { // var result = GetExceptionActionResult(ex); return result; } } /// /// Query Messages ... /// /// /// /// /// [HttpGet("Conversations/{conversationId}/Messages/Query")] public async Task>> QueryMessages( [FromRoute] Guid conversationId, [FromQuery] XQuery query, CancellationToken cancellationToken = default ) { // try { // var userInfo = await GetUserInfo(); var connectionId = GetConnectionId(); // var result = await aiService.QueryMessages( query: query, ownerId: userInfo.UserId, conversationId: conversationId, cancellationToken: cancellationToken ); // return Ok(result); } catch (Exception ex) { // var result = GetExceptionActionResult(ex); return result; } } #endregion // #region Creators ... /// /// Create an AI Project ... /// /// /// /// [HttpPost("Projects")] public async Task> CreateProject( [FromBody] XAiProjectDto item, CancellationToken cancellationToken = default ) { // try { // // Validate ... if (item.IsNullOrDefault()) { XException.InvalidArgs.Throw(); } // var userInfo = await GetUserInfo(); var connectionId = GetConnectionId(); // // Update Required Params ... item.OwnerId = userInfo.UserId; if (!item.IsValidForAddOrUpdate()) { XException.InvalidData.Throw(); } // var result = await aiService.CreateProject( item: item, ownerId: userInfo.UserId, connectionId: connectionId, cancellationToken: cancellationToken ); // return Ok(result); } catch (Exception ex) { // var result = GetExceptionActionResult(ex); return result; } } /// /// Create an AI Conversation ... /// /// /// /// /// [HttpPost("Projects/{projectId}/Conversations")] public async Task> CreateConversation( [FromRoute] Guid projectId, [FromBody] XAiConversationDto item, CancellationToken cancellationToken = default ) { // try { // // Validate ... if (projectId.IsDefaultGuid() || item.IsNullOrDefault()) { XException.InvalidArgs.Throw(); } // var userInfo = await GetUserInfo(); var connectionId = GetConnectionId(); // // Update Required Params ... item.OwnerId = userInfo.UserId; if (!item.IsValidForAddOrUpdate()) { XException.InvalidData.Throw(); } // var result = await aiService.CreateConversation( item: item, projectId: projectId, ownerId: userInfo.UserId, connectionId: connectionId, cancellationToken: cancellationToken ); // return Ok(result); } catch (Exception ex) { // var result = GetExceptionActionResult(ex); return result; } } #endregion // #region Content Extraction ... /// /// Extract Files Content ... /// /// /// /// /// [HttpPost("ExtractContent")] [Consumes("multipart/form-data")] public async Task> ExtractContent( [FromForm] IFormFileCollection files, [FromForm] string extractors = null, CancellationToken cancellationToken = default ) { // try { // // Validate ... if (!files.HasChild()) { XException.InvalidArgs.Throw(); } // var userInfo = await GetUserInfo(); var connectionId = GetConnectionId(); // string[] extractorArr = null; if (!extractors.IsNullOrEmpty()) { extractorArr = [.. extractors.ParseListString()]; } // var result = await aiService.ExtractContent( files: files, extractors: extractorArr, cancellationToken: cancellationToken ); // return Ok(result); } catch (Exception ex) { var result = GetExceptionActionResult(ex); return result; } } #endregion // #region Data Actions ... /// /// 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(); } } /// /// Ask Question with Files and Get Response ... /// /// /// /// [HttpPost("AskWithFiles")] [Consumes("multipart/form-data")] public async Task> AskWithFiles( [FromForm] XAiResponseRequest request, [FromForm] IFormFileCollection files, CancellationToken cancellationToken = default ) { // try { // // Validate ... if (!request.IsValid()) { XException.InvalidArgs.Throw(); } // var userInfo = await GetUserInfo(); var connectionId = GetConnectionId(); // var result = await aiService.AskAsync( files: files, userInfo: userInfo, 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; } } #endregion } }