diff --git a/Controllers/XAiServiceControllerBase.cs b/Controllers/XAiServiceControllerBase.cs index 85bf350..9eebff1 100644 --- a/Controllers/XAiServiceControllerBase.cs +++ b/Controllers/XAiServiceControllerBase.cs @@ -1,4 +1,5 @@ using System; +using xModels.Dtos; using System.Text.Json; using System.Threading; using xAiModels.Models; @@ -164,14 +165,16 @@ namespace xAiApi.Controllers #endregion // - #region Data Provided Actions ... + #region Loaders ... /// /// Retrieve AI Projects ... /// + /// /// /// [HttpGet("Projects")] public async Task>> LoadProjects( + [FromQuery] bool includeDefaults = false, CancellationToken cancellationToken = default ) { @@ -186,6 +189,7 @@ namespace xAiApi.Controllers var result = await aiService.LoadProjects( ownerId: userInfo.UserId, connectionId: connectionId, + includeDefaults: includeDefaults, cancellationToken: cancellationToken ); @@ -282,7 +286,132 @@ namespace xAiApi.Controllers 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 ... /// @@ -333,7 +462,7 @@ namespace xAiApi.Controllers // var result = GetExceptionActionResult(ex); return result; - } + } } /// @@ -355,7 +484,7 @@ namespace xAiApi.Controllers { // // Validate ... - if (projectId.IsDefaultGuid() || + if (projectId.IsDefaultGuid() || item.IsNullOrDefault()) { XException.InvalidArgs.Throw(); @@ -390,9 +519,12 @@ namespace xAiApi.Controllers // var result = GetExceptionActionResult(ex); return result; - } + } } + #endregion + // + #region Data Actions ... /// /// Ask Question and Get Response ... /// diff --git a/Interfaces/IXAiServiceBase.cs b/Interfaces/IXAiServiceBase.cs index 7869fb4..582b6aa 100644 --- a/Interfaces/IXAiServiceBase.cs +++ b/Interfaces/IXAiServiceBase.cs @@ -1,4 +1,5 @@ using System; +using xModels.Dtos; using System.Net.Http; using System.Threading; using xAiModels.Models; @@ -92,11 +93,13 @@ namespace xAiApi.Interfaces /// /// /// + /// /// /// Task> LoadProjects( string ownerId, string connectionId = null, + bool includeDefaults = false, CancellationToken cancellationToken = default ); @@ -123,6 +126,49 @@ namespace xAiApi.Interfaces Guid conversationId, CancellationToken cancellationToken = default ); + + /// + /// Query Projects ... + /// + /// + /// + /// + /// + Task> QueryProjects( + string ownerId, + XQuery query, + CancellationToken cancellationToken = default + ); + + /// + /// Query Conversations ... + /// + /// + /// + /// + /// + /// + Task> QueryConversations( + string ownerId, + Guid projectId, + XQuery query, + CancellationToken cancellationToken = default + ); + + /// + /// Query Messages ... + /// + /// + /// + /// + /// + /// + Task> QueryMessages( + string ownerId, + Guid conversationId, + XQuery query, + CancellationToken cancellationToken = default + ); /// /// Create an AI Project ... diff --git a/Interfaces/IXAiServiceControllerBase.cs b/Interfaces/IXAiServiceControllerBase.cs index c733f1f..e073f63 100644 --- a/Interfaces/IXAiServiceControllerBase.cs +++ b/Interfaces/IXAiServiceControllerBase.cs @@ -1,4 +1,5 @@ using System; +using xModels.Dtos; using System.Threading; using xAiModels.Models; using xAiModels.Models.Dtos; @@ -38,14 +39,16 @@ namespace xAiApi.Interfaces #endregion // - #region Data Provided Actions ... + #region Loaders ... /// /// Retrieve AI Projects ... /// + /// /// /// [HttpGet("Projects")] Task>> LoadProjects( + [FromQuery] bool includeDefaults = false, CancellationToken cancellationToken = default ); @@ -72,7 +75,55 @@ namespace xAiApi.Interfaces [FromRoute] Guid conversationId, CancellationToken cancellationToken = default ); + #endregion + // + #region Queries ... + /// + /// Query Projects ... + /// + /// + /// + /// + /// + /// + [HttpGet("Projects/Query")] + Task>> QueryProjects( + [FromQuery] XQuery query, + CancellationToken cancellationToken = default + ); + + /// + /// Query Conversations ... + /// + /// + /// + /// + /// + [HttpGet("Projects/{projectId}/Conversations/Query")] + Task>> QueryConversations( + [FromRoute] Guid projectId, + [FromQuery] XQuery query, + CancellationToken cancellationToken = default + ); + + /// + /// Query Messages ... + /// + /// + /// + /// + /// + [HttpGet("Conversations/{conversationId}/Messages/Query")] + Task>> QueryMessages( + [FromRoute] Guid conversationId, + [FromQuery] XQuery query, + CancellationToken cancellationToken = default + ); + #endregion + + // + #region Creators ... /// /// Create an AI Project ... /// @@ -98,7 +149,10 @@ namespace xAiApi.Interfaces [FromBody] XAiConversationDto item, CancellationToken cancellationToken = default ); + #endregion + // + #region Data Actions ... /// /// Ask Question and Get Response ... /// diff --git a/Providers/XAIServiceBase.cs b/Providers/XAIServiceBase.cs index 63acec0..30092fe 100644 --- a/Providers/XAIServiceBase.cs +++ b/Providers/XAIServiceBase.cs @@ -2,6 +2,7 @@ using System; using OpenAI; using OllamaSharp; using System.Linq; +using xModels.Dtos; using System.Net.Http; using System.Threading; using xAiApi.Constants; @@ -352,11 +353,13 @@ namespace xAiApi.Providers /// /// /// + /// /// /// public async Task> LoadProjects( string ownerId, string connectionId = null, + bool includeDefaults = false, CancellationToken cancellationToken = default ) { @@ -377,42 +380,50 @@ namespace xAiApi.Providers ); // - // Here we have to fill Conversations of Default Project ... - var defaultProjectId = result.GetDafualtProject(ownerId); - var defaultProject = result.FirstOrDefault(x => x.Id == defaultProjectId); - result = result.Where(x => x.Id != defaultProjectId); + // Handle Defaults Including ... + if (includeDefaults) + { + // + // Here we have to fill Conversations of Default Project ... + var defaultProjectId = result.GetDafualtProject(ownerId); + var defaultProject = result.FirstOrDefault(x => x.Id == defaultProjectId); + result = result.Where(x => x.Id != defaultProjectId); - // - // Reading Conversations of Specified Default Project ... - var defaultProjectConversations = await dataProvider.LoadConversations( - fillMessages: false, - connectionId: connectionId, - projectId: defaultProjectId, - cancellationToken: cancellationToken - ); - var defaultConversationId = defaultProjectConversations.GetDefaultConversation( - ownerId: ownerId, - projectId: defaultProjectId - ); - var defaultConversation = defaultProjectConversations.First(x => x.Id == defaultConversationId); - defaultProjectConversations = defaultProjectConversations.Where(x => x.Id != defaultConversationId); + // + // Reading Conversations of Specified Default Project ... + var defaultProjectConversations = await dataProvider.LoadConversations( + fillMessages: false, + connectionId: connectionId, + projectId: defaultProjectId, + cancellationToken: cancellationToken + ); + var defaultConversationId = defaultProjectConversations.GetDefaultConversation( + ownerId: ownerId, + projectId: defaultProjectId + ); + var defaultConversation = defaultProjectConversations.First(x => x.Id == defaultConversationId); + defaultProjectConversations = defaultProjectConversations.Where(x => x.Id != defaultConversationId); - // - // Reading Default Conversations Messages ... - var defaultConversationMessages = await dataProvider.LoadMessages( - cancellationToken: cancellationToken, - conversationId: defaultConversationId - ); + // + // Reading Default Conversations Messages ... + var defaultConversationMessages = await dataProvider.LoadMessages( + cancellationToken: cancellationToken, + conversationId: defaultConversationId + ); - // - defaultConversation.Messages = [.. defaultConversationMessages]; - defaultProjectConversations = [defaultConversation, .. defaultProjectConversations]; - result = [defaultProject, .. result]; + // + defaultConversation.Messages = [.. defaultConversationMessages.OrderByDescending(m => m.CreatedOn)]; + defaultProjectConversations = [defaultConversation, .. defaultProjectConversations]; + defaultProject.Conversations = [.. defaultProjectConversations]; + result = [defaultProject, .. result]; + } // return result; } + + /// /// Load Conversations of Specified AI Project ... /// @@ -470,7 +481,7 @@ namespace xAiApi.Providers ); // - return result; + return result; } /// @@ -647,6 +658,7 @@ namespace xAiApi.Providers Role = XAiChatRole.User, CreatedOn = DateTime.UtcNow, ConversationId = conversationId, + ConversationTitle = conversation.Title }; promptMessage = await dataProvider.AddMessage( item: promptMessage, @@ -678,9 +690,10 @@ namespace xAiApi.Providers { Content = answer, OwnerId = ownerId, - Role = XAiChatRole.System, CreatedOn = DateTime.UtcNow, - ConversationId = conversationId + Role = XAiChatRole.Assistant, + ConversationId = conversationId, + ConversationTitle = conversation.Title }; result = await dataProvider.AddMessage( item: result, @@ -891,6 +904,149 @@ namespace xAiApi.Providers } #endregion + // + #region Query ... + /// + /// Query Projects ... + /// + /// + /// + /// + /// + public async Task> QueryProjects( + string ownerId, + XQuery query, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (ownerId.IsNullOrEmpty()) + { + XException.InvalidArgs.Throw(); + } + + // + var result = await dataProvider.QueryProjects( + query: query, + ownerId: ownerId, + cancellationToken: cancellationToken + ); + + // + return result; + } + + /// + /// Query Conversations ... + /// + /// + /// + /// + /// + /// + public async Task> QueryConversations( + string ownerId, + Guid projectId, + XQuery query, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (ownerId.IsNullOrEmpty() || + projectId.IsDefaultGuid()) + { + XException.InvalidArgs.Throw(); + } + + // + // Retrieve Project ... + var project = await dataProvider.GetProject( + id: projectId, + fillConversations: false, + fillConversationMessages: false, + cancellationToken: cancellationToken + ); + if (project.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Validate Owner ... + if (ownerId != project.OwnerId) + { + XException.NotAllowed.Throw(); + } + + // + var result = await dataProvider.QueryConversations( + query: query, + ownerId: ownerId, + projectId: projectId, + cancellationToken: cancellationToken + ); + + // + return result; + } + + /// + /// Query Messages ... + /// + /// + /// + /// + /// + /// + public async Task> QueryMessages( + string ownerId, + Guid conversationId, + XQuery query, + CancellationToken cancellationToken = default + ) + { + // + // Validate ... + if (ownerId.IsNullOrEmpty() || + conversationId.IsDefaultGuid()) + { + XException.InvalidArgs.Throw(); + } + + // + // Retrieve Conversation ... + var conversation = await dataProvider.GetConversation( + id: conversationId, + fillMessages: false, + cancellationToken: cancellationToken + ); + if (conversation.IsNullOrDefault()) + { + XException.NotFound.Throw(); + } + + // + // Validate Owner ... + if (ownerId != conversation.OwnerId) + { + XException.NotAllowed.Throw(); + } + + // + var result = await dataProvider.QueryMessages( + query: query, + ownerId: ownerId, + conversationId: conversationId, + cancellationToken: cancellationToken + ); + + // + return result; + } + #endregion + /// /// Base Dispose Action ... /// @@ -916,49 +1072,45 @@ namespace xAiApi.Providers // var result = new List(); + // + // Retrieve Introduction Prompt ... + var introductionPrompt = configuration.GetPrompt(XAiApiConstants.XAiApiIntroductionPromptName); + if (introductionPrompt.IsNullOrEmpty()) + { + XException.InvalidData.Throw(); + } + + // + var introductionMessage = new ChatMessage( + role: ChatRole.System, + content: introductionPrompt + ); + + // + result.Add(introductionMessage); + + // + if (!project.IsNullOrDefault() && + !project.Prompt.IsNullOrEmpty()) + { + // + // TODO: Handle Project Files Here ... + var projectMessage = new ChatMessage( + role: ChatRole.System, + contents: [new TextContent(project.Prompt)] + ); + result.Add(projectMessage); + } + // if (messages.HasChild()) { // - // if Messages Has Child, this means - // Default Messages Handled before ... - result = [.. messages.Select(m => m.ToChatMessages())]; - } - else - { - // - // If Message Has not any Childs, this means - // First Asking Questions ... - - // - // Retrieve Introduction Prompt ... - var introductionPrompt = configuration.GetPrompt(XAiApiConstants.XAiApiIntroductionPromptName); - if (introductionPrompt.IsNullOrEmpty()) - { - XException.InvalidData.Throw(); - } - - // - var introductionMessage = new ChatMessage( - role: ChatRole.System, - content: introductionPrompt - ); - - // - result.Add(introductionMessage); - - // - if (!project.IsNullOrDefault() && - !project.Prompt.IsNullOrEmpty()) - { - // - // TODO: Handle Project Files Here ... - var projectMessage = new ChatMessage( - role: ChatRole.System, - contents: [new TextContent(project.Prompt)] - ); - result.Add(projectMessage); - } + // TODO: Here we can Implement Memory Management based on + // Last nth of Messages, or Summarization of Messages, etc ... + result = [..result, ..messages + .OrderBy(m => m.CreatedOn) + .Select(m => m.ToChatMessages())]; } //