From f7184c7b79c174e118b08e9e7778e6a1a7b98d71 Mon Sep 17 00:00:00 2001 From: Hadi Khazaee Asl Date: Thu, 30 Apr 2026 12:01:56 +0330 Subject: [PATCH] last works ... --- AI/Models/Dtos/XAiAskRequestDto.cs | 31 ++++++ AI/Services/XAiProvider.cs | 36 +++++-- Controllers/V1/AIController.cs | 153 +++++++++++++++++++++++++++++ 3 files changed, 210 insertions(+), 10 deletions(-) create mode 100644 AI/Models/Dtos/XAiAskRequestDto.cs diff --git a/AI/Models/Dtos/XAiAskRequestDto.cs b/AI/Models/Dtos/XAiAskRequestDto.cs new file mode 100644 index 0000000..2604079 --- /dev/null +++ b/AI/Models/Dtos/XAiAskRequestDto.cs @@ -0,0 +1,31 @@ +using Microsoft.AspNetCore.Mvc; +using xModels.Base; + +namespace xAiApi.AI.Models.Dtos +{ + /// + /// Ai Ask Request Data Transfer Object ... + /// + public class XAiAskRequestDto : XBaseDto + { + /// + /// Provided Prompt for Ask ... + /// + [FromQuery] + public string Prompt { get; set; } + + /// + /// Ai Selected Project Id, Optional ... + /// if null, Use Default Project for Authenticated User ... + /// + [FromQuery] + public string ProjectId { get; set; } = null; + + /// + /// Ai Selected Conversation of Project, Optional ... + /// if null, Create new Conversation in Selected Project ... + /// + [FromQuery] + public string ConversationId { get; set; } = null; + } +} \ No newline at end of file diff --git a/AI/Services/XAiProvider.cs b/AI/Services/XAiProvider.cs index d51f2cd..49adf5e 100644 --- a/AI/Services/XAiProvider.cs +++ b/AI/Services/XAiProvider.cs @@ -509,12 +509,17 @@ namespace xAiApi.AI.Services // // Getting Response Message from LLM ... - var chatResponseEnumerable = chatClient + var chatStream = chatClient .GetStreamingResponseAsync( options: chatOptions, messages: requirements.ChatHistory, cancellationToken: cancellationToken ); + isValid = !chatStream.IsNull(); + if (!isValid) + { + yield break; + } // // Create an Empty Message for ID ... @@ -531,31 +536,31 @@ namespace xAiApi.AI.Services saveChanges: true, item: responseMessage ); - + // - // Collecting Data through Enumeration ... + // Loop through Async Enumerable ... string content = string.Empty; var sequence = DateTime.UtcNow.ToTimestamp(); - await foreach (var data in chatResponseEnumerable) + await foreach(var msg in chatStream) { // // Crucially, check the cancellation token before continuing // Throwing OperationCanceledException is the standard way to signal cancellation. - if (cancellationToken.CanBeCanceled) + if (cancellationToken.IsCancellationRequested) { yield break; } // // Append recieved data to Content ... - content += data; + content += msg.Text; // // Crate Message Update Model ... var item = new XAiMessageUpdateDto { Sequense = sequence, - Content = data.Text, + Content = msg.Text, Id = responseMessage.Id, OwnerId = userInfo.UserId, UpdatedAt = DateTime.UtcNow, @@ -586,6 +591,7 @@ namespace xAiApi.AI.Services } // + // End Async Enumerable ... yield break; } #endregion @@ -976,7 +982,10 @@ namespace xAiApi.AI.Services { // // Complete Chat History ... - foreach (var msg in conversation.Messages) + var orderedByTimeMessage = conversation.Messages + .OrderBy(x => x.CreatedOn) + .AsEnumerable(); + foreach (var msg in orderedByTimeMessage) { // result.ChatHistory.Add( @@ -1025,11 +1034,18 @@ namespace xAiApi.AI.Services XException.InvalidData.Throw(); } + // + // Update Conversation ... + var conversationEntity = await aiConversationRepository + .GetAsync(id: result.AiConversation.Id); + conversation = mapper.Map(conversationEntity); + result.AiConversation = conversation; + // result.ChatHistory.Add( new ChatMessage( - content: prompt, - role: XAiChatRole.User.ToChatRole() + content: promptAiMessage.Content, + role: promptAiMessage.Role.ToChatRole() ) ); diff --git a/Controllers/V1/AIController.cs b/Controllers/V1/AIController.cs index 239c43c..621be03 100644 --- a/Controllers/V1/AIController.cs +++ b/Controllers/V1/AIController.cs @@ -8,11 +8,13 @@ using Microsoft.Extensions.AI; using Microsoft.Extensions.Logging; using OpenAI.Realtime; using xAiApi.AI.Interfaces; +using xAiApi.AI.Models.Dtos; using xAiApi.Base; using xAiModels.Models; using xCommons.Configurations; using xCommons.Extensions; using xCommons.Providers; +using xExceptions.Constants; using xIdentityHelper; using xIdentityService.Constants; using xIdentityService.Interfaces; @@ -51,6 +53,62 @@ namespace xAiApi.Controllers.V1 // #region Actions ... + /// + /// As a Message from Ai ... + /// + /// + /// + /// + /// + /// + [HttpGet("Ask")] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task> Ask( + [FromQuery] XAiAskRequestDto request, + CancellationToken cancellationToken = default + ) + { + // + // Do ... + try + { + // + // Validate Model ... + var isValid = + ModelState.IsValid && + !request.IsNull() && + !request.Prompt.IsNullOrEmpty(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + // Retrieve User Info ... + var userInfo = await GetUserInfo(); + + // + // Retrieve Result ... + var result = await aiService + .Ask( + userInfo: userInfo, + prompt: request.Prompt, + projectId: request.ProjectId, + cancellationToken: cancellationToken, + conversationId: request.ConversationId + ); + + // + return Ok(result.ToDynamicObject()); + } + catch (Exception ex) + { + // + var result = GetExceptionActionResult(ex); + return result; + } + } + /// /// As a Message from Ai ... /// @@ -96,6 +154,98 @@ namespace xAiApi.Controllers.V1 } } + /// + /// Ask a Message from Ai using Streamin Pattern ... + /// + /// + /// + /// + /// + /// + [HttpGet("AskStream")] + [Authorize(Policy = XPolicies.EnabledUser)] + public async Task AskStream( + [FromQuery] XAiAskRequestDto request, + CancellationToken cancellationToken = default + ) + { + // + // Do ... + try + { + // + // Validate Model ... + var isValid = + ModelState.IsValid && + !request.IsNull() && + !request.Prompt.IsNullOrEmpty(); + if (!isValid) + { + XException.InvalidArgs.Throw(); + } + + // + // Adding Content Type ... + Response.Headers + .Append("Content-Type", "text/event-stream"); + + // + // Retrieve User Info ... + var userInfo = await GetUserInfo(); + + // + // Access Async Enumerable ... + var stream = aiService + .AskStream( + userInfo: userInfo, + prompt: request.Prompt, + projectId: request.ProjectId, + cancellationToken: cancellationToken, + conversationId: request.ConversationId + ); + + // + // Loop through Enumerable ... + await foreach (var message in stream) + { + // + // Converts Model to Json String ... + var json = message.ToJSON(); + + // + // Generates Json byte[] ... + var bytes = json.ToBytes(); + + // + // Write Bytes to Stream ... + await Response.Body.WriteAsync( + bytes, + 0, + bytes.Length, + cancellationToken + ); + + // + // Flushing Stream ... + await Response.Body.FlushAsync(cancellationToken); + } + + // + // Closing Connection ... + await Response.Body.FlushAsync(); + } + catch (Exception) + { } + } + + /// + /// Ask a Message from Ai using Streamin Pattern ... + /// + /// + /// + /// + /// + /// [HttpGet("AskAIStream")] [Authorize(Policy = XPolicies.EnabledUser)] public async Task AskAIStream( @@ -124,6 +274,9 @@ namespace xAiApi.Controllers.V1 conversationId: conversationId, cancellationToken: cancellationToken ); + + // + // Loop through Enumerable ... await foreach (var message in stream) { //