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