BotSharp/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs

454 lines
17 KiB
C#
Raw Normal View History

2024-05-27 01:05:46 +00:00
using BotSharp.Abstraction.Options;
2024-03-26 21:57:13 +00:00
using BotSharp.Abstraction.Routing;
2024-05-21 16:20:03 +00:00
using BotSharp.Abstraction.Users.Enums;
2024-01-31 19:57:54 +00:00
2023-08-19 00:15:02 +00:00
namespace BotSharp.OpenAPI.Controllers;
[Authorize]
[ApiController]
2023-12-27 15:53:54 +00:00
public class ConversationController : ControllerBase
2023-08-19 00:15:02 +00:00
{
private readonly IServiceProvider _services;
private readonly IUserIdentity _user;
2024-05-27 01:05:46 +00:00
private readonly JsonSerializerOptions _jsonOptions;
2023-08-19 00:15:02 +00:00
public ConversationController(IServiceProvider services,
2024-05-27 01:05:46 +00:00
IUserIdentity user,
BotSharpOptions options)
2023-08-19 00:15:02 +00:00
{
_services = services;
_user = user;
2024-05-27 01:05:46 +00:00
_jsonOptions = InitJsonOptions(options);
2023-08-19 00:15:02 +00:00
}
[HttpPost("/conversation/{agentId}")]
2023-10-27 15:18:48 +00:00
public async Task<ConversationViewModel> NewConversation([FromRoute] string agentId, [FromBody] MessageConfig config)
2023-08-19 00:15:02 +00:00
{
var service = _services.GetRequiredService<IConversationService>();
2023-10-27 15:18:48 +00:00
var conv = new Conversation
2023-08-19 00:15:02 +00:00
{
2023-11-14 01:25:25 +00:00
AgentId = agentId,
2023-11-27 03:04:48 +00:00
Channel = ConversationChannel.OpenAPI,
2024-02-12 15:14:33 +00:00
TaskId = config.TaskId
2023-08-19 00:15:02 +00:00
};
2023-10-27 15:18:48 +00:00
conv = await service.NewConversation(conv);
2023-11-20 17:08:05 +00:00
service.SetConversationId(conv.Id, config.States);
2023-10-27 15:18:48 +00:00
return ConversationViewModel.FromSession(conv);
2023-08-19 00:15:02 +00:00
}
2024-03-08 22:02:08 +00:00
[HttpPost("/conversations")]
public async Task<PagedItems<ConversationViewModel>> GetConversations([FromBody] ConversationFilter filter)
2023-11-14 01:25:25 +00:00
{
2024-05-15 20:54:08 +00:00
var convService = _services.GetRequiredService<IConversationService>();
2023-11-14 01:25:25 +00:00
var userService = _services.GetRequiredService<IUserService>();
2024-05-15 20:54:08 +00:00
var user = await userService.GetUser(_user.Id);
if (user == null)
{
return new PagedItems<ConversationViewModel>();
}
filter.UserId = user.Role != UserRole.Admin ? user.Id : null;
var conversations = await convService.GetConversations(filter);
2024-01-28 01:02:33 +00:00
var agentService = _services.GetRequiredService<IAgentService>();
2024-05-15 22:30:45 +00:00
var list = conversations.Items.Select(x => ConversationViewModel.FromSession(x)).ToList();
2023-11-14 01:25:25 +00:00
foreach (var item in list)
{
2024-05-15 20:54:08 +00:00
user = await userService.GetUser(item.User.Id);
2023-11-14 01:25:25 +00:00
item.User = UserViewModel.FromUser(user);
2024-01-28 01:02:33 +00:00
var agent = await agentService.GetAgent(item.AgentId);
2024-02-12 15:14:33 +00:00
item.AgentName = agent?.Name;
2023-11-14 01:25:25 +00:00
}
2023-12-04 23:42:46 +00:00
return new PagedItems<ConversationViewModel>
{
Count = conversations.Count,
Items = list
};
2023-11-14 01:25:25 +00:00
}
2023-11-14 14:13:54 +00:00
[HttpGet("/conversation/{conversationId}/dialogs")]
public async Task<IEnumerable<ChatResponseModel>> GetDialogs([FromRoute] string conversationId)
{
var conv = _services.GetRequiredService<IConversationService>();
2024-03-27 18:50:27 +00:00
conv.SetConversationId(conversationId, new List<MessageState>());
2024-03-24 01:31:15 +00:00
var history = conv.GetDialogHistory(fromBreakpoint: false);
2023-11-14 14:13:54 +00:00
var userService = _services.GetRequiredService<IUserService>();
2024-01-31 20:01:59 +00:00
var agentService = _services.GetRequiredService<IAgentService>();
2023-11-14 14:13:54 +00:00
var dialogs = new List<ChatResponseModel>();
foreach (var message in history)
{
2024-01-31 19:57:54 +00:00
if (message.Role == AgentRole.User)
{
var user = await userService.GetUser(message.SenderId);
2023-11-14 14:13:54 +00:00
2024-01-31 19:57:54 +00:00
dialogs.Add(new ChatResponseModel
{
ConversationId = conversationId,
MessageId = message.MessageId,
CreatedAt = message.CreatedAt,
2024-04-23 21:27:09 +00:00
Text = !string.IsNullOrEmpty(message.SecondaryContent) ? message.SecondaryContent : message.Content,
2024-02-06 20:42:35 +00:00
Data = message.Data,
2024-05-07 16:55:44 +00:00
Sender = UserViewModel.FromUser(user),
Payload = message.Payload
2024-01-31 19:57:54 +00:00
});
}
2024-02-28 04:36:33 +00:00
else if (message.Role == AgentRole.Assistant)
2023-11-14 14:13:54 +00:00
{
2024-01-31 20:01:59 +00:00
var agent = await agentService.GetAgent(message.CurrentAgentId);
2024-01-31 19:57:54 +00:00
dialogs.Add(new ChatResponseModel
{
ConversationId = conversationId,
MessageId = message.MessageId,
CreatedAt = message.CreatedAt,
2024-04-23 21:27:09 +00:00
Text = !string.IsNullOrEmpty(message.SecondaryContent) ? message.SecondaryContent : message.Content,
2024-02-28 16:16:46 +00:00
Function = message.FunctionName,
2024-02-06 20:42:35 +00:00
Data = message.Data,
2024-01-31 20:01:59 +00:00
Sender = new UserViewModel
{
FirstName = agent.Name,
Role = message.Role,
2024-02-22 16:02:11 +00:00
},
2024-04-23 21:27:09 +00:00
RichContent = message.SecondaryRichContent ?? message.RichContent
2024-01-31 19:57:54 +00:00
});
}
2023-11-14 14:13:54 +00:00
}
return dialogs;
}
2023-12-26 03:16:41 +00:00
[HttpGet("/conversation/{conversationId}")]
2024-05-15 20:54:08 +00:00
public async Task<ConversationViewModel?> GetConversation([FromRoute] string conversationId)
2023-12-26 03:16:41 +00:00
{
var service = _services.GetRequiredService<IConversationService>();
2024-05-15 20:54:08 +00:00
var userService = _services.GetRequiredService<IUserService>();
var user = await userService.GetUser(_user.Id);
if (user == null)
2023-12-26 03:16:41 +00:00
{
2024-05-15 20:54:08 +00:00
return null;
}
2023-12-26 03:16:41 +00:00
2024-05-15 20:54:08 +00:00
var filter = new ConversationFilter
{
Id = conversationId,
UserId = user.Role != UserRole.Admin ? user.Id : null
};
var conversations = await service.GetConversations(filter);
if (conversations.Items.IsNullOrEmpty())
{
return null;
}
2023-12-26 03:16:41 +00:00
var result = ConversationViewModel.FromSession(conversations.Items.First());
2023-12-27 19:27:52 +00:00
var state = _services.GetRequiredService<IConversationStateService>();
2024-04-10 17:45:35 +00:00
result.States = state.Load(conversationId, isReadOnly: true);
2023-12-26 03:16:41 +00:00
result.User = UserViewModel.FromUser(user);
return result;
}
2024-05-29 02:50:16 +00:00
[HttpPost("/conversation/summary")]
public async Task<string> GetConversationSummary([FromBody] ConversationSummaryModel input)
2024-05-27 01:40:24 +00:00
{
var service = _services.GetRequiredService<IConversationService>();
2024-05-29 02:50:16 +00:00
return await service.GetConversationSummary(input.ConversationIds);
2024-05-27 01:40:24 +00:00
}
2024-05-15 16:23:02 +00:00
[HttpGet("/conversation/{conversationId}/user")]
public async Task<UserViewModel> GetConversationUser([FromRoute] string conversationId)
{
var service = _services.GetRequiredService<IConversationService>();
var conversations = await service.GetConversations(new ConversationFilter
{
Id = conversationId
});
var userService = _services.GetRequiredService<IUserService>();
var conversation = conversations?.Items?.FirstOrDefault();
var userId = conversation == null ? _user.Id : conversation.UserId;
var user = await userService.GetUser(userId);
if (user == null)
{
return new UserViewModel
{
Id = _user.Id,
2024-05-15 16:23:29 +00:00
UserName = _user.UserName,
2024-05-15 16:23:02 +00:00
FirstName = _user.FirstName,
LastName = _user.LastName,
Email = _user.Email,
Source = "Unknown"
};
}
return UserViewModel.FromUser(user);
}
2023-11-14 14:13:54 +00:00
[HttpDelete("/conversation/{conversationId}")]
2023-11-26 06:29:06 +00:00
public async Task<bool> DeleteConversation([FromRoute] string conversationId)
2023-08-19 00:15:02 +00:00
{
2024-05-15 23:07:11 +00:00
var userService = _services.GetRequiredService<IUserService>();
2023-11-26 06:29:06 +00:00
var conversationService = _services.GetRequiredService<IConversationService>();
2024-05-15 23:07:11 +00:00
var user = await userService.GetUser(_user.Id);
var filter = new ConversationFilter
{
Id = conversationId,
UserId = user.Role != UserRole.Admin ? user.Id : null
};
var conversations = await conversationService.GetConversations(filter);
if (conversations.Items.IsNullOrEmpty())
{
return false;
}
var response = await conversationService.DeleteConversations(new List<string> { conversationId });
2023-11-26 06:29:06 +00:00
return response;
2023-08-19 00:15:02 +00:00
}
2024-01-29 01:33:40 +00:00
[HttpDelete("/conversation/{conversationId}/message/{messageId}")]
public async Task<bool> DeleteConversationMessage([FromRoute] string conversationId, [FromRoute] string messageId)
{
var conversationService = _services.GetRequiredService<IConversationService>();
var response = await conversationService.TruncateConversation(conversationId, messageId);
return response;
}
2024-05-30 00:27:38 +00:00
#region Send message
2023-08-19 00:15:02 +00:00
[HttpPost("/conversation/{agentId}/{conversationId}")]
2023-11-14 04:56:06 +00:00
public async Task<ChatResponseModel> SendMessage([FromRoute] string agentId,
[FromRoute] string conversationId,
2023-09-09 15:37:38 +00:00
[FromBody] NewMessageModel input)
2023-08-19 00:15:02 +00:00
{
var conv = _services.GetRequiredService<IConversationService>();
2024-05-03 19:57:44 +00:00
var inputMsg = new RoleDialogModel(AgentRole.User, input.Text)
{
2024-05-10 02:37:32 +00:00
Files = input.Files,
CreatedAt = DateTime.UtcNow
2024-05-03 19:57:44 +00:00
};
2024-05-10 02:37:32 +00:00
2024-05-06 22:31:52 +00:00
if (!string.IsNullOrEmpty(input.TruncateMessageId))
{
await conv.TruncateConversation(conversationId, input.TruncateMessageId, inputMsg.MessageId);
}
2024-03-26 21:57:13 +00:00
var routing = _services.GetRequiredService<IRoutingService>();
routing.Context.SetMessageId(conversationId, inputMsg.MessageId);
2024-03-26 23:11:05 +00:00
conv.SetConversationId(conversationId, input.States);
2024-04-30 21:25:58 +00:00
SetStates(conv, input);
2023-09-14 01:41:51 +00:00
2023-11-14 04:56:06 +00:00
var response = new ChatResponseModel();
2024-01-13 21:17:40 +00:00
2023-11-27 03:44:17 +00:00
await conv.SendMessage(agentId, inputMsg,
2024-03-16 14:43:00 +00:00
replyMessage: input.Postback,
2023-08-19 00:15:02 +00:00
async msg =>
{
2024-04-23 21:27:09 +00:00
response.Text = !string.IsNullOrEmpty(msg.SecondaryContent) ? msg.SecondaryContent : msg.Content;
2023-10-30 16:48:18 +00:00
response.Function = msg.FunctionName;
2024-04-23 21:27:09 +00:00
response.RichContent = msg.SecondaryRichContent ?? msg.RichContent;
2023-10-30 16:48:18 +00:00
response.Instruction = msg.Instruction;
response.Data = msg.Data;
},
2024-03-16 14:43:00 +00:00
_ => Task.CompletedTask,
_ => Task.CompletedTask);
2023-08-19 00:15:02 +00:00
2023-11-03 14:16:16 +00:00
var state = _services.GetRequiredService<IConversationStateService>();
response.States = state.GetStates();
2023-10-27 15:18:48 +00:00
response.MessageId = inputMsg.MessageId;
2023-11-14 14:13:54 +00:00
response.ConversationId = conversationId;
2023-09-22 20:38:58 +00:00
2023-08-19 00:15:02 +00:00
return response;
}
2023-11-09 21:11:36 +00:00
2024-04-30 21:25:58 +00:00
[HttpPost("/conversation/{agentId}/{conversationId}/sse")]
public async Task SendMessageSse([FromRoute] string agentId,
[FromRoute] string conversationId,
[FromBody] NewMessageModel input)
{
var conv = _services.GetRequiredService<IConversationService>();
2024-05-06 22:31:52 +00:00
var inputMsg = new RoleDialogModel(AgentRole.User, input.Text)
{
2024-05-10 02:37:32 +00:00
Files = input.Files,
CreatedAt = DateTime.UtcNow
2024-05-06 22:31:52 +00:00
};
2024-05-10 02:37:32 +00:00
2024-04-30 21:25:58 +00:00
if (!string.IsNullOrEmpty(input.TruncateMessageId))
{
2024-05-06 22:31:52 +00:00
await conv.TruncateConversation(conversationId, input.TruncateMessageId, inputMsg.MessageId);
2024-04-30 21:25:58 +00:00
}
2024-05-22 16:02:14 +00:00
var state = _services.GetRequiredService<IConversationStateService>();
2024-04-30 21:25:58 +00:00
var routing = _services.GetRequiredService<IRoutingService>();
routing.Context.SetMessageId(conversationId, inputMsg.MessageId);
conv.SetConversationId(conversationId, input.States);
SetStates(conv, input);
2024-05-21 17:02:06 +00:00
var response = new ChatResponseModel
{
ConversationId = conversationId,
MessageId = inputMsg.MessageId,
};
2024-04-30 21:25:58 +00:00
Response.StatusCode = 200;
Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.ContentType, "text/event-stream");
Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.CacheControl, "no-cache");
Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.Connection, "keep-alive");
await conv.SendMessage(agentId, inputMsg,
replyMessage: input.Postback,
2024-05-21 17:02:06 +00:00
// responsed generated
2024-04-30 21:25:58 +00:00
async msg =>
{
response.Text = !string.IsNullOrEmpty(msg.SecondaryContent) ? msg.SecondaryContent : msg.Content;
response.Function = msg.FunctionName;
response.RichContent = msg.SecondaryRichContent ?? msg.RichContent;
response.Instruction = msg.Instruction;
response.Data = msg.Data;
2024-05-22 16:02:14 +00:00
response.States = state.GetStates();
2024-04-30 21:25:58 +00:00
2024-05-21 17:02:06 +00:00
await OnChunkReceived(Response, response);
2024-04-30 21:25:58 +00:00
},
2024-05-21 17:02:06 +00:00
// executing
2024-04-30 21:25:58 +00:00
async msg =>
{
2024-05-21 17:02:06 +00:00
var indicator = new ChatResponseModel
2024-04-30 21:25:58 +00:00
{
2024-05-21 17:02:06 +00:00
ConversationId = conversationId,
MessageId = msg.MessageId,
Text = msg.Indication,
Function = "indicating",
2024-05-28 00:26:58 +00:00
Instruction = msg.Instruction,
2024-05-22 16:02:14 +00:00
States = new Dictionary<string, string>()
2024-04-30 21:25:58 +00:00
};
2024-05-21 17:02:06 +00:00
await OnChunkReceived(Response, indicator);
2024-04-30 21:25:58 +00:00
},
2024-05-21 17:02:06 +00:00
// executed
2024-04-30 21:25:58 +00:00
async msg =>
{
});
response.States = state.GetStates();
response.MessageId = inputMsg.MessageId;
response.ConversationId = conversationId;
// await OnEventCompleted(Response);
}
2024-05-30 00:27:38 +00:00
#endregion
#region Files and attachments
[HttpPost("/conversation/{conversationId}/attachments")]
public IActionResult UploadAttachments([FromRoute] string conversationId,
IFormFile[] files)
{
if (files != null && files.Length > 0)
{
var fileService = _services.GetRequiredService<IBotSharpFileService>();
var dir = fileService.GetDirectory(conversationId);
foreach (var file in files)
{
// Save the file, process it, etc.
var fileName = ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.Trim('"');
var filePath = Path.Combine(dir, fileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
file.CopyTo(stream);
}
}
return Ok(new { message = "File uploaded successfully." });
}
return BadRequest(new { message = "Invalid file." });
}
[HttpGet("/conversation/{conversationId}/files/{messageId}")]
public IEnumerable<MessageFileViewModel> GetMessageFiles([FromRoute] string conversationId, [FromRoute] string messageId)
{
var fileService = _services.GetRequiredService<IBotSharpFileService>();
var files = fileService.GetMessageFiles(conversationId, new List<string> { messageId });
return files?.Select(x => MessageFileViewModel.Transform(x))?.ToList() ?? new List<MessageFileViewModel>();
}
[HttpGet("/conversation/{conversationId}/message/{messageId}/file/{fileName}")]
public IActionResult GetMessageFile([FromRoute] string conversationId, [FromRoute] string messageId, [FromRoute] string fileName)
{
var fileService = _services.GetRequiredService<IBotSharpFileService>();
var file = fileService.GetMessageFile(conversationId, messageId, fileName);
if (string.IsNullOrEmpty(file))
{
return NotFound();
}
return BuildFileResult(file);
}
#endregion
#region Private methods
private void SetStates(IConversationService conv, NewMessageModel input)
{
conv.States.SetState("channel", input.Channel, source: StateSource.External)
.SetState("provider", input.Provider, source: StateSource.External)
.SetState("model", input.Model, source: StateSource.External)
.SetState("temperature", input.Temperature, source: StateSource.External)
.SetState("sampling_factor", input.SamplingFactor, source: StateSource.External);
}
private FileContentResult BuildFileResult(string file)
{
using Stream stream = System.IO.File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read);
var bytes = new byte[stream.Length];
stream.Read(bytes, 0, (int)stream.Length);
return File(bytes, "application/octet-stream", Path.GetFileName(file));
}
2024-04-30 21:25:58 +00:00
2024-05-21 17:02:06 +00:00
private async Task OnChunkReceived(HttpResponse response, ChatResponseModel message)
2024-04-30 21:25:58 +00:00
{
2024-05-27 01:05:46 +00:00
var json = JsonSerializer.Serialize(message, _jsonOptions);
2024-04-30 21:25:58 +00:00
var buffer = Encoding.UTF8.GetBytes($"data:{json}\n");
await response.Body.WriteAsync(buffer, 0, buffer.Length);
await Task.Delay(10);
buffer = Encoding.UTF8.GetBytes("\n");
await response.Body.WriteAsync(buffer, 0, buffer.Length);
}
private async Task OnEventCompleted(HttpResponse response)
{
var buffer = Encoding.UTF8.GetBytes("data:[DONE]\n");
await response.Body.WriteAsync(buffer, 0, buffer.Length);
buffer = Encoding.UTF8.GetBytes("\n");
await response.Body.WriteAsync(buffer, 0, buffer.Length);
}
2024-05-27 01:05:46 +00:00
private JsonSerializerOptions InitJsonOptions(BotSharpOptions options)
{
var jsonOption = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
AllowTrailingCommas = true
};
if (options?.JsonSerializerOptions != null)
{
foreach (var option in options.JsonSerializerOptions.Converters)
{
jsonOption.Converters.Add(option);
}
}
return jsonOption;
}
2024-05-30 00:27:38 +00:00
#endregion
2023-08-19 00:15:02 +00:00
}