11using BotSharp . Abstraction . Files ;
2- using BotSharp . Abstraction . Routing ;
32using BotSharp . Core . Infrastructures ;
43using BotSharp . Plugin . Twilio . Models ;
54using BotSharp . Plugin . Twilio . Services ;
109namespace BotSharp . Plugin . Twilio . Controllers ;
1110
1211[ AllowAnonymous ]
13- [ Route ( "[controller] " ) ]
12+ [ Route ( "twilio/voice " ) ]
1413public class TwilioVoiceController : TwilioController
1514{
1615 private readonly TwilioSetting _settings ;
@@ -38,73 +37,25 @@ public Token GetAccessToken()
3837 } ;
3938 }
4039
41- [ HttpPost ( "/twilio/voice/welcome" ) ]
42- public async Task < TwiMLResult > StartConversation ( VoiceRequest request )
43- {
44- string sessionId = $ "TwilioVoice_{ request . CallSid } ";
45- var twilio = _services . GetRequiredService < TwilioService > ( ) ;
46- var response = twilio . ReturnInstructions ( "Hello, how may I help you?" ) ;
47- return TwiML ( response ) ;
48- }
49-
50- [ HttpPost ( "/twilio/voice/{agentId}" ) ]
51- public async Task < TwiMLResult > ReceivedVoiceMessage ( [ FromRoute ] string agentId , VoiceRequest input )
52- {
53- string sessionId = $ "TwilioVoice_{ input . CallSid } ";
54-
55- var inputMsg = new RoleDialogModel ( AgentRole . User , input . SpeechResult ) ;
56- var conv = _services . GetRequiredService < IConversationService > ( ) ;
57- var routing = _services . GetRequiredService < IRoutingService > ( ) ;
58- routing . Context . SetMessageId ( sessionId , inputMsg . MessageId ) ;
59-
60- conv . SetConversationId ( sessionId , new List < MessageState >
61- {
62- new MessageState ( "channel" , ConversationChannel . Phone ) ,
63- new MessageState ( "calling_phone" , input . DialCallSid )
64- } ) ;
65-
66- var twilio = _services . GetRequiredService < TwilioService > ( ) ;
67- VoiceResponse response = default ;
68-
69- var result = await conv . SendMessage ( agentId ,
70- inputMsg ,
71- replyMessage : null ,
72- async msg =>
73- {
74- response = twilio . ReturnInstructions ( msg . Content ) ;
75- if ( msg . FunctionName == "conversation_end" )
76- {
77- response = twilio . HangUp ( msg . Content ) ;
78- }
79- } , async functionExecuting =>
80- {
81- } , async functionExecuted =>
82- {
83- } ) ;
84-
85- return TwiML ( response ) ;
86- }
87-
88-
89- [ HttpPost ( "start" ) ]
90- public TwiMLResult InitiateConversation ( VoiceRequest request )
40+ [ HttpPost ( "welcome" ) ]
41+ public TwiMLResult InitiateConversation ( VoiceRequest request , [ FromQuery ] string states )
9142 {
9243 if ( request ? . CallSid == null ) throw new ArgumentNullException ( nameof ( VoiceRequest . CallSid ) ) ;
93- string sessionId = $ "TwilioVoice_{ request . CallSid } ";
44+ string conversationId = $ "TwilioVoice_{ request . CallSid } ";
9445 var twilio = _services . GetRequiredService < TwilioService > ( ) ;
95- var url = $ "twiliovoice/ { sessionId } /send/0 ";
96- var response = twilio . ReturnInstructions ( "twilio/welcome.mp3" , url , false ) ;
46+ var url = $ "twilio/voice/ { conversationId } /receive/0?states= { states } ";
47+ var response = twilio . ReturnInstructions ( "twilio/welcome.mp3" , url , true ) ;
9748 return TwiML ( response ) ;
9849 }
9950
100- [ HttpPost ( "{sessionId}/send /{seqNum}" ) ]
101- public async Task < TwiMLResult > SendCallerMessage ( [ FromRoute ] string sessionId , [ FromRoute ] int seqNum , VoiceRequest request )
51+ [ HttpPost ( "{conversationId}/receive /{seqNum}" ) ]
52+ public async Task < TwiMLResult > ReceiveCallerMessage ( [ FromRoute ] string conversationId , [ FromRoute ] int seqNum , [ FromQuery ] string states , VoiceRequest request )
10253 {
10354 var twilio = _services . GetRequiredService < TwilioService > ( ) ;
10455 var messageQueue = _services . GetRequiredService < TwilioMessageQueue > ( ) ;
10556 var sessionManager = _services . GetRequiredService < ITwilioSessionManager > ( ) ;
106- var url = $ "twiliovoice/ { sessionId } /reply/{ seqNum } ";
107- var messages = await sessionManager . RetrieveStagedCallerMessagesAsync ( sessionId , seqNum ) ;
57+ var url = $ "twilio/voice/ { conversationId } /reply/{ seqNum } ?states= { states } ";
58+ var messages = await sessionManager . RetrieveStagedCallerMessagesAsync ( conversationId , seqNum ) ;
10859 if ( ! string . IsNullOrWhiteSpace ( request . SpeechResult ) )
10960 {
11061 messages . Add ( request . SpeechResult ) ;
@@ -113,48 +64,78 @@ public async Task<TwiMLResult> SendCallerMessage([FromRoute] string sessionId, [
11364 VoiceResponse response ;
11465 if ( ! string . IsNullOrWhiteSpace ( messageContent ) )
11566 {
67+
11668 var callerMessage = new CallerMessage ( )
11769 {
118- SessionId = sessionId ,
70+ ConversationId = conversationId ,
11971 SeqNumber = seqNum ,
12072 Content = messageContent ,
12173 From = request . From
12274 } ;
75+ if ( ! string . IsNullOrEmpty ( states ) )
76+ {
77+ var kvp = states . Split ( ':' ) ;
78+ if ( kvp . Length == 2 )
79+ {
80+ callerMessage . States . Add ( kvp [ 0 ] , kvp [ 1 ] ) ;
81+ }
82+ }
12383 await messageQueue . EnqueueAsync ( callerMessage ) ;
124- response = twilio . ReturnInstructions ( "twilio/holdon.mp3" , url , true ) ;
84+ response = twilio . ReturnInstructions ( null , url , true , 1 ) ;
12585 }
12686 else
12787 {
128- response = twilio . HangUp ( "twilio/holdon.mp3" ) ;
88+ var speechPath = seqNum > 0 ? $ "twilio/voice/speeches/{ conversationId } /{ seqNum - 1 } .mp3" : "twilio/welcome.mp3" ;
89+ response = twilio . ReturnInstructions ( speechPath , $ "twilio/voice/{ conversationId } /receive/{ seqNum } ?states={ states } ", true ) ;
12990 }
13091 return TwiML ( response ) ;
13192 }
13293
133- [ HttpPost ( "{sessionId }/reply/{seqNum}" ) ]
134- public async Task < TwiMLResult > ReplyCallerMessage ( [ FromRoute ] string sessionId , [ FromRoute ] int seqNum , VoiceRequest request )
94+ [ HttpPost ( "{conversationId }/reply/{seqNum}" ) ]
95+ public async Task < TwiMLResult > ReplyCallerMessage ( [ FromRoute ] string conversationId , [ FromRoute ] int seqNum , [ FromQuery ] string states , VoiceRequest request )
13596 {
13697 var nextSeqNum = seqNum + 1 ;
13798 var sessionManager = _services . GetRequiredService < ITwilioSessionManager > ( ) ;
13899 var twilio = _services . GetRequiredService < TwilioService > ( ) ;
139100 if ( request . SpeechResult != null )
140101 {
141- await sessionManager . StageCallerMessageAsync ( sessionId , nextSeqNum , request . SpeechResult ) ;
102+ await sessionManager . StageCallerMessageAsync ( conversationId , nextSeqNum , request . SpeechResult ) ;
142103 }
143- var reply = await sessionManager . GetAssistantReplyAsync ( sessionId , seqNum ) ;
104+ var reply = await sessionManager . GetAssistantReplyAsync ( conversationId , seqNum ) ;
144105 VoiceResponse response ;
145- if ( string . IsNullOrEmpty ( reply ) )
106+ if ( reply == null )
146107 {
147- response = twilio . ReturnInstructions ( null , $ "twiliovoice/{ sessionId } /reply/{ seqNum } ", true ) ;
108+ var indication = await sessionManager . GetReplyIndicationAsync ( conversationId , seqNum ) ;
109+ if ( indication != null )
110+ {
111+ var textToSpeechService = CompletionProvider . GetTextToSpeech ( _services , "openai" , "tts-1" ) ;
112+ var fileService = _services . GetRequiredService < IFileStorageService > ( ) ;
113+ var data = await textToSpeechService . GenerateSpeechFromTextAsync ( indication ) ;
114+ var fileName = $ "indication_{ seqNum } .mp3";
115+ await fileService . SaveSpeechFileAsync ( conversationId , fileName , data ) ;
116+ response = twilio . ReturnInstructions ( $ "twilio/voice/speeches/{ conversationId } /{ fileName } ", $ "twilio/voice/{ conversationId } /reply/{ seqNum } ?states={ states } ", true , 2 ) ;
117+ }
118+ else
119+ {
120+ response = twilio . ReturnInstructions ( null , $ "twilio/voice/{ conversationId } /reply/{ seqNum } ?states={ states } ", true , 1 ) ;
121+ }
148122 }
149123 else
150124 {
151-
152125 var textToSpeechService = CompletionProvider . GetTextToSpeech ( _services , "openai" , "tts-1" ) ;
153126 var fileService = _services . GetRequiredService < IFileStorageService > ( ) ;
154- var data = await textToSpeechService . GenerateSpeechFromTextAsync ( reply ) ;
155- var fileName = $ "{ seqNum } .mp3";
156- await fileService . SaveSpeechFileAsync ( sessionId , fileName , data ) ;
157- response = twilio . ReturnInstructions ( $ "twiliovoice/speeches/{ sessionId } /{ fileName } ", $ "twiliovoice/{ sessionId } /send/{ nextSeqNum } ", true ) ;
127+ var data = await textToSpeechService . GenerateSpeechFromTextAsync ( reply . Content ) ;
128+ var fileName = $ "reply_{ seqNum } .mp3";
129+ await fileService . SaveSpeechFileAsync ( conversationId , fileName , data ) ;
130+ if ( reply . ConversationEnd )
131+ {
132+ response = twilio . HangUp ( $ "twilio/voice/speeches/{ conversationId } /{ fileName } ") ;
133+ }
134+ else
135+ {
136+ response = twilio . ReturnInstructions ( $ "twilio/voice/speeches/{ conversationId } /{ fileName } ", $ "twilio/voice/{ conversationId } /receive/{ nextSeqNum } ?states={ states } ", true ) ;
137+ }
138+
158139 }
159140 return TwiML ( response ) ;
160141 }
0 commit comments