12 changed files with 252 additions and 3 deletions
@ -0,0 +1,14 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.AIManagement.Chats.Dtos; |
|||
public abstract class ChatMessageDto : ExtensibleAuditedEntityDto<Guid> |
|||
{ |
|||
public string Workspace { get; set; } |
|||
|
|||
public DateTime CreatedAt { get; set; } |
|||
|
|||
public Guid? UserId { get; set; } |
|||
|
|||
public Guid? ConversationId { get; set; } |
|||
} |
|||
@ -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; } |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
|
|||
namespace LINGYUN.Abp.AIManagement.Chats.Dtos; |
|||
public class TextChatMessageDto |
|||
{ |
|||
} |
|||
@ -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<string> SendMessageAsync(SendTextChatMessageDto input); |
|||
} |
|||
@ -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<string> SendMessageAsync(SendTextChatMessageDto input) |
|||
{ |
|||
var chatMessage = new TextChatMessage( |
|||
input.Workspace, |
|||
input.Content, |
|||
ChatRole.User, |
|||
Clock.Now); |
|||
|
|||
chatMessage.WithConversationId(input.ConversationId); |
|||
|
|||
return _agentService.SendMessageAsync(chatMessage); |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -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<AbpMvcDataAnnotationsLocalizationOptions>(options => |
|||
{ |
|||
options.AddAssemblyResource(typeof(AIManagementResource), typeof(AbpAIManagementApplicationContractsModule).Assembly); |
|||
}); |
|||
|
|||
PreConfigure<IMvcBuilder>(mvcBuilder => |
|||
{ |
|||
mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpAIManagementHttpApiModule).Assembly); |
|||
}); |
|||
} |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Get<AIManagementResource>() |
|||
.AddBaseTypes(typeof(AbpUiResource)); |
|||
}); |
|||
|
|||
context.Services.AddTransient<SseAsyncEnumerableResultFilter>(); |
|||
} |
|||
} |
|||
@ -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<SseAsyncEnumerableResultFilter>] |
|||
public async virtual IAsyncEnumerable<string> SendMessageAsync(SendTextChatMessageDto input) |
|||
{ |
|||
await foreach (var content in _service.SendMessageAsync(input)) |
|||
{ |
|||
yield return content; |
|||
} |
|||
} |
|||
} |
|||
@ -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<string> _asyncEnumerable; |
|||
public SseAsyncEnumerableResult(IAsyncEnumerable<string> 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
|
|||
} |
|||
} |
|||
} |
|||
@ -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<string> asyncEnumerable) |
|||
{ |
|||
executedContext.Result = new SseAsyncEnumerableResult(asyncEnumerable); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue