This commit is contained in:
2026-08-14 00:01:17 +03:30
parent 842feac905
commit 73fb9a01b8
3 changed files with 774 additions and 10 deletions
+108
View File
@@ -0,0 +1,108 @@
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xCommons.Extensions;
namespace xAiModels.Extensions
{
/// <summary>
/// Extensions Methods for XAiModels ...
/// </summary>
public static class XAiModelsExtensions
{
/// <summary>
/// Validate a Project for Add Or Update ...
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static bool IsValidForAddOrUpdate(this XAiProject source)
{
//
var result =
!source.IsNullOrDefault() &&
!source.OwnerId.IsNullOrEmpty() &&
!source.Title.IsNullOrEmpty() &&
!source.Prompt.IsNullOrEmpty();
//
return result;
}
public static bool IsValidForAddOrUpdate(this XAiProjectDto source)
{
//
var result =
!source.IsNullOrDefault() &&
!source.OwnerId.IsNullOrEmpty() &&
!source.Title.IsNullOrEmpty() &&
!source.Prompt.IsNullOrEmpty();
//
return result;
}
/// <summary>
/// Validate a Conversation for Add Or Update ...
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static bool IsValidForAddOrUpdate(this XAiConversation source)
{
//
var result =
!source.IsNullOrDefault() &&
!source.OwnerId.IsNullOrEmpty() &&
!source.Title.IsNullOrEmpty() &&
!source.ProjectId.IsNull() &&
!source.ProjectId.IsDefaultGuid();
//
return result;
}
public static bool IsValidForAddOrUpdate(this XAiConversationDto source)
{
//
var result =
!source.IsNullOrDefault() &&
!source.OwnerId.IsNullOrEmpty() &&
!source.Title.IsNullOrEmpty() &&
!source.ProjectId.IsNull() &&
!source.ProjectId.IsDefaultGuid();
//
return result;
}
/// <summary>
/// Validate a Message for Add Or Update ...
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
public static bool IsValidForAddOrUpdate(this XAiMessage source)
{
//
var result =
!source.IsNullOrDefault() &&
!source.OwnerId.IsNullOrEmpty() &&
source.Role.IsValid() &&
!source.ConversationId.IsNull() &&
!source.ConversationId.IsDefaultGuid() &&
!source.Content.IsNullOrEmpty();
//
return result;
}
public static bool IsValidForAddOrUpdate(this XAiMessageDto source)
{
//
var result =
!source.IsNullOrDefault() &&
!source.OwnerId.IsNullOrEmpty() &&
source.Role.IsValid() &&
!source.ConversationId.IsNull() &&
!source.ConversationId.IsDefaultGuid() &&
!source.Content.IsNullOrEmpty();
//
return result;
}
}
}
+94
View File
@@ -1,3 +1,9 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using xAiModels.Models.Dtos;
namespace xAiModels.Interfaces
{
/// <summary>
@@ -5,6 +11,94 @@ namespace xAiModels.Interfaces
/// </summary>
public interface IXAiDataProvider
{
//
#region Loaders ...
/// <summary>
/// Load Projects List of Specified Owner ...
/// </summary>
/// <param name="ownerId"></param>
/// <param name="fillConversations"></param>
/// <param name="fillConversationMessages"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<XAiProjectDto>> LoadProjects(
string ownerId,
bool fillConversations = false,
bool fillConversationMessages = false,
CancellationToken cancellationToken = default
);
/// <summary>
/// Load List of Conversations for Specified Project ...
/// </summary>
/// <param name="projectId"></param>
/// <param name="fillMessages"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<XAiConversationDto>> LoadConversations(
Guid projectId,
bool fillMessages = false,
CancellationToken cancellationToken = default
);
/// <summary>
/// Load Messages of Specified Conversation ...
/// </summary>
/// <param name="conversationId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<XAiMessageDto>> LoadMessages(
Guid conversationId,
CancellationToken cancellationToken = default
);
#endregion
//
#region Creator/Adders ...
/// <summary>
/// Create Project ...
/// </summary>
/// <param name="ownerId"></param>
/// <param name="item"></param>
/// <param name="connectionId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XAiProjectDto> CreateProject(
string ownerId,
XAiProjectDto item = null,
string connectionId = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Create Conversation ...
/// </summary>
/// <param name="projectId"></param>
/// <param name="item"></param>
/// <param name="connectionId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XAiConversationDto> CreateConversation(
Guid projectId,
XAiConversationDto item = null,
string connectionId = null,
CancellationToken cancellationToken = default
);
/// <summary>
/// Add Message ...
/// </summary>
/// <param name="conversationId"></param>
/// <param name="item"></param>
/// <param name="connectionId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<XAiMessageDto> AddMessage(
Guid conversationId,
XAiMessageDto item,
string connectionId = null,
CancellationToken cancellationToken = default
);
#endregion
}
}
+567 -5
View File
@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using MongoDB.Driver.Core.Connections;
using xAiModels.Constants;
using xAiModels.Extensions;
using xAiModels.Interfaces;
using xAiModels.Interfaces.Dtos;
using xAiModels.Models.Dtos;
@@ -21,6 +24,8 @@ namespace xAiModels.Providers
/// </summary>
public class XAiDataProvider : IXAiDataProvider
{
//
#region Properties ...
private readonly IXStringProvider stringProvider;
private readonly XAppConfiguration appConfiguration;
private readonly IHubContext<XAiDataProviderHub> hub;
@@ -30,6 +35,7 @@ namespace xAiModels.Providers
private readonly IXAiConversationRepositoryService conversationRepositoryService;
private readonly IXAiConversationTitleResourceProvider conversationTitleResourceProvider;
private readonly IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider;
#endregion
public XAiDataProvider(
IXStringProvider stringProvider,
@@ -55,7 +61,415 @@ namespace xAiModels.Providers
}
//
#region Actions ...
#region Loaders ...
/// <summary>
/// Load Projects List of Specified Owner ...
/// </summary>
/// <param name="ownerId"></param>
/// <param name="fillConversations"></param>
/// <param name="fillConversationMessages"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XAiProjectDto>> LoadProjects(
string ownerId,
bool fillConversations = false,
bool fillConversationMessages = false,
CancellationToken cancellationToken = default
)
{
//
// Validate Args ...
if (ownerId.IsNullOrEmpty())
{
XException.InvalidArgs.Throw();
}
//
// Handle Default Project and Conversation ...
bool hasDefault = await HasDefaultProject(
ownerId: ownerId,
cancellationToken: cancellationToken
);
if (!hasDefault)
{
//
await CreateDefaultProject(
ownerId: ownerId,
connectionId: null,
cancellationToken: cancellationToken
);
}
//
// Retrieve Project List ...
var result = await projectRepositoryService.FindManyAsync(
includeBuilder: null,
ignoreSoftDeleteds: true,
fillConversations: fillConversations,
predicate: x => x.OwnerId == ownerId,
cancellationToken: cancellationToken,
orderBuilder: x => x.OrderBy(y => y.CreatedOn),
fillConversationMessages: fillConversationMessages
);
//
return result;
}
/// <summary>
/// Load List of Conversations for Specified Project ...
/// </summary>
/// <param name="projectId"></param>
/// <param name="fillMessages"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XAiConversationDto>> LoadConversations(
Guid projectId,
bool fillMessages = false,
CancellationToken cancellationToken = default
)
{
//
// Validate Args ...
if (projectId.IsNull() ||
projectId.IsDefaultGuid()
)
{
XException.InvalidArgs.Throw();
}
//
// Retrieve Conversation's List ...
var result = await conversationRepositoryService.FindManyAsync(
includeBuilder: null,
ignoreSoftDeleteds: false,
fillMessages: fillMessages,
cancellationToken: cancellationToken,
predicate: x => x.ProjectId == projectId,
orderBuilder: x => x.OrderBy(y => y.CreatedOn)
);
//
return result;
}
/// <summary>
/// Load Messages of Specified Conversation ...
/// </summary>
/// <param name="conversationId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<IEnumerable<XAiMessageDto>> LoadMessages(
Guid conversationId,
CancellationToken cancellationToken = default
)
{
//
// Validate Args ...
if (conversationId.IsNull() ||
conversationId.IsDefaultGuid()
)
{
XException.InvalidArgs.Throw();
}
//
// Retrieve Message Lists of Specified Conversation ...
var result = await messageRepositoryService.FindManyAsync(
includeBuilder: null,
ignoreSoftDeleteds: true,
cancellationToken: cancellationToken,
orderBuilder: x => x.OrderBy(y => y.CreatedOn),
predicate: x => x.ConversationId == conversationId
);
//
return result;
}
#endregion
//
#region Creator/Adders ...
public async Task<XAiProjectDto> CreateProject(
string ownerId,
XAiProjectDto item = null,
string connectionId = null,
CancellationToken cancellationToken = default
)
{
//
// Validate Args ...
if (ownerId.IsNullOrEmpty())
{
XException.InvalidArgs.Throw();
}
//
// Check Project Provided or not ...
var hasProject = !item.IsNullOrDefault();
//
// Normalize Provided Project ...
if (hasProject)
{
//
item.CreatedOn = DateTime.UtcNow;
//
// Validate Item ...
if (!item.IsValidForAddOrUpdate())
{
XException.InvalidData.Throw();
}
}
//
XAiProjectDto result = null;
//
// Create Provided Project ...
if (hasProject)
{
//
result = await projectRepositoryService.AddAsync(
item: item,
saveChanges: true,
fillConversations: false,
fillConversationMessages: false,
cancellationToken: cancellationToken
);
//
// Handling Send Push Notification ...
if (!result.IsNullOrDefault() &&
!connectionId.IsNullOrEmpty()
)
{
//
await SendPush(
connectionId: connectionId,
cancellationToken: cancellationToken,
payLoad: item.ToJSON(camelCase: true),
action: XAiDataProviderHubActions.ProjectCreated.GetStringValue()
);
}
}
//
// Check Default Project ...
else
{
//
// Check Default Project Exists or not ...
var hasDefault = await HasDefaultProject(
ownerId: ownerId,
cancellationToken: cancellationToken
);
//
// If Default Project Exists, Throw Exception ...
if (hasDefault)
{
XException.InvalidArgs.Throw();
}
//
// Create and Retrive Default Project ...
result = await GetDefaultProject(
ownerId: ownerId,
connectionId: connectionId,
cancellationToken: cancellationToken
);
}
//
// Validate ...
if (result.IsNullOrDefault())
{
XException.ActionFailed.Throw();
}
//
return result;
}
public async Task<XAiConversationDto> CreateConversation(
Guid projectId,
XAiConversationDto item = null,
string connectionId = null,
CancellationToken cancellationToken = default
)
{
//
// Validate Args ...
if (projectId.IsNull() ||
projectId.IsDefaultGuid())
{
XException.InvalidArgs.Throw();
}
//
// Handle Proejct ...
XAiProjectDto project = await projectRepositoryService.GetAsync(
id: projectId,
includeBuilder: null,
ignoreSoftDeleteds: true,
fillConversations: false,
fillConversationMessages: false,
cancellationToken: cancellationToken
);
if (project.IsNullOrDefault())
{
XException.NotFound.Throw();
}
//
// Check Conversation Provided or not ...
var hasConversation = !item.IsNullOrDefault();
//
// Normalize Conversation if Provided ...
if (hasConversation)
{
//
item.ProjectId = project.Id;
item.CreatedOn = DateTime.UtcNow;
//
// Validate Item ...
if (!item.IsValidForAddOrUpdate())
{
XException.InvalidData.Throw();
}
}
//
XAiConversationDto result = null;
//
// Create Provided Conversation ...
if (hasConversation)
{
//
result = await conversationRepositoryService.AddAsync(
item: item,
saveChanges: true,
fillMessages: false,
cancellationToken: cancellationToken
);
//
// Handling Send Push Notification ...
if (!result.IsNullOrDefault() &&
!connectionId.IsNullOrEmpty()
)
{
//
await SendPush(
connectionId: connectionId,
cancellationToken: cancellationToken,
payLoad: item.ToJSON(camelCase: true),
action: XAiDataProviderHubActions.ConversationCreated.GetStringValue()
);
}
}
//
// Check Default Conversation ...
else
{
//
// Check Default Conversation Exists or not ...
var hasDefault = await HasDefaultConversation(
projectId: project.Id,
ownerId: project.OwnerId,
cancellationToken: cancellationToken
);
//
// If Default Project Exists, Throw Exception ...
if (hasDefault)
{
XException.InvalidArgs.Throw();
}
//
// Create and Retrive Default Conversation ...
result = await GetDefaultConversation(
projectId: project.Id,
ownerId: project.OwnerId,
connectionId: connectionId,
cancellationToken: cancellationToken
);
}
//
// Validate ...
if (result.IsNullOrDefault())
{
XException.ActionFailed.Throw();
}
//
return result;
}
public async Task<XAiMessageDto> AddMessage(
Guid conversationId,
XAiMessageDto item,
string connectionId = null,
CancellationToken cancellationToken = default
)
{
//
// Validate Args ...
if (item.IsNullOrDefault() ||
conversationId.IsNull() ||
conversationId.IsDefaultGuid()
)
{
XException.InvalidArgs.Throw();
}
//
// Normalize ...
item.CreatedOn = DateTime.UtcNow;
item.ConversationId = conversationId;
//
// Validate Item ...
if (!item.IsValidForAddOrUpdate())
{
XException.InvalidData.Throw();
}
//
var result = await messageRepositoryService.AddAsync(
item: item,
saveChanges: true,
cancellationToken: cancellationToken
);
//
// Handling Send Push Notification ...
if (!result.IsNullOrDefault() &&
!connectionId.IsNullOrEmpty()
)
{
//
await SendPush(
connectionId: connectionId,
cancellationToken: cancellationToken,
payLoad: item.ToJSON(camelCase: true),
action: XAiDataProviderHubActions.MessageCreated.GetStringValue()
);
}
//
return result;
}
#endregion
//
#region Updaters ...
#endregion
//
@@ -64,10 +478,12 @@ namespace xAiModels.Providers
/// Check Default Project Exists or not ...
/// </summary>
/// <param name="ownerId"></param>
/// <param name="connectionId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task<bool> HasDefaultProject(
string ownerId,
string connectionId = null,
CancellationToken cancellationToken = default
)
{
@@ -117,6 +533,7 @@ namespace xAiModels.Providers
// Check Default Project Exists or noit ...
var isExists = await HasDefaultProject(
ownerId: ownerId,
connectionId: connectionId,
cancellationToken: cancellationToken
);
if (!isExists)
@@ -273,10 +690,14 @@ namespace xAiModels.Providers
/// Check Default Conversation Exists or not ...
/// </summary>
/// <param name="ownerId"></param>
/// <param name="projectId"></param>
/// <param name="connectionId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task<bool> HasDefaultConversation(
string ownerId,
Guid projectId,
string connectionId = null,
CancellationToken cancellationToken = default
)
{
@@ -288,9 +709,40 @@ namespace xAiModels.Providers
return result;
}
//
// Prepare Project ...
XAiProjectDto project = null;
if (projectId.IsNull() ||
projectId.IsDefaultGuid()
)
{
//
project = await GetDefaultProject(
ownerId: ownerId,
connectionId: connectionId,
cancellationToken: cancellationToken
);
}
else
{
//
project = await projectRepositoryService.GetAsync(
id: project.Id,
includeBuilder: null,
ignoreSoftDeleteds: true,
fillConversations: false,
fillConversationMessages: false,
cancellationToken: cancellationToken
);
}
if (project.IsNullOrDefault())
{
XException.NotFound.Throw();
}
//
// Check Default Owner Exists or not ...
result = !(await stringProvider.FindOne(
var resource = await stringProvider.FindOne(
cancellationToken: cancellationToken,
predicate: x =>
x.ResourceTitle
@@ -303,8 +755,23 @@ namespace xAiModels.Providers
x.TranslatedValue
.ToNormalString()
.Contains(XAiDataProviderConstants.XDefaultAiConversationSuffix)
);
result = !resource.IsNullOrDefault();
if (!result)
{
return result;
}
//
result = !(await conversationRepositoryService.FindOneAsync(
cancellationToken: cancellationToken,
predicate: x =>
x.ProjectId == projectId &&
x.Title == resource.ResourceTitle
))
.IsNullOrDefault();
//
return result;
}
@@ -312,11 +779,13 @@ namespace xAiModels.Providers
/// Check Default Conversation Exists or not ...
/// </summary>
/// <param name="ownerId"></param>
/// <param name="projectId"></param>
/// <param name="connectionId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task CreateDefaultConversation(
string ownerId,
Guid projectId,
string connectionId = null,
CancellationToken cancellationToken = default
)
@@ -333,17 +802,41 @@ namespace xAiModels.Providers
var defaultLang = appConfiguration.DefaultLanguage;
//
// Handle Default Project ...
var project = await GetDefaultProject(
// Prepare Project ...
XAiProjectDto project = null;
if (projectId.IsNull() ||
projectId.IsDefaultGuid()
)
{
//
project = await GetDefaultProject(
ownerId: ownerId,
connectionId: connectionId,
cancellationToken: cancellationToken
);
}
else
{
//
project = await projectRepositoryService.GetAsync(
id: project.Id,
includeBuilder: null,
ignoreSoftDeleteds: true,
fillConversations: false,
fillConversationMessages: false,
cancellationToken: cancellationToken
);
}
if (project.IsNullOrDefault())
{
XException.NotFound.Throw();
}
//
// Check Default Owner Exists or not ...
isValid = await HasDefaultConversation(
ownerId: ownerId,
projectId: project.Id,
cancellationToken: cancellationToken
);
if (isValid)
@@ -412,19 +905,61 @@ namespace xAiModels.Providers
/// Create it if not Handled ...
/// </summary>
/// <param name="ownerId"></param>
/// <param name="projectId"></param>
/// <param name="connectionId"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task<XAiConversationDto> GetDefaultConversation(
string ownerId,
Guid projectId,
string connectionId = null,
CancellationToken cancellationToken = default
)
{
//
// Validate Args ...
if (ownerId.IsNullOrEmpty())
{
XException.InvalidArgs.Throw();
}
//
// Prepare Project ...
XAiProjectDto project = null;
if (projectId.IsNull() ||
projectId.IsDefaultGuid()
)
{
//
project = await GetDefaultProject(
ownerId: ownerId,
connectionId: connectionId,
cancellationToken: cancellationToken
);
}
else
{
//
project = await projectRepositoryService.GetAsync(
id: project.Id,
includeBuilder: null,
ignoreSoftDeleteds: true,
fillConversations: false,
fillConversationMessages: false,
cancellationToken: cancellationToken
);
}
if (project.IsNullOrDefault())
{
XException.NotFound.Throw();
}
//
// Check Default Conversation Exists or noit ...
var isExists = await HasDefaultConversation(
ownerId: ownerId,
projectId: project.Id,
connectionId: connectionId,
cancellationToken: cancellationToken
);
if (!isExists)
@@ -432,12 +967,14 @@ namespace xAiModels.Providers
//
await CreateDefaultConversation(
ownerId: ownerId,
projectId: project.Id,
connectionId: connectionId,
cancellationToken: cancellationToken
);
}
//
// Detect Conversation Resource ...
var resource = await stringProvider.FindOne(
cancellationToken: cancellationToken,
predicate: x =>
@@ -458,13 +995,15 @@ namespace xAiModels.Providers
}
//
// Find Default Conversation ...
var result = await conversationRepositoryService.FindOneAsync(
cancellationToken: cancellationToken,
predicate: x => x.Title
.Equals(
resource.ResourceTitle,
StringComparison.InvariantCultureIgnoreCase
)
) &&
x.ProjectId == project.Id
);
if (result.IsNullOrDefault())
{
@@ -475,6 +1014,29 @@ namespace xAiModels.Providers
return result;
}
/// <summary>
/// Normalize and Validate Language ...
/// </summary>
/// <param name="language"></param>
/// <returns></returns>
private string NormalizeAndValidateLanguage(string language)
{
//
if (language.IsNullOrEmpty())
{
language = appConfiguration.DefaultLanguage;
}
//
if (language.IsNullOrEmpty())
{
XException.InvalidArgs.Throw();
}
//
return language;
}
/// <summary>
/// Send Custom Push Message ...
/// </summary>