Implement InstantMatch and OnlyIfNoReply
This commit is contained in:
parent
138ede6ad6
commit
46941e45a5
2 changed files with 71 additions and 27 deletions
|
@ -162,11 +162,19 @@ class Condition(object):
|
|||
return self.method(story)
|
||||
|
||||
def _hasMetTimeout(self, story):
|
||||
now = time.time()
|
||||
now = story.timer.getElapsed()
|
||||
# check if the message already finished playing
|
||||
if not story.lastMsgFinishTime:
|
||||
return False
|
||||
|
||||
if 'onlyIfNoReply' in self.vars and self.vars['onlyIfNoReply']:
|
||||
if story.currentReply and story.currentReply.hasUtterances():
|
||||
logger.log(LOG_BS, f'Only if no reply has text! {story.currentReply.getText()}')
|
||||
# 'onlyIfNoReply': only use this timeout if participants doesn't speak.
|
||||
return False
|
||||
# else:
|
||||
# logger.debug('Only if no reply has no text yet!')
|
||||
|
||||
return now - story.lastMsgFinishTime >= float(self.vars['seconds'])
|
||||
|
||||
def _hasMetReplyContains(self, story) -> bool:
|
||||
|
@ -197,6 +205,9 @@ class Condition(object):
|
|||
for captureGroup in results:
|
||||
story.variableValues[captureGroup] = results[captureGroup]
|
||||
|
||||
if 'instantMatch' in self.vars and self.vars['instantMatch']:
|
||||
logger.info(f"Instant match on {self.vars['regex']}, {self.vars}")
|
||||
return True
|
||||
# TODO: implement 'instant match' -> don't wait for isFinished()
|
||||
|
||||
if r.isSpeaking():
|
||||
|
@ -206,11 +217,14 @@ class Condition(object):
|
|||
# print(self.vars)
|
||||
# either there's a match, or nothing to match at all
|
||||
if 'delays' in self.vars:
|
||||
replyDuration = story.timer.getElapsed() - r.getFirstUtterance().endTime
|
||||
timeSinceReply = story.timer.getElapsed() - r.getLastUtterance().endTime
|
||||
if story.lastMsgFinishTime is None:
|
||||
return False
|
||||
# time between finishing playback and ending of speaking:
|
||||
replyDuration = r.getLastUtterance().endTime - story.lastMsgFinishTime
|
||||
delays = sorted(self.vars['delays'], key=lambda k: float(k['minReplyDuration']), reverse=True)
|
||||
for delay in delays:
|
||||
if replyDuration > float(delay['minReplyDuration']):
|
||||
timeSinceReply = story.timer.getElapsed() - r.getLastUtterance().endTime
|
||||
logger.log(LOG_BS, f"check delay duration is now {replyDuration}, already waiting for {timeSinceReply}, have to wait {delay['waitTime']}")
|
||||
if timeSinceReply > float(delay['waitTime']):
|
||||
return True
|
||||
|
@ -435,6 +449,9 @@ class Story(object):
|
|||
self.log = [] # all nodes/elements that are triggered
|
||||
self.currentReply = None
|
||||
|
||||
for msg in self.getMessages():
|
||||
pass
|
||||
|
||||
def add(self, obj):
|
||||
if obj.id in self.elements:
|
||||
# print(obj)
|
||||
|
@ -460,6 +477,8 @@ class Story(object):
|
|||
if id in self.elements:
|
||||
return self.elements[id]
|
||||
return None
|
||||
def getMessages(self):
|
||||
return [el for el in self.elements if type(el) == Message]
|
||||
|
||||
def stop(self):
|
||||
logger.info("Stop Story")
|
||||
|
@ -482,7 +501,8 @@ class Story(object):
|
|||
|
||||
if e['event'] == "playbackFinish":
|
||||
if e['msgId'] == self.currentMessage.id:
|
||||
self.lastMsgFinishTime = time.time()
|
||||
#TODO: migrate value to Messagage instead of Story
|
||||
self.lastMsgFinishTime = self.timer.getElapsed()
|
||||
|
||||
if self.currentMessage.id not in self.directionsPerMsg:
|
||||
logger.info("THE END!")
|
||||
|
@ -573,8 +593,14 @@ class Story(object):
|
|||
self.currentMessage = message
|
||||
self.lastMsgTime = time.time()
|
||||
self.lastMsgFinishTime = None # to be filled in by the event
|
||||
|
||||
# if not reset:
|
||||
self.previousReply = self.currentReply # we can use this for interrptions
|
||||
self.currentReply = self.currentMessage.reply
|
||||
# else:
|
||||
# # if we press 'save & play', it should not remember it's last reply to that msg
|
||||
# self.previousReply = self.currentReply # we can use this for interrptions
|
||||
# self.currentReply = self.currentMessage.reply
|
||||
|
||||
logger.info("Current message: ({0}) \"{1}\"".format(
|
||||
message.id, message.text))
|
||||
|
|
|
@ -15,7 +15,6 @@ class Panopticon {
|
|||
},
|
||||
methods: {
|
||||
time_passed: function( hugvey, property ) {
|
||||
console.log( "property!", Date( hugvey[property] * 1000 ) );
|
||||
return moment( Date( hugvey[property] * 1000 ) ).fromNow();
|
||||
},
|
||||
timer: function(hugvey, property) {
|
||||
|
@ -202,14 +201,12 @@ class Graph {
|
|||
let graph = this;
|
||||
this.controlDown = false;
|
||||
document.addEventListener( 'keydown', function( e ) {
|
||||
console.log( e );
|
||||
if ( e.which == "16" ) { // shift
|
||||
graph.controlDown = true;
|
||||
document.body.classList.add( 'controlDown' );
|
||||
}
|
||||
} );
|
||||
document.addEventListener( 'keyup', function( e ) {
|
||||
console.log( e );
|
||||
if ( e.which == "16" ) { // shift
|
||||
graph.controlDown = false;
|
||||
document.body.classList.remove( 'controlDown' );
|
||||
|
@ -236,8 +233,6 @@ class Graph {
|
|||
|
||||
clickMsg( msg ) {
|
||||
// event when a message is clicked.
|
||||
console.log( msg );
|
||||
|
||||
if ( this.controlDown ) {
|
||||
this.secondarySelectMsg( msg );
|
||||
} else {
|
||||
|
@ -282,7 +277,7 @@ class Graph {
|
|||
|
||||
let startAttributes = {
|
||||
'name': msg['@id'] + '-start',
|
||||
'readonly': 'readonly',
|
||||
// 'readonly': 'readonly',
|
||||
'type': 'checkbox',
|
||||
'on': {
|
||||
'change': this.getEditEventListener()
|
||||
|
@ -344,10 +339,10 @@ class Graph {
|
|||
'change': function(e) {
|
||||
audioSpan.innerHTML = "...";
|
||||
panopticon.graph.saveJson(msg['@id'], e.target, function(e2){
|
||||
console.log(e);
|
||||
// console.log(e, e2);
|
||||
audioSpan.innerHTML = e.target.files[0].name + "<sup>*</sup>";
|
||||
// reload graph:
|
||||
console.log('reload', panopticon.graph.language_code);
|
||||
// console.log('reload', panopticon.graph.language_code);
|
||||
panopticon.loadNarrative(panopticon.graph.language_code);
|
||||
});
|
||||
// console.log(this,e);
|
||||
|
@ -461,7 +456,6 @@ class Graph {
|
|||
|
||||
for ( let conditionId of direction['conditions'] ) {
|
||||
let condition = this.getNodeById( conditionId );
|
||||
console.log(conditionId, condition);
|
||||
directionEl.appendChild( this.getEditConditionFormEl( condition, direction ) );
|
||||
}
|
||||
|
||||
|
@ -518,7 +512,8 @@ class Graph {
|
|||
getConditionTypes() {
|
||||
return {
|
||||
'timeout': {
|
||||
'seconds': { 'type': 'number', 'value': 10, 'min': 0, 'step': 0.1, 'unit': "s" }
|
||||
'seconds': { 'type': 'number', 'value': 10, 'min': 0, 'step': 0.1, 'unit': "s" },
|
||||
'onlyIfNoReply': { 'type': 'checkbox', label: "Only if no reply", "title": "This timeout is only used if the participant doesn't say a word. If the participant starts speaking within the time of this timeout condition, only other conditions are applicable." }
|
||||
},
|
||||
'replyContains': {
|
||||
'delays.0.minReplyDuration': { 'type': 'number', 'value': 0, 'min': 0, 'step': 0.1, 'label': 'Delay 1 - reply duration', 'unit': "s", 'readonly': 'readonly' },
|
||||
|
@ -541,12 +536,16 @@ class Graph {
|
|||
attr['name'] = typeof conditionId == 'undefined' ? v : `${conditionId}-vars.${v}`;
|
||||
if(typeof values != 'undefined') {
|
||||
let value = this._getValueForPath(v, values);
|
||||
if(attr['type'] == 'checkbox' ) {
|
||||
if(value)
|
||||
attr['checked'] = 'checked';
|
||||
}
|
||||
attr['value'] = typeof value == 'undefined' ? "": value;
|
||||
attr['on'] = {
|
||||
'change': this.getEditEventListener()
|
||||
} ;
|
||||
} else {
|
||||
console.log(attr);
|
||||
// console.log(attr);
|
||||
}
|
||||
|
||||
inputs.push(
|
||||
|
@ -573,17 +572,21 @@ class Graph {
|
|||
_getValueForPath(path, vars) {
|
||||
path = path.split( '.' ); // use vars.test to set ['vars']['test'] = value
|
||||
let v = vars;
|
||||
let result = null;
|
||||
for ( let i = 0; i < path.length; i++ ) {
|
||||
if(!isNaN(parseInt(path[i])) && isFinite(path[i])) {
|
||||
// is int, use array, instead of obj
|
||||
path[i] = parseInt(path[i]);
|
||||
}
|
||||
v = v[path[i]];
|
||||
if(i == path.length - 1) {
|
||||
result = v;
|
||||
}
|
||||
if(typeof v == 'undefined') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -595,11 +598,9 @@ class Graph {
|
|||
*/
|
||||
_formPathToVars(path, value, vars) {
|
||||
path = path.split( '.' ); // use vars.test to set ['vars']['test'] = value
|
||||
console.log(path);
|
||||
let res = vars;
|
||||
for ( let i = 0; i < path.length; i++ ) {
|
||||
if ( i == ( path.length - 1 ) ) {
|
||||
console.log( 'last', path[i] );
|
||||
res[path[i]] = value;
|
||||
} else {
|
||||
if(!isNaN(parseInt(path[i+1])) && isFinite(path[i+1])) {
|
||||
|
@ -636,8 +637,24 @@ class Graph {
|
|||
form.delete( 'type' );
|
||||
let label = form.get( 'label' );
|
||||
form.delete( 'label' );
|
||||
|
||||
// checkboxes to true/false
|
||||
let defs = g.getConditionTypes()[type];
|
||||
console.log(defs);
|
||||
for(let field in defs) {
|
||||
console.log(field);
|
||||
if(defs[field]['type'] == 'checkbox') {
|
||||
console.info('configure checkbox', field);
|
||||
form.set(field, form.has(field));
|
||||
}
|
||||
}
|
||||
|
||||
let vars = {};
|
||||
for ( var pair of form.entries() ) {
|
||||
// FormData only has strings & blobs, we want booleans:
|
||||
if(pair[1] === 'true') pair[1] = true;
|
||||
if(pair[1] === 'false') pair[1] = false;
|
||||
|
||||
vars = g._formPathToVars(pair[0], pair[1], vars);
|
||||
}
|
||||
// TODO: checkboxes
|
||||
|
@ -841,18 +858,20 @@ class Graph {
|
|||
getEditEventListener() {
|
||||
let graph = this;
|
||||
let el = function( e ) {
|
||||
console.info("Changed", e);
|
||||
let parts = e.srcElement.name.split( '-' );
|
||||
let field = parts.pop();
|
||||
let id = parts.join('-');
|
||||
console.log( this, graph );
|
||||
let node = graph.getNodeById( id );
|
||||
let path = field.split( '.' ); // use vars.test to set ['vars']['test'] = value
|
||||
console.log(id, node);
|
||||
var res = node;
|
||||
let value = e.srcElement.value
|
||||
if(e.srcElement.type == 'checkbox') {
|
||||
value = e.srcElement.checked;
|
||||
}
|
||||
for ( var i = 0; i < path.length; i++ ) {
|
||||
if ( i == ( path.length - 1 ) ) {
|
||||
console.log( 'last', path[i] );
|
||||
res[path[i]] = e.srcElement.value;
|
||||
res[path[i]] = value;
|
||||
} else {
|
||||
res = res[path[i]];
|
||||
}
|
||||
|
@ -885,6 +904,7 @@ class Graph {
|
|||
}
|
||||
d.push( n );
|
||||
}
|
||||
console.info("Jsonified graph:",d);
|
||||
return JSON.stringify( d );
|
||||
}
|
||||
|
||||
|
@ -923,7 +943,7 @@ class Graph {
|
|||
|
||||
let blob = new Blob( [this.getJsonString()], { type: "application/json" } );
|
||||
formData.append( "json", blob );
|
||||
console.log( formData );
|
||||
console.info("Save json", formData );
|
||||
var request = new XMLHttpRequest();
|
||||
request.open( "POST", "http://localhost:8888/upload" );
|
||||
|
||||
|
@ -1010,7 +1030,6 @@ class Graph {
|
|||
this.clickMsg( d );
|
||||
}.bind( this ) )
|
||||
;
|
||||
console.log( 'a' );
|
||||
let circle = newNodeG.append( "circle" )
|
||||
.attr( 'r', this.nodeSize )
|
||||
// .text(d => d.id)
|
||||
|
@ -1058,7 +1077,6 @@ class Graph {
|
|||
link.attr( 'class', l => { return `link ` + ( l['conditions'].length == 0 ? "link--noconditions" : "link--withconditions" ); } );
|
||||
link.attr('id', (l) => l['@id']);
|
||||
|
||||
// console.log('c');
|
||||
let formatText = ( t ) => {
|
||||
if ( t.length > this.maxChars ) {
|
||||
return t.substr( 0, this.maxChars - 3 ) + '...';
|
||||
|
|
Loading…
Reference in a new issue