diff --git a/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs b/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs index 158257be3..1cb09f3e2 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 override async Task OnMessageReceived(RoleDialogModel message) } } + var states = _services.GetRequiredService(); + var channel = states.GetState("channel"); + // Check the number of conversations - var user = _services.GetRequiredService(); - var convService = _services.GetRequiredService(); - var results = await convService.GetConversations(new ConversationFilter + if (channel != ConversationChannel.Phone && channel != ConversationChannel.Email) { - UserId = user.Id, - StartTime = DateTime.UtcNow.AddHours(-24), - }); + 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; + 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 a89c64648..4cb1fa9cf 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.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Twilio.Http; namespace BotSharp.Plugin.Twilio.Controllers; @@ -35,7 +36,7 @@ public TwiMLResult InitiateConversation(VoiceRequest request, [FromQuery] string 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 async Task ReceiveCallerMessage([FromRoute] string conversat 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 async Task ReceiveCallerMessage([FromRoute] string conversat } 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 async Task ReplyCallerMessage([FromRoute] string conversatio 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 async Task ReplyCallerMessage([FromRoute] string conversatio } 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 19e0ce681..3338c46d8 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs @@ -82,7 +82,7 @@ private async Task ProcessUserMessageAsync(CallerMessage message) { 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 309d627c8..5ccdc7dba 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs @@ -64,7 +64,7 @@ public VoiceResponse ReturnInstructions(string message) 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 VoiceResponse ReturnInstructions(string speechPath, string callbackPath, 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);