2024-08-02 21:24:44 +00:00
|
|
|
using BotSharp.Abstraction.Files.Constants;
|
2024-06-19 23:04:47 +00:00
|
|
|
using BotSharp.Abstraction.Files.Enums;
|
2024-11-04 18:12:40 +00:00
|
|
|
using BotSharp.Abstraction.Files.Utilities;
|
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-10-07 21:35:39 +00:00
|
|
|
using BotSharp.Core.Infrastructures;
|
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
|
|
|
|
2023-10-27 22:16:53 +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>();
|
2024-06-24 21:39:53 +00:00
|
|
|
var channel = config.States.FirstOrDefault(x => x.Key == "channel");
|
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,
|
2024-06-24 21:39:53 +00:00
|
|
|
Channel = channel == default ? ConversationChannel.OpenAPI : channel.Value,
|
2024-10-23 01:52:18 +00:00
|
|
|
Tags = config.Tags ?? new(),
|
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
|
|
|
}
|
|
|
|
|
|
2025-03-10 17:14:44 +00:00
|
|
|
[HttpGet("/conversations")]
|
|
|
|
|
public async Task<PagedItems<ConversationViewModel>> GetConversations([FromQuery] 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>();
|
2025-03-10 17:14:44 +00:00
|
|
|
var (isAdmin, user) = await userService.IsAdminUser(_user.Id);
|
2024-05-15 20:54:08 +00:00
|
|
|
if (user == null)
|
|
|
|
|
{
|
|
|
|
|
return new PagedItems<ConversationViewModel>();
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-10 17:14:44 +00:00
|
|
|
filter.UserId = !isAdmin ? user.Id : filter.UserId;
|
2024-05-15 20:54:08 +00:00
|
|
|
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
|
|
|
|
2025-03-10 17:14:44 +00:00
|
|
|
var agentIds = list.Select(x => x.AgentId).ToList();
|
|
|
|
|
var agents = await agentService.GetAgentOptions(agentIds);
|
|
|
|
|
|
|
|
|
|
var userIds = list.Select(x => x.User.Id).ToList();
|
|
|
|
|
var users = await userService.GetUsers(new UserFilter { UserIds = userIds, Size = filter.Pager.Size });
|
|
|
|
|
|
2023-11-14 01:25:25 +00:00
|
|
|
foreach (var item in list)
|
|
|
|
|
{
|
2025-03-10 17:14:44 +00:00
|
|
|
user = users.Items.FirstOrDefault(x => x.Id == item.User.Id);
|
2023-11-14 01:25:25 +00:00
|
|
|
item.User = UserViewModel.FromUser(user);
|
2025-03-10 17:14:44 +00:00
|
|
|
var agent = agents.FirstOrDefault(x => x.Id == item.AgentId);
|
2024-06-11 21:56:17 +00:00
|
|
|
item.AgentName = agent?.Name ?? "Unkown";
|
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")]
|
2025-03-12 15:13:57 +00:00
|
|
|
public async Task<IEnumerable<ChatResponseModel>> GetDialogs([FromRoute] string conversationId, [FromQuery] int count = 100)
|
2023-11-14 14:13:54 +00:00
|
|
|
{
|
2025-02-20 17:26:49 +00:00
|
|
|
var conv = _services.GetRequiredService<IConversationService>();
|
|
|
|
|
conv.SetConversationId(conversationId, [], isReadOnly: true);
|
2025-03-12 15:13:57 +00:00
|
|
|
var history = conv.GetDialogHistory(lastCount: count, 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>();
|
2024-08-08 01:56:29 +00:00
|
|
|
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
2024-07-08 22:37:22 +00:00
|
|
|
|
|
|
|
|
var messageIds = history.Select(x => x.MessageId).Distinct().ToList();
|
2024-08-08 01:56:29 +00:00
|
|
|
var fileMessages = fileStorage.GetMessagesWithFile(conversationId, messageIds);
|
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),
|
2024-07-08 22:37:22 +00:00
|
|
|
Payload = message.Payload,
|
|
|
|
|
HasMessageFiles = fileMessages.Any(x => x.MessageId.IsEqualTo(message.MessageId) && x.FileSource == FileSourceType.User)
|
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
|
|
|
|
|
{
|
2024-06-11 21:56:17 +00:00
|
|
|
FirstName = agent?.Name ?? "Unkown",
|
2024-01-31 20:01:59 +00:00
|
|
|
Role = message.Role,
|
2024-02-22 16:02:11 +00:00
|
|
|
},
|
2024-07-08 22:37:22 +00:00
|
|
|
RichContent = message.SecondaryRichContent ?? message.RichContent,
|
|
|
|
|
HasMessageFiles = fileMessages.Any(x => x.MessageId.IsEqualTo(message.MessageId) && x.FileSource == FileSourceType.Bot)
|
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}")]
|
2025-03-14 22:04:54 +00:00
|
|
|
public async Task<ConversationViewModel?> GetConversation([FromRoute] string conversationId, [FromQuery] bool isLoadStates = false)
|
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>();
|
2025-03-10 17:14:44 +00:00
|
|
|
var (isAdmin, user) = await userService.IsAdminUser(_user.Id);
|
2024-05-15 20:54:08 +00:00
|
|
|
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,
|
2025-03-14 22:04:54 +00:00
|
|
|
UserId = !isAdmin ? user.Id : null,
|
|
|
|
|
IsLoadLatestStates = isLoadStates
|
2024-05-15 20:54:08 +00:00
|
|
|
};
|
|
|
|
|
var conversations = await service.GetConversations(filter);
|
|
|
|
|
if (conversations.Items.IsNullOrEmpty())
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2024-06-19 23:04:47 +00:00
|
|
|
|
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-08-22 03:59:40 +00:00
|
|
|
user = await userService.GetUser(result.User.Id);
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-22 03:59:40 +00:00
|
|
|
[HttpPut("/conversation/{conversationId}/update-title")]
|
|
|
|
|
public async Task<bool> UpdateConversationTitle([FromRoute] string conversationId, [FromBody] UpdateConversationTitleModel newTile)
|
|
|
|
|
{
|
|
|
|
|
var userService = _services.GetRequiredService<IUserService>();
|
2024-10-18 20:50:39 +00:00
|
|
|
var conv = _services.GetRequiredService<IConversationService>();
|
2024-08-22 03:59:40 +00:00
|
|
|
|
2025-03-10 17:14:44 +00:00
|
|
|
var (isAdmin, user) = await userService.IsAdminUser(_user.Id);
|
2024-08-22 03:59:40 +00:00
|
|
|
var filter = new ConversationFilter
|
|
|
|
|
{
|
|
|
|
|
Id = conversationId,
|
2025-03-10 17:14:44 +00:00
|
|
|
UserId = !isAdmin ? user?.Id : null
|
2024-08-22 03:59:40 +00:00
|
|
|
};
|
2024-10-18 20:50:39 +00:00
|
|
|
var conversations = await conv.GetConversations(filter);
|
2024-08-22 03:59:40 +00:00
|
|
|
|
|
|
|
|
if (conversations.Items.IsNullOrEmpty())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-18 20:50:39 +00:00
|
|
|
var response = await conv.UpdateConversationTitle(conversationId, newTile.NewTitle);
|
2024-08-22 03:59:40 +00:00
|
|
|
return response != null;
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-12 03:28:00 +00:00
|
|
|
[HttpPut("/conversation/{conversationId}/update-title-alias")]
|
|
|
|
|
public async Task<bool> UpdateConversationTitleAlias([FromRoute] string conversationId, [FromBody] UpdateConversationTitleAliasModel newTile)
|
|
|
|
|
{
|
|
|
|
|
var userService = _services.GetRequiredService<IUserService>();
|
|
|
|
|
var conversationService = _services.GetRequiredService<IConversationService>();
|
|
|
|
|
|
2025-03-10 17:14:44 +00:00
|
|
|
var (isAdmin, user) = await userService.IsAdminUser(_user.Id);
|
2024-12-12 03:28:00 +00:00
|
|
|
var filter = new ConversationFilter
|
|
|
|
|
{
|
|
|
|
|
Id = conversationId,
|
2025-03-10 17:14:44 +00:00
|
|
|
UserId = !isAdmin ? user?.Id : null
|
2024-12-12 03:28:00 +00:00
|
|
|
};
|
|
|
|
|
var conversations = await conversationService.GetConversations(filter);
|
|
|
|
|
|
|
|
|
|
if (conversations.Items.IsNullOrEmpty())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var response = await conversationService.UpdateConversationTitleAlias(conversationId, newTile.NewTitleAlias);
|
|
|
|
|
return response != null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2024-10-18 20:50:39 +00:00
|
|
|
[HttpPut("/conversation/{conversationId}/update-tags")]
|
|
|
|
|
public async Task<bool> UpdateConversationTags([FromRoute] string conversationId, [FromBody] UpdateConversationRequest request)
|
|
|
|
|
{
|
|
|
|
|
var conv = _services.GetRequiredService<IConversationService>();
|
2025-03-13 18:06:00 +00:00
|
|
|
return await conv.UpdateConversationTags(conversationId, request.ToAddTags, request.ToDeleteTags);
|
2024-10-18 20:50:39 +00:00
|
|
|
}
|
|
|
|
|
|
2024-10-11 20:07:41 +00:00
|
|
|
[HttpPut("/conversation/{conversationId}/update-message")]
|
|
|
|
|
public async Task<bool> UpdateConversationMessage([FromRoute] string conversationId, [FromBody] UpdateMessageModel model)
|
|
|
|
|
{
|
|
|
|
|
var conversationService = _services.GetRequiredService<IConversationService>();
|
|
|
|
|
var request = new UpdateMessageRequest
|
|
|
|
|
{
|
|
|
|
|
Message = new DialogElement
|
|
|
|
|
{
|
|
|
|
|
MetaData = new DialogMetaData
|
|
|
|
|
{
|
|
|
|
|
MessageId = model.Message.MessageId,
|
|
|
|
|
Role = model.Message.Sender?.Role
|
|
|
|
|
},
|
|
|
|
|
Content = model.Message.Text,
|
|
|
|
|
RichContent = JsonSerializer.Serialize(model.Message.RichContent, _jsonOptions),
|
|
|
|
|
},
|
|
|
|
|
InnderIndex = model.InnerIndex
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return await conversationService.UpdateConversationMessage(conversationId, request);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
2025-03-10 17:14:44 +00:00
|
|
|
var (isAdmin, user) = await userService.IsAdminUser(_user.Id);
|
2024-05-15 23:07:11 +00:00
|
|
|
var filter = new ConversationFilter
|
|
|
|
|
{
|
|
|
|
|
Id = conversationId,
|
2025-03-10 17:14:44 +00:00
|
|
|
UserId = !isAdmin ? user?.Id : null
|
2024-05-15 23:07:11 +00:00
|
|
|
};
|
|
|
|
|
var conversations = await conversationService.GetConversations(filter);
|
|
|
|
|
|
|
|
|
|
if (conversations.Items.IsNullOrEmpty())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-07 07:34:52 +00:00
|
|
|
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}")]
|
2024-06-19 23:04:47 +00:00
|
|
|
public async Task<string?> DeleteConversationMessage([FromRoute] string conversationId, [FromRoute] string messageId, [FromBody] TruncateMessageRequest request)
|
2024-01-29 01:33:40 +00:00
|
|
|
{
|
|
|
|
|
var conversationService = _services.GetRequiredService<IConversationService>();
|
2024-06-19 23:04:47 +00:00
|
|
|
var newMessageId = request.isNewMessage ? Guid.NewGuid().ToString() : null;
|
|
|
|
|
var isSuccess = await conversationService.TruncateConversation(conversationId, messageId, newMessageId);
|
|
|
|
|
return isSuccess ? newMessageId : string.Empty;
|
2024-01-29 01:33:40 +00:00
|
|
|
}
|
|
|
|
|
|
2024-10-07 21:35:39 +00:00
|
|
|
#region Send notification
|
|
|
|
|
[HttpPost("/conversation/{conversationId}/notification")]
|
|
|
|
|
public async Task<ChatResponseModel> SendNotification([FromRoute] string conversationId, [FromBody] NewMessageModel input)
|
|
|
|
|
{
|
|
|
|
|
var conv = _services.GetRequiredService<IConversationService>();
|
|
|
|
|
var routing = _services.GetRequiredService<IRoutingService>();
|
|
|
|
|
var userService = _services.GetRequiredService<IUserService>();
|
|
|
|
|
|
|
|
|
|
conv.SetConversationId(conversationId, new List<MessageState>(), isReadOnly: true);
|
|
|
|
|
|
|
|
|
|
var inputMsg = new RoleDialogModel(AgentRole.User, input.Text)
|
|
|
|
|
{
|
|
|
|
|
MessageId = Guid.NewGuid().ToString(),
|
|
|
|
|
CreatedAt = DateTime.UtcNow
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var user = await userService.GetUser(_user.Id);
|
|
|
|
|
var response = new ChatResponseModel()
|
|
|
|
|
{
|
|
|
|
|
ConversationId = conversationId,
|
|
|
|
|
MessageId = inputMsg.MessageId,
|
|
|
|
|
Sender = new UserViewModel
|
|
|
|
|
{
|
|
|
|
|
Id = user?.Id ?? string.Empty,
|
|
|
|
|
FirstName = user?.FirstName ?? string.Empty,
|
|
|
|
|
LastName = user?.LastName ?? string.Empty
|
|
|
|
|
},
|
|
|
|
|
CreatedAt = DateTime.UtcNow
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await HookEmitter.Emit<IConversationHook>(_services, async hook =>
|
|
|
|
|
await hook.OnNotificationGenerated(inputMsg)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return response;
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
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,
|
2023-10-27 22:16:53 +00:00
|
|
|
[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-06-19 23:04:47 +00:00
|
|
|
MessageId = !string.IsNullOrWhiteSpace(input.InputMessageId) ? input.InputMessageId : Guid.NewGuid().ToString(),
|
2024-05-10 02:37:32 +00:00
|
|
|
CreatedAt = DateTime.UtcNow
|
2024-05-03 19:57:44 +00:00
|
|
|
};
|
2024-05-10 02:37:32 +00:00
|
|
|
|
2024-03-26 21:57:13 +00:00
|
|
|
var routing = _services.GetRequiredService<IRoutingService>();
|
|
|
|
|
routing.Context.SetMessageId(conversationId, inputMsg.MessageId);
|
|
|
|
|
|
2024-07-26 22:40:49 +00:00
|
|
|
conv.SetConversationId(conversationId, input.States);
|
2025-04-14 18:19:41 +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-06-19 23:04:47 +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 =>
|
2023-08-20 19:02:59 +00:00
|
|
|
{
|
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-09-12 10:49:45 +00:00
|
|
|
});
|
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")]
|
2024-08-27 15:42:48 +00:00
|
|
|
public async Task SendMessageSse([FromRoute] string agentId, [FromRoute] string conversationId, [FromBody] NewMessageModel input)
|
2024-04-30 21:25:58 +00:00
|
|
|
{
|
|
|
|
|
var conv = _services.GetRequiredService<IConversationService>();
|
2024-05-06 22:31:52 +00:00
|
|
|
var inputMsg = new RoleDialogModel(AgentRole.User, input.Text)
|
|
|
|
|
{
|
2024-06-19 23:04:47 +00:00
|
|
|
MessageId = !string.IsNullOrWhiteSpace(input.InputMessageId) ? input.InputMessageId : Guid.NewGuid().ToString(),
|
2024-05-10 02:37:32 +00:00
|
|
|
CreatedAt = DateTime.UtcNow
|
2024-05-06 22:31:52 +00:00
|
|
|
};
|
2024-05-10 02:37:32 +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");
|
2024-09-12 10:49:45 +00:00
|
|
|
InitProgressService(conversationId);
|
2024-04-30 21:25:58 +00:00
|
|
|
|
|
|
|
|
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
|
|
|
});
|
|
|
|
|
|
|
|
|
|
response.States = state.GetStates();
|
|
|
|
|
response.MessageId = inputMsg.MessageId;
|
|
|
|
|
response.ConversationId = conversationId;
|
|
|
|
|
|
|
|
|
|
// await OnEventCompleted(Response);
|
|
|
|
|
}
|
2024-09-12 10:49:45 +00:00
|
|
|
|
|
|
|
|
private void InitProgressService(string conversationId)
|
|
|
|
|
{
|
|
|
|
|
var progressService = _services.GetService<IConversationProgressService>();
|
|
|
|
|
progressService.OnFunctionExecuting = async msg =>
|
|
|
|
|
{
|
|
|
|
|
var indicator = new ChatResponseModel
|
|
|
|
|
{
|
|
|
|
|
ConversationId = conversationId,
|
|
|
|
|
MessageId = msg.MessageId,
|
|
|
|
|
Text = msg.Indication,
|
|
|
|
|
Function = "indicating",
|
|
|
|
|
Instruction = msg.Instruction,
|
|
|
|
|
States = new Dictionary<string, string>()
|
|
|
|
|
};
|
|
|
|
|
await OnChunkReceived(Response, indicator);
|
|
|
|
|
};
|
|
|
|
|
progressService.OnFunctionExecuted = async msg => { };
|
|
|
|
|
}
|
2024-05-30 00:27:38 +00:00
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Files and attachments
|
|
|
|
|
[HttpPost("/conversation/{conversationId}/attachments")]
|
2024-09-09 22:42:09 +00:00
|
|
|
public IActionResult UploadAttachments([FromRoute] string conversationId, IFormFile[] files)
|
2024-05-30 00:27:38 +00:00
|
|
|
{
|
|
|
|
|
if (files != null && files.Length > 0)
|
|
|
|
|
{
|
2024-08-08 01:56:29 +00:00
|
|
|
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
|
|
|
|
var dir = fileStorage.GetDirectory(conversationId);
|
2024-05-30 00:27:38 +00:00
|
|
|
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);
|
|
|
|
|
|
2024-08-08 01:56:29 +00:00
|
|
|
fileStorage.SaveFileStreamToPath(filePath, file.OpenReadStream());
|
2024-05-30 00:27:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Ok(new { message = "File uploaded successfully." });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return BadRequest(new { message = "Invalid file." });
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-19 23:04:47 +00:00
|
|
|
[HttpPost("/agent/{agentId}/conversation/{conversationId}/upload")]
|
2024-08-27 15:42:48 +00:00
|
|
|
public async Task<string> UploadConversationMessageFiles([FromRoute] string agentId, [FromRoute] string conversationId, [FromBody] InputMessageFiles input)
|
2024-06-19 23:04:47 +00:00
|
|
|
{
|
|
|
|
|
var convService = _services.GetRequiredService<IConversationService>();
|
|
|
|
|
convService.SetConversationId(conversationId, input.States);
|
|
|
|
|
var conv = await convService.GetConversationRecordOrCreateNew(agentId);
|
2024-08-08 01:56:29 +00:00
|
|
|
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
2024-06-19 23:04:47 +00:00
|
|
|
var messageId = Guid.NewGuid().ToString();
|
2024-08-08 01:56:29 +00:00
|
|
|
var isSaved = fileStorage.SaveMessageFiles(conv.Id, messageId, FileSourceType.User, input.Files);
|
2024-06-19 23:04:47 +00:00
|
|
|
return isSaved ? messageId : string.Empty;
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-06 19:57:41 +00:00
|
|
|
[HttpGet("/conversation/{conversationId}/files/{messageId}/{source}")]
|
2024-06-19 23:04:47 +00:00
|
|
|
public IEnumerable<MessageFileViewModel> GetConversationMessageFiles([FromRoute] string conversationId, [FromRoute] string messageId, [FromRoute] string source)
|
2024-05-30 00:27:38 +00:00
|
|
|
{
|
2024-08-08 01:56:29 +00:00
|
|
|
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
|
|
|
|
var files = fileStorage.GetMessageFiles(conversationId, new List<string> { messageId }, source);
|
2024-05-30 00:27:38 +00:00
|
|
|
return files?.Select(x => MessageFileViewModel.Transform(x))?.ToList() ?? new List<MessageFileViewModel>();
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-06 19:57:41 +00:00
|
|
|
[HttpGet("/conversation/{conversationId}/message/{messageId}/{source}/file/{index}/{fileName}")]
|
|
|
|
|
public IActionResult GetMessageFile([FromRoute] string conversationId, [FromRoute] string messageId, [FromRoute] string source, [FromRoute] string index, [FromRoute] string fileName)
|
2024-05-30 00:27:38 +00:00
|
|
|
{
|
2024-08-08 01:56:29 +00:00
|
|
|
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
|
|
|
|
var file = fileStorage.GetMessageFile(conversationId, messageId, source, index, fileName);
|
2024-05-30 00:27:38 +00:00
|
|
|
if (string.IsNullOrEmpty(file))
|
|
|
|
|
{
|
|
|
|
|
return NotFound();
|
|
|
|
|
}
|
|
|
|
|
return BuildFileResult(file);
|
|
|
|
|
}
|
2024-11-04 18:12:40 +00:00
|
|
|
|
|
|
|
|
[HttpGet("/conversation/{conversationId}/message/{messageId}/{source}/file/{index}/{fileName}/download")]
|
|
|
|
|
public IActionResult DownloadMessageFile([FromRoute] string conversationId, [FromRoute] string messageId, [FromRoute] string source, [FromRoute] string index, [FromRoute] string fileName)
|
|
|
|
|
{
|
|
|
|
|
var fileStorage = _services.GetRequiredService<IFileStorageService>();
|
|
|
|
|
var file = fileStorage.GetMessageFile(conversationId, messageId, source, index, fileName);
|
|
|
|
|
if (string.IsNullOrEmpty(file))
|
|
|
|
|
{
|
|
|
|
|
return NotFound();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var fName = file.Split(Path.DirectorySeparatorChar).Last();
|
|
|
|
|
var contentType = FileUtility.GetFileContentType(fName);
|
|
|
|
|
var 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);
|
|
|
|
|
stream.Position = 0;
|
|
|
|
|
|
|
|
|
|
return new FileStreamResult(stream, contentType) { FileDownloadName = fName };
|
|
|
|
|
}
|
2024-05-30 00:27:38 +00:00
|
|
|
#endregion
|
|
|
|
|
|
2025-01-31 20:49:38 +00:00
|
|
|
#region Dashboard
|
2024-11-21 19:07:00 +00:00
|
|
|
[HttpPut("/agent/{agentId}/conversation/{conversationId}/dashboard")]
|
2024-11-07 15:12:10 +00:00
|
|
|
public async Task<bool> PinConversationToDashboard([FromRoute] string agentId, [FromRoute] string conversationId)
|
|
|
|
|
{
|
|
|
|
|
var userService = _services.GetRequiredService<IUserService>();
|
2025-01-31 20:49:38 +00:00
|
|
|
var pinned = await userService.AddDashboardConversation(conversationId);
|
2024-11-07 15:12:10 +00:00
|
|
|
return pinned;
|
|
|
|
|
}
|
2024-11-21 19:07:00 +00:00
|
|
|
|
|
|
|
|
[HttpDelete("/agent/{agentId}/conversation/{conversationId}/dashboard")]
|
|
|
|
|
public async Task<bool> UnpinConversationFromDashboard([FromRoute] string agentId, [FromRoute] string conversationId)
|
|
|
|
|
{
|
|
|
|
|
var userService = _services.GetRequiredService<IUserService>();
|
2025-01-31 20:49:38 +00:00
|
|
|
var unpinned = await userService.RemoveDashboardConversation(conversationId);
|
2024-11-21 19:07:00 +00:00
|
|
|
return unpinned;
|
|
|
|
|
}
|
2024-11-07 15:12:10 +00:00
|
|
|
#endregion
|
|
|
|
|
|
2025-02-11 20:27:53 +00:00
|
|
|
#region Search state keys
|
|
|
|
|
[HttpGet("/conversation/state/keys")]
|
2025-03-10 17:14:44 +00:00
|
|
|
public async Task<List<string>> GetConversationStateKeys([FromQuery] ConversationStateKeysFilter request)
|
2025-02-11 20:27:53 +00:00
|
|
|
{
|
|
|
|
|
var convService = _services.GetRequiredService<IConversationService>();
|
2025-03-10 17:14:44 +00:00
|
|
|
var keys = await convService.GetConversationStateSearhKeys(request);
|
2025-02-11 20:27:53 +00:00
|
|
|
return keys;
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
2025-03-06 22:46:38 +00:00
|
|
|
#region Migrate Latest States
|
|
|
|
|
[HttpPost("/conversation/latest-state/migrate")]
|
|
|
|
|
public async Task<bool> MigrateConversationLatestStates([FromBody] MigrateLatestStateRequest request)
|
|
|
|
|
{
|
|
|
|
|
var convService = _services.GetRequiredService<IConversationService>();
|
|
|
|
|
var res = await convService.MigrateLatestStates(request.BatchSize, request.ErrorLimit);
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
2024-05-30 00:27:38 +00:00
|
|
|
#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);
|
2024-08-02 21:26:11 +00:00
|
|
|
var fileExtension = Path.GetExtension(file).ToLower();
|
|
|
|
|
var enableRangeProcessing = FileConstants.AudioExtensions.Contains(fileExtension);
|
2024-08-02 21:24:44 +00:00
|
|
|
return File(bytes, "application/octet-stream", Path.GetFileName(file), enableRangeProcessing: enableRangeProcessing);
|
2024-05-30 00:27:38 +00:00
|
|
|
}
|
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
|
|
|
}
|