diff --git a/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs b/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs index 158257be..1cb09f3e 100644 --- a/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs +++ b/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Agents.Enums; +using BotSharp.Abstraction.Conversations.Enums; using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Users; @@ -47,20 +48,26 @@ public class RateLimitConversationHook : ConversationHookBase } } - // Check the number of conversations - var user = _services.GetRequiredService(); - var convService = _services.GetRequiredService(); - var results = await convService.GetConversations(new ConversationFilter - { - UserId = user.Id, - StartTime = DateTime.UtcNow.AddHours(-24), - }); + var states = _services.GetRequiredService(); + var channel = states.GetState("channel"); - if (results.Count > rateLimit.MaxConversationPerDay) + // Check the number of conversations + if (channel != ConversationChannel.Phone && channel != ConversationChannel.Email) { - message.Content = $"The number of conversations you have exceeds the system maximum of {rateLimit.MaxConversationPerDay}"; - message.StopCompletion = true; - return; + var user = _services.GetRequiredService(); + var convService = _services.GetRequiredService(); + var results = await convService.GetConversations(new ConversationFilter + { + UserId = user.Id, + StartTime = DateTime.UtcNow.AddHours(-24), + }); + + if (results.Count > rateLimit.MaxConversationPerDay) + { + message.Content = $"The number of conversations you have exceeds the system maximum of {rateLimit.MaxConversationPerDay}"; + message.StopCompletion = true; + return; + } } } } diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs index a89c6464..4cb1fa9c 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs @@ -4,6 +4,7 @@ using BotSharp.Plugin.Twilio.Models; using BotSharp.Plugin.Twilio.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Twilio.Http; namespace BotSharp.Plugin.Twilio.Controllers; @@ -35,7 +36,7 @@ public class TwilioVoiceController : TwilioController string conversationId = $"TwilioVoice_{request.CallSid}"; var twilio = _services.GetRequiredService(); var url = $"twilio/voice/{conversationId}/receive/0?states={states}"; - var response = twilio.ReturnInstructions("twilio/welcome.mp3", url, true); + var response = twilio.ReturnInstructions(new List { "twilio/welcome.mp3" }, url, true); return TwiML(response); } @@ -53,17 +54,10 @@ public class TwilioVoiceController : TwilioController messages.Add(text); await sessionManager.StageCallerMessageAsync(conversationId, seqNum, text); } + VoiceResponse response; - if (messages.Count == 0 && seqNum == 0) - { - response = twilio.ReturnInstructions("twilio/welcome.mp3", $"twilio/voice/{conversationId}/receive/{seqNum}?states={states}", true, timeout: 2); - } - else + if (messages.Any()) { - if (messages.Count == 0) - { - messages = await sessionManager.RetrieveStagedCallerMessagesAsync(conversationId, seqNum - 1); - } var messageContent = string.Join("\r\n", messages); var callerMessage = new CallerMessage() { @@ -82,15 +76,21 @@ public class TwilioVoiceController : TwilioController } await messageQueue.EnqueueAsync(callerMessage); - int audioIndex = Random.Shared.Next(1, 5); - response = twilio.ReturnInstructions($"twilio/hold-on-{audioIndex}.mp3", $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}", true, 1); + response = new VoiceResponse() + .Redirect(new Uri($"{_settings.CallbackHost}/twilio/voice/{conversationId}/reply/{seqNum}?states={states}"), HttpMethod.Post); } + else + { + response = twilio.ReturnInstructions(null, $"twilio/voice/{conversationId}/receive/{seqNum}?states={states}", true); + } + return TwiML(response); } [ValidateRequest] [HttpPost("twilio/voice/{conversationId}/reply/{seqNum}")] - public async Task ReplyCallerMessage([FromRoute] string conversationId, [FromRoute] int seqNum, [FromQuery] string states, VoiceRequest request) + public async Task ReplyCallerMessage([FromRoute] string conversationId, [FromRoute] int seqNum, + [FromQuery] string states, [FromQuery] string play, VoiceRequest request) { var nextSeqNum = seqNum + 1; var sessionManager = _services.GetRequiredService(); @@ -106,25 +106,33 @@ public class TwilioVoiceController : TwilioController var indication = await sessionManager.GetReplyIndicationAsync(conversationId, seqNum); if (indication != null) { - string speechPath; - if (indication.StartsWith('#')) + var speechPaths = new List(); + foreach (var text in indication.Split('|')) { - speechPath = $"twilio/{indication.Substring(1)}"; + var seg = text.Trim(); + if (seg.StartsWith('#')) + { + speechPaths.Add($"twilio/{seg.Substring(1)}.mp3"); + } + else + { + var textToSpeechService = CompletionProvider.GetTextToSpeech(_services, "openai", "tts-1"); + var fileService = _services.GetRequiredService(); + var data = await textToSpeechService.GenerateSpeechFromTextAsync(seg); + var fileName = $"indication_{seqNum}.mp3"; + await fileService.SaveSpeechFileAsync(conversationId, fileName, data); + speechPaths.Add($"twilio/voice/speeches/{conversationId}/{fileName}"); + } } - else - { - var textToSpeechService = CompletionProvider.GetTextToSpeech(_services, "openai", "tts-1"); - var fileService = _services.GetRequiredService(); - var data = await textToSpeechService.GenerateSpeechFromTextAsync(indication); - var fileName = $"indication_{seqNum}.mp3"; - await fileService.SaveSpeechFileAsync(conversationId, fileName, data); - speechPath = $"twilio/voice/speeches/{conversationId}/{fileName}"; - } - response = twilio.ReturnInstructions(speechPath, $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}", true, 2); + response = twilio.ReturnInstructions(speechPaths, $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}", true); } else { - response = twilio.ReturnInstructions(null, $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}", true, 1); + response = twilio.ReturnInstructions(new List + { + $"twilio/hold-on-{Random.Shared.Next(1, 5)}.mp3", + $"twilio/typing-{Random.Shared.Next(2, 4)}.mp3" + }, $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}", true); } } else @@ -135,7 +143,7 @@ public class TwilioVoiceController : TwilioController } else { - response = twilio.ReturnInstructions($"twilio/voice/speeches/{conversationId}/{reply.SpeechFileName}", $"twilio/voice/{conversationId}/receive/{nextSeqNum}?states={states}", true); + response = twilio.ReturnInstructions(new List { $"twilio/voice/speeches/{conversationId}/{reply.SpeechFileName}" }, $"twilio/voice/{conversationId}/receive/{nextSeqNum}?states={states}", true); } } diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs index 19e0ce68..3338c46d 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs @@ -82,7 +82,7 @@ namespace BotSharp.Plugin.Twilio.Services { reply = new AssistantMessage() { - ConversationEnd = msg.Instruction.ConversationEnd, + ConversationEnd = msg.Instruction?.ConversationEnd ?? false, Content = msg.Content, MessageId = msg.MessageId }; diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs index 309d627c..5ccdc7db 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs @@ -64,7 +64,7 @@ public class TwilioService return response; } - public VoiceResponse ReturnInstructions(string speechPath, string callbackPath, bool actionOnEmptyResult, int timeout = 3) + public VoiceResponse ReturnInstructions(List speechPaths, string callbackPath, bool actionOnEmptyResult, int timeout = 2) { var response = new VoiceResponse(); var gather = new Gather() @@ -80,13 +80,11 @@ public class TwilioService Timeout = timeout > 0 ? timeout : 3, ActionOnEmptyResult = actionOnEmptyResult }; - if (!string.IsNullOrEmpty(speechPath)) + if (speechPaths != null && speechPaths.Any()) { - gather.Play(new Uri($"{_settings.CallbackHost}/{speechPath}")); - if (speechPath.Contains("hold-on-")) + foreach (var speechPath in speechPaths) { - int audioIndex = Random.Shared.Next(1, 4); - gather.Play(new Uri($"{_settings.CallbackHost}/twilio/typing-{audioIndex}.mp3")); + gather.Play(new Uri($"{_settings.CallbackHost}/{speechPath}")); } } response.Append(gather);