last works ...

This commit is contained in:
2026-04-30 12:01:56 +03:30
parent 2b5e09912f
commit f7184c7b79
3 changed files with 210 additions and 10 deletions
+31
View File
@@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Mvc;
using xModels.Base;
namespace xAiApi.AI.Models.Dtos
{
/// <summary>
/// Ai Ask Request Data Transfer Object ...
/// </summary>
public class XAiAskRequestDto : XBaseDto
{
/// <summary>
/// Provided Prompt for Ask ...
/// </summary>
[FromQuery]
public string Prompt { get; set; }
/// <summary>
/// Ai Selected Project Id, Optional ...
/// if null, Use Default Project for Authenticated User ...
/// </summary>
[FromQuery]
public string ProjectId { get; set; } = null;
/// <summary>
/// Ai Selected Conversation of Project, Optional ...
/// if null, Create new Conversation in Selected Project ...
/// </summary>
[FromQuery]
public string ConversationId { get; set; } = null;
}
}
+25 -9
View File
@@ -509,12 +509,17 @@ namespace xAiApi.AI.Services
// //
// Getting Response Message from LLM ... // Getting Response Message from LLM ...
var chatResponseEnumerable = chatClient var chatStream = chatClient
.GetStreamingResponseAsync( .GetStreamingResponseAsync(
options: chatOptions, options: chatOptions,
messages: requirements.ChatHistory, messages: requirements.ChatHistory,
cancellationToken: cancellationToken cancellationToken: cancellationToken
); );
isValid = !chatStream.IsNull();
if (!isValid)
{
yield break;
}
// //
// Create an Empty Message for ID ... // Create an Empty Message for ID ...
@@ -533,29 +538,29 @@ namespace xAiApi.AI.Services
); );
// //
// Collecting Data through Enumeration ... // Loop through Async Enumerable ...
string content = string.Empty; string content = string.Empty;
var sequence = DateTime.UtcNow.ToTimestamp(); var sequence = DateTime.UtcNow.ToTimestamp();
await foreach (var data in chatResponseEnumerable) await foreach(var msg in chatStream)
{ {
// //
// Crucially, check the cancellation token before continuing // Crucially, check the cancellation token before continuing
// Throwing OperationCanceledException is the standard way to signal cancellation. // Throwing OperationCanceledException is the standard way to signal cancellation.
if (cancellationToken.CanBeCanceled) if (cancellationToken.IsCancellationRequested)
{ {
yield break; yield break;
} }
// //
// Append recieved data to Content ... // Append recieved data to Content ...
content += data; content += msg.Text;
// //
// Crate Message Update Model ... // Crate Message Update Model ...
var item = new XAiMessageUpdateDto var item = new XAiMessageUpdateDto
{ {
Sequense = sequence, Sequense = sequence,
Content = data.Text, Content = msg.Text,
Id = responseMessage.Id, Id = responseMessage.Id,
OwnerId = userInfo.UserId, OwnerId = userInfo.UserId,
UpdatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow,
@@ -586,6 +591,7 @@ namespace xAiApi.AI.Services
} }
// //
// End Async Enumerable ...
yield break; yield break;
} }
#endregion #endregion
@@ -976,7 +982,10 @@ namespace xAiApi.AI.Services
{ {
// //
// Complete Chat History ... // 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( result.ChatHistory.Add(
@@ -1025,11 +1034,18 @@ namespace xAiApi.AI.Services
XException.InvalidData.Throw(); XException.InvalidData.Throw();
} }
//
// Update Conversation ...
var conversationEntity = await aiConversationRepository
.GetAsync(id: result.AiConversation.Id);
conversation = mapper.Map<XAiConversationDto>(conversationEntity);
result.AiConversation = conversation;
// //
result.ChatHistory.Add( result.ChatHistory.Add(
new ChatMessage( new ChatMessage(
content: prompt, content: promptAiMessage.Content,
role: XAiChatRole.User.ToChatRole() role: promptAiMessage.Role.ToChatRole()
) )
); );
+153
View File
@@ -8,11 +8,13 @@ using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using OpenAI.Realtime; using OpenAI.Realtime;
using xAiApi.AI.Interfaces; using xAiApi.AI.Interfaces;
using xAiApi.AI.Models.Dtos;
using xAiApi.Base; using xAiApi.Base;
using xAiModels.Models; using xAiModels.Models;
using xCommons.Configurations; using xCommons.Configurations;
using xCommons.Extensions; using xCommons.Extensions;
using xCommons.Providers; using xCommons.Providers;
using xExceptions.Constants;
using xIdentityHelper; using xIdentityHelper;
using xIdentityService.Constants; using xIdentityService.Constants;
using xIdentityService.Interfaces; using xIdentityService.Interfaces;
@@ -51,6 +53,62 @@ namespace xAiApi.Controllers.V1
// //
#region Actions ... #region Actions ...
/// <summary>
/// As a Message from Ai ...
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpGet("Ask")]
[Authorize(Policy = XPolicies.EnabledUser)]
public async Task<ActionResult<XAiMessageDto>> 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;
}
}
/// <summary> /// <summary>
/// As a Message from Ai ... /// As a Message from Ai ...
/// </summary> /// </summary>
@@ -96,6 +154,98 @@ namespace xAiApi.Controllers.V1
} }
} }
/// <summary>
/// Ask a Message from Ai using Streamin Pattern ...
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[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)
{ }
}
/// <summary>
/// Ask a Message from Ai using Streamin Pattern ...
/// </summary>
/// <param name="prompt"></param>
/// <param name="projectId"></param>
/// <param name="conversationId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
[HttpGet("AskAIStream")] [HttpGet("AskAIStream")]
[Authorize(Policy = XPolicies.EnabledUser)] [Authorize(Policy = XPolicies.EnabledUser)]
public async Task AskAIStream( public async Task AskAIStream(
@@ -124,6 +274,9 @@ namespace xAiApi.Controllers.V1
conversationId: conversationId, conversationId: conversationId,
cancellationToken: cancellationToken cancellationToken: cancellationToken
); );
//
// Loop through Enumerable ...
await foreach (var message in stream) await foreach (var message in stream)
{ {
// //