diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Chats/Dtos/ChatMessageDto.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Chats/Dtos/ChatMessageDto.cs new file mode 100644 index 000000000..035fc1464 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Chats/Dtos/ChatMessageDto.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.Application.Dtos; + +namespace LINGYUN.Abp.AIManagement.Chats.Dtos; +public abstract class ChatMessageDto : ExtensibleAuditedEntityDto +{ + public string Workspace { get; set; } + + public DateTime CreatedAt { get; set; } + + public Guid? UserId { get; set; } + + public Guid? ConversationId { get; set; } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Chats/Dtos/SendTextChatMessageDto.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Chats/Dtos/SendTextChatMessageDto.cs new file mode 100644 index 000000000..ad0372260 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Chats/Dtos/SendTextChatMessageDto.cs @@ -0,0 +1,19 @@ +using LINGYUN.Abp.AIManagement.Workspaces; +using System; +using System.ComponentModel.DataAnnotations; +using Volo.Abp.Validation; + +namespace LINGYUN.Abp.AIManagement.Chats.Dtos; +public class SendTextChatMessageDto +{ + [Required] + [DynamicStringLength(typeof(WorkspaceDefinitionRecordConsts), nameof(WorkspaceDefinitionRecordConsts.MaxNameLength))] + public string Workspace { get; set; } + + [Required] + public Guid ConversationId { get; set; } + + [Required] + [DynamicStringLength(typeof(TextChatMessageRecordConsts), nameof(TextChatMessageRecordConsts.MaxContentLength))] + public string Content { get; set; } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Chats/Dtos/TextChatMessageDto.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Chats/Dtos/TextChatMessageDto.cs new file mode 100644 index 000000000..1ef328229 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Chats/Dtos/TextChatMessageDto.cs @@ -0,0 +1,8 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LINGYUN.Abp.AIManagement.Chats.Dtos; +public class TextChatMessageDto +{ +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Chats/IChatAppService.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Chats/IChatAppService.cs new file mode 100644 index 000000000..dcfaa06ca --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application.Contracts/LINGYUN/Abp/AIManagement/Chats/IChatAppService.cs @@ -0,0 +1,9 @@ +using LINGYUN.Abp.AIManagement.Chats.Dtos; +using System.Collections.Generic; +using Volo.Abp.Application.Services; + +namespace LINGYUN.Abp.AIManagement.Chats; +public interface IChatAppService : IApplicationService +{ + IAsyncEnumerable SendMessageAsync(SendTextChatMessageDto input); +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application/LINGYUN/Abp/AIManagement/Chats/ChatAppService.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application/LINGYUN/Abp/AIManagement/Chats/ChatAppService.cs new file mode 100644 index 000000000..fe420a66a --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Application/LINGYUN/Abp/AIManagement/Chats/ChatAppService.cs @@ -0,0 +1,28 @@ +using LINGYUN.Abp.AI.Agent; +using LINGYUN.Abp.AI.Models; +using LINGYUN.Abp.AIManagement.Chats.Dtos; +using Microsoft.Extensions.AI; +using System.Collections.Generic; + +namespace LINGYUN.Abp.AIManagement.Chats; +public class ChatAppService : AIManagementApplicationServiceBase, IChatAppService +{ + private readonly IAgentService _agentService; + public ChatAppService(IAgentService agentService) + { + _agentService = agentService; + } + + public IAsyncEnumerable SendMessageAsync(SendTextChatMessageDto input) + { + var chatMessage = new TextChatMessage( + input.Workspace, + input.Content, + ChatRole.User, + Clock.Now); + + chatMessage.WithConversationId(input.ConversationId); + + return _agentService.SendMessageAsync(chatMessage); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/FodyWeavers.xsd b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN.Abp.AIManagement.Domain.csproj b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN.Abp.AIManagement.Domain.csproj index 2cc7884dc..813624bf7 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN.Abp.AIManagement.Domain.csproj +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN.Abp.AIManagement.Domain.csproj @@ -23,7 +23,7 @@ - + diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/AbpAIManagementDomainModule.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/AbpAIManagementDomainModule.cs index 8d139de9b..75ba502f1 100644 --- a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/AbpAIManagementDomainModule.cs +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.Domain/LINGYUN/Abp/AIManagement/AbpAIManagementDomainModule.cs @@ -1,4 +1,6 @@ -using LINGYUN.Abp.AI; +using LINGYUN.Abp.AI.Agent; +using LINGYUN.Abp.AI.Localization; +using LINGYUN.Abp.AIManagement.Localization; using LINGYUN.Abp.AIManagement.Workspaces; using Microsoft.Extensions.DependencyInjection; using System.Threading; @@ -8,6 +10,7 @@ using Volo.Abp.Caching; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain; +using Volo.Abp.Localization; using Volo.Abp.Mapperly; using Volo.Abp.Modularity; using Volo.Abp.Threading; @@ -16,7 +19,7 @@ namespace LINGYUN.Abp.AIManagement; [DependsOn( typeof(AbpAIManagementDomainSharedModule), - typeof(AbpAICoreModule), + typeof(AbpAIAgentModule), typeof(AbpCachingModule), typeof(AbpMapperlyModule), typeof(AbpDddDomainModule))] @@ -37,6 +40,13 @@ public class AbpAIManagementDomainModule : AbpModule options.IsDynamicWorkspaceStoreEnabled = false; }); } + + Configure(options => + { + options.Resources + .Get() + .AddBaseTypes(typeof(AbpAIResource)); + }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/LINGYUN/Abp/AIManagement/AbpAIManagementHttpApiModule.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/LINGYUN/Abp/AIManagement/AbpAIManagementHttpApiModule.cs new file mode 100644 index 000000000..7e119483f --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/LINGYUN/Abp/AIManagement/AbpAIManagementHttpApiModule.cs @@ -0,0 +1,41 @@ +using LINGYUN.Abp.AIManagement.Localization; +using Localization.Resources.AbpUi; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; + +namespace LINGYUN.Abp.AIManagement; + +[DependsOn( + typeof(AbpAIManagementApplicationContractsModule), + typeof(AbpAspNetCoreMvcModule))] +public class AbpAIManagementHttpApiModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(options => + { + options.AddAssemblyResource(typeof(AIManagementResource), typeof(AbpAIManagementApplicationContractsModule).Assembly); + }); + + PreConfigure(mvcBuilder => + { + mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpAIManagementHttpApiModule).Assembly); + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.Resources + .Get() + .AddBaseTypes(typeof(AbpUiResource)); + }); + + context.Services.AddTransient(); + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/LINGYUN/Abp/AIManagement/Chats/ChatController.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/LINGYUN/Abp/AIManagement/Chats/ChatController.cs new file mode 100644 index 000000000..811a29c56 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/LINGYUN/Abp/AIManagement/Chats/ChatController.cs @@ -0,0 +1,30 @@ +using LINGYUN.Abp.AIManagement.Chats.Dtos; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using Volo.Abp; +using Volo.Abp.AspNetCore.Mvc; + +namespace LINGYUN.Abp.AIManagement.Chats; + +[Controller] +[RemoteService(Name = AIManagementRemoteServiceConsts.RemoteServiceName)] +[Area(AIManagementRemoteServiceConsts.ModuleName)] +[Route($"api/{AIManagementRemoteServiceConsts.ModuleName}/chats")] +public class ChatController : AbpControllerBase, IChatAppService +{ + private readonly IChatAppService _service; + public ChatController(IChatAppService service) + { + _service = service; + } + + [HttpPost] + [ServiceFilter] + public async virtual IAsyncEnumerable SendMessageAsync(SendTextChatMessageDto input) + { + await foreach (var content in _service.SendMessageAsync(input)) + { + yield return content; + } + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/Microsoft/AspNetCore/Mvc/SseAsyncEnumerableResult.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/Microsoft/AspNetCore/Mvc/SseAsyncEnumerableResult.cs new file mode 100644 index 000000000..f44e8f911 --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/Microsoft/AspNetCore/Mvc/SseAsyncEnumerableResult.cs @@ -0,0 +1,42 @@ +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc; +public class SseAsyncEnumerableResult : IActionResult +{ + private readonly IAsyncEnumerable _asyncEnumerable; + public SseAsyncEnumerableResult(IAsyncEnumerable asyncEnumerable) + { + _asyncEnumerable = asyncEnumerable; + } + public async Task ExecuteResultAsync(ActionContext context) + { + var response = context.HttpContext.Response; + + response.Headers.Append("Content-Type", "text/event-stream"); + response.Headers.Append("Cache-Control", "no-cache"); + response.Headers.Append("Connection", "keep-alive"); + response.Headers.Append("X-Accel-Buffering", "no"); + + try + { + await foreach (var content in _asyncEnumerable) + { + if (!string.IsNullOrEmpty(content)) + { + await response.WriteAsync($"data: {content}\n\n"); + await response.Body.FlushAsync(); + } + } + + await response.WriteAsync("data: [DONE]\n\n"); + await response.Body.FlushAsync(); + } + catch (OperationCanceledException) + { + // ignore + } + } +} diff --git a/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/Microsoft/AspNetCore/Mvc/SseAsyncEnumerableResultFilter.cs b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/Microsoft/AspNetCore/Mvc/SseAsyncEnumerableResultFilter.cs new file mode 100644 index 000000000..55d20014e --- /dev/null +++ b/aspnet-core/modules/ai/LINGYUN.Abp.AIManagement.HttpApi/Microsoft/AspNetCore/Mvc/SseAsyncEnumerableResultFilter.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Mvc.Filters; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc; +public class SseAsyncEnumerableResultFilter : IAsyncActionFilter +{ + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + var executedContext = await next(); + + if (executedContext.Result is ObjectResult objectResult && + objectResult.Value is IAsyncEnumerable asyncEnumerable) + { + executedContext.Result = new SseAsyncEnumerableResult(asyncEnumerable); + } + } +}