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