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)
{
//