diff --git a/hugvey/story.py b/hugvey/story.py index 09d66f1..c372ad7 100644 --- a/hugvey/story.py +++ b/hugvey/story.py @@ -432,10 +432,22 @@ class Diversion(object): self.id = id self.params = params self.finaliseMethod = None + self.hasHit = False if type == 'no_response': self.method = self._divergeIfNoResponse self.finaliseMethod = self._returnAfterNoResponse self.counter = 0 + if type == 'reply_contains': + self.method = self._divergeIfReplyContains + self.finaliseMethod = self._returnAfterReplyContains + if len(self.params['regex']) > 0: + self.regex = re.compile(self.params['regex']) + else: + self.regex = None + +# if type == 'timeout': +# self.method = self._divergeIfNoResponse +# self.finaliseMethod = self._returnAfterNoResponse if type == 'repeat': self.method = self._divergeIfRepeatRequest self.regex = re.compile(self.params['regex']) @@ -460,13 +472,17 @@ class Diversion(object): Validate if condition is met for the current story state Returns True when diverging """ - return await self.method(story, msgFrom, msgTo) + r = await self.method(story, msgFrom, msgTo) + if r: + self.hasHit = True + return r async def finalise(self, story): """" Only used if the Diversion sets the story.currentDiversion """ if not self.finaliseMethod: + story.logger.info(f"No finalisation for diversion {self.id}") return False await self.finaliseMethod(story) return True @@ -499,6 +515,45 @@ class Diversion(object): story.stats['consecutiveSilentTimeouts'] = 0 # reset counter after diverging if self.params['returnAfterStrand']: await story.setCurrentMessage(self.returnMessage) + + async def _divergeIfReplyContains(self, story, msgFrom, msgTo): + """ + Participant doesn't speak for x consecutive replies (has had timeout) + """ + ':type story: Story' + if story.currentDiversion: + # don't do nested diversions + # if we remove this, don't forget to double check 'returnMessage' + return False + + if self.hasHit: + # don't match twice + return + + if story.currentReply is None or not self.regex: + return + + r = self.regex.search(story.currentReply.getText()) + if r is None: + return + + logger.info(f"Diverge: reply contains {self.id}") + story.stats['diversions']['reply_contains'] += 1 + + msg = story.get(self.params['msgId']) + if msg is None: + story.logger.critical(f"Not a valid message id for diversion: {self.params['msgId']}") + return + + self.returnMessage = msgTo + await story.setCurrentMessage(msg) + story.currentDiversion = self + return True + + async def _returnAfterReplyContains(self, story): + story.logger.info(f"Finalise diversion: {self.id}") + if self.params['returnAfterStrand']: + await story.setCurrentMessage(self.returnMessage) async def _divergeIfRepeatRequest(self, story, msgFrom, msgTo): """ @@ -708,6 +763,8 @@ class Story(object): 'diversions': { 'no_response': 0, 'repeat': 0, + 'reply_contains': 0, + 'timeout': 0 } } @@ -781,13 +838,14 @@ class Story(object): # self.hugvey.google.resume() if self.currentMessage.id not in self.directionsPerMsg: + print(self.currentDiversion) if self.currentDiversion is not None: self.logger.info("end of diversion") await self.currentDiversion.finalise(self) self.currentDiversion = None else: self.logger.info("THE END!") - self.stop() + self.finish() return if e['event'] == 'speech': @@ -882,8 +940,7 @@ class Story(object): for i in range(len(self.events)): await self._processPendingEvents() - if self.currentMessage.id not in self.directionsPerMsg: - self.finish() + # The finish is not here anymore, but only on the playbackFinish event. directions = self.getCurrentDirections() await self._processDirections(directions) @@ -998,6 +1055,7 @@ class Story(object): def finish(self): self.logger.info(f"Finished story for {self.hugvey.id}") self.hugvey.eventLogger.info("story: finished") + self.stop() self.hugvey.pause() self.finish_time = time.time() self.timer.pause() diff --git a/www/js/hugvey_console.js b/www/js/hugvey_console.js index 8b6a432..be4b2f5 100644 --- a/www/js/hugvey_console.js +++ b/www/js/hugvey_console.js @@ -440,6 +440,11 @@ class Graph { div['params']['returnAfterStrand'] = true; div['params']['msgId'] = ""; } + else if(type == 'reply_contains') { + div['params']['regex'] = ""; + div['params']['returnAfterStrand'] = true; + div['params']['msgId'] = ""; + } else if(type == 'repeat') { div['params']['regex'] = "can you repeat that\\?"; } else { @@ -464,7 +469,7 @@ class Graph { let msgEl = document.getElementById( 'msg' ); msgEl.innerHTML = ""; - let divsNoResponse =[], divsRepeat = []; + let divsNoResponse =[], divsRepeat = [], divsReplyContains = [], divsTimeouts = []; for(let div of this.diversions) { if(div['type'] == 'no_response') { let returnAttrs = { @@ -535,6 +540,68 @@ class Graph { }}, ...msgOptions) ) )); + } + if(div['type'] == 'reply_contains') { + let returnAttrs = { + 'type': 'checkbox', + 'on': { + 'change': (e) => div['params']['returnAfterStrand'] = e.target.checked + } + } + if(div['params']['returnAfterStrand']) { + returnAttrs['checked'] = 'checked'; + } + let msgOptions = [crel('option',"")]; + let starts = this.messages.filter( m => m.hasOwnProperty('start') && m['start'] == true); + for(let startMsg of starts) { + let optionParams = {}; + if(div['params']['msgId'] == startMsg['@id']) { + optionParams['selected'] = 'selected'; + } + msgOptions.push(crel('option', optionParams , startMsg['@id'])); + } + + divsReplyContains.push(crel( + 'div', { + 'class': 'diversion', + 'on': { + 'mouseover': function(e) { + if(div['params']['msgId']) + document.getElementById(div['params']['msgId']).classList.add('selectedMsg'); + }, + 'mouseout': function(e) { + if(div['params']['msgId']) + document.getElementById(div['params']['msgId']).classList.remove('selectedMsg'); + } + } + }, + crel('h3', div['@id']), + crel( + 'div', { + 'class':'btn btn--delete', + 'on': { + 'click': (e) => this.deleteDiversion(div) + } + }, 'Delete diversion'), + crel('label', 'Regex', + crel('input', { + 'type': 'text', + 'value': div['params']['regex'], + 'placeholder': 'regex', + 'on': { + 'change': (e) => div['params']['regex'] = e.target.value + } + }) + ), + crel('label', 'Return to point of departure afterwards', + crel('input', returnAttrs) + ), + crel('label', 'Go to (start message)', + crel('select', {'on': { + 'change': (e) => div['params']['msgId'] = e.target.value + }}, ...msgOptions) + ) + )); } if(div['type'] == 'repeat'){ divsRepeat.push(crel( @@ -560,7 +627,7 @@ class Graph { } } - console.log(divsNoResponse, divsRepeat); + console.log(divsReplyContains, divsNoResponse, divsRepeat); let divEl = crel( 'div', @@ -581,6 +648,19 @@ class Graph { 'New case for no_response' ) ), + crel('div', + crel('h2', 'Reply Contains'), + ...divsReplyContains, + crel('div', + { + 'class': 'btn', + 'on': { + 'click': (e) => this.createDiversion('reply_contains') + } + }, + 'New case for reply contains' + ) + ), crel('div', crel('h2', 'Request repeat'), ...divsRepeat,