New contain conditions now work incl. timer
This commit is contained in:
parent
74730c1d25
commit
7eb48bd016
7 changed files with 306 additions and 79 deletions
|
@ -3,6 +3,9 @@ import logging
|
||||||
|
|
||||||
logger = logging.getLogger("communication")
|
logger = logging.getLogger("communication")
|
||||||
|
|
||||||
|
# hyper verbose log level. Have it here, becase it needs to be _somewhere_
|
||||||
|
LOG_BS = 5
|
||||||
|
|
||||||
def getTopic(hugvey_id):
|
def getTopic(hugvey_id):
|
||||||
return "hv{}".format(hugvey_id)
|
return "hv{}".format(hugvey_id)
|
||||||
|
|
||||||
|
|
182
hugvey/story.py
182
hugvey/story.py
|
@ -3,10 +3,28 @@ import time
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from .communication import LOG_BS
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger("narrative")
|
logger = logging.getLogger("narrative")
|
||||||
|
|
||||||
|
class Utterance(object):
|
||||||
|
"""Part of a reply"""
|
||||||
|
def __init__(self, startTime):
|
||||||
|
self.startTime = startTime
|
||||||
|
self.endTime = None
|
||||||
|
self.text = ""
|
||||||
|
|
||||||
|
def setText(self, text):
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
def setFinished(self, endTime):
|
||||||
|
self.endTime = endTime
|
||||||
|
|
||||||
|
def isFinished(self):
|
||||||
|
return self.endTime is not None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Message(object):
|
class Message(object):
|
||||||
def __init__(self, id, text):
|
def __init__(self, id, text):
|
||||||
|
@ -14,39 +32,97 @@ class Message(object):
|
||||||
self.text = text
|
self.text = text
|
||||||
self.isStart = False
|
self.isStart = False
|
||||||
self.reply = None
|
self.reply = None
|
||||||
self.replyTime = None
|
# self.replyTime = None
|
||||||
self.audioFile= None
|
self.audioFile= None
|
||||||
|
self.interruptCount = 0
|
||||||
|
self.afterrunTime = 0. # the time after this message to allow for interrupts
|
||||||
|
self.finishTime = None # message can be finished without finished utterance (with instant replycontains)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def initFromJson(message, data, story):
|
def initFromJson(message, data, story):
|
||||||
msg = message(data['@id'], data['text'])
|
msg = message(data['@id'], data['text'])
|
||||||
msg.isStart = data['start'] if 'start' in data else False
|
msg.isStart = data['start'] if 'start' in data else False
|
||||||
|
msg.afterrunTime = data['afterrun'] if 'afterrun' in data else 0.
|
||||||
if 'audio' in data:
|
if 'audio' in data:
|
||||||
msg.audioFile = data['audio']['file']
|
msg.audioFile = data['audio']['file']
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
def setReply(self, text, replyTime):
|
def setReply(self, reply):
|
||||||
self.reply = text
|
self.reply = reply
|
||||||
self.replyTime = replyTime
|
|
||||||
|
|
||||||
def hasReply(self):
|
def hasReply(self):
|
||||||
return self.reply is not None
|
return self.reply is not None
|
||||||
|
|
||||||
def getReply(self):
|
def getReply(self):
|
||||||
if self.reply is None:
|
if not self.hasReply():
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Getting reply while there is none! {0}".format(self.id))
|
"Getting reply while there is none! {0}".format(self.id))
|
||||||
|
|
||||||
return self.reply
|
return self.reply
|
||||||
|
|
||||||
|
def isFinished(self):
|
||||||
|
return self.finishTime is not None
|
||||||
|
|
||||||
|
def setFinished(self, currentTime):
|
||||||
|
self.finishTime = currentTime
|
||||||
|
|
||||||
|
def getFinishedTime(self):
|
||||||
|
return self.finishTime
|
||||||
|
|
||||||
def getLogSummary(self):
|
def getLogSummary(self):
|
||||||
return {
|
return {
|
||||||
'id': self.id,
|
'id': self.id,
|
||||||
'time': self.replyTime,
|
'time': None if self.reply is None else [u.startTime for u in self.reply.utterances],
|
||||||
'replyText': self.reply
|
'replyText': None if self.reply is None else [u.text for u in self.reply.utterances]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Reply(object):
|
||||||
|
def __init__(self, message: Message):
|
||||||
|
self.forMessage = None
|
||||||
|
self.utterances = []
|
||||||
|
self.setForMessage(message)
|
||||||
|
|
||||||
|
def setForMessage(self, message: Message):
|
||||||
|
self.forMessage = message
|
||||||
|
message.setReply(self)
|
||||||
|
|
||||||
|
def getLastUtterance(self) -> Utterance:
|
||||||
|
if not self.hasUtterances():
|
||||||
|
return None
|
||||||
|
return self.utterances[-1]
|
||||||
|
|
||||||
|
def getFirstUtterance(self) -> Utterance:
|
||||||
|
if not self.hasUtterances():
|
||||||
|
return None
|
||||||
|
return self.utterances[0]
|
||||||
|
|
||||||
|
def hasUtterances(self) -> bool:
|
||||||
|
return len(self.utterances) > 0
|
||||||
|
|
||||||
|
def addUtterance(self, utterance: Utterance):
|
||||||
|
self.utterances.append(utterance)
|
||||||
|
|
||||||
|
def getText(self) -> str:
|
||||||
|
return ". ".join([u.text for u in self.utterances])
|
||||||
|
|
||||||
|
def getActiveUtterance(self, currentTime) -> Utterance:
|
||||||
|
"""
|
||||||
|
If no utterance is active, create a new one. Otherwise return non-finished utterance for update
|
||||||
|
"""
|
||||||
|
if len(self.utterances) < 1 or self.getLastUtterance().isFinished():
|
||||||
|
u = Utterance(currentTime)
|
||||||
|
self.addUtterance(u)
|
||||||
|
else:
|
||||||
|
u = self.getLastUtterance()
|
||||||
|
return u
|
||||||
|
|
||||||
|
def isSpeaking(self):
|
||||||
|
u = self.getLastUtterance()
|
||||||
|
if u is not None and not u.isFinished():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
class Condition(object):
|
class Condition(object):
|
||||||
"""
|
"""
|
||||||
A condition, basic conditions are built in, custom condition can be given by
|
A condition, basic conditions are built in, custom condition can be given by
|
||||||
|
@ -86,28 +162,57 @@ class Condition(object):
|
||||||
|
|
||||||
return now - story.lastMsgFinishTime >= float(self.vars['seconds'])
|
return now - story.lastMsgFinishTime >= float(self.vars['seconds'])
|
||||||
|
|
||||||
def _hasMetReplyContains(self, story):
|
def _hasMetReplyContains(self, story) -> bool:
|
||||||
if not story.currentMessage.hasReply():
|
"""
|
||||||
|
Check the reply for specific characteristics:
|
||||||
|
- regex: regular expression. If empy, just way for isFinished()
|
||||||
|
- delays: an array of [{'minReplyDuration', 'waitTime'},...]
|
||||||
|
- minReplyDuration: the nr of seconds the reply should take. Preferably have one with 0
|
||||||
|
- waitTime: the time to wait after isFinished() before continuing
|
||||||
|
"""
|
||||||
|
r = story.currentReply # make sure we keep working with the same object
|
||||||
|
if not r or not r.hasUtterances():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if 'regex' in self.vars:
|
if 'regex' in self.vars and len(self.vars['regex']):
|
||||||
if 'regexCompiled' not in self.vars:
|
if 'regexCompiled' not in self.vars:
|
||||||
# Compile once, as we probably run it more than once
|
# Compile once, as we probably run it more than once
|
||||||
self.vars['regexCompiled'] = re.compile(self.vars['regex'])
|
self.vars['regexCompiled'] = re.compile(self.vars['regex'])
|
||||||
result = re.match(self.vars['regexCompiled'], story.currentMessage.getReply())
|
|
||||||
|
t = story.currentReply.getText().lower()
|
||||||
|
logger.log(LOG_BS, 'attempt regex: {} on {}'.format(self.vars['regex'], t))
|
||||||
|
result = self.vars['regexCompiled'].search(t)
|
||||||
if result is None:
|
if result is None:
|
||||||
|
#if there is something to match, but not found, it's never ok
|
||||||
return False
|
return False
|
||||||
|
logger.debug('Got match on {}'.format(self.vars['regex']))
|
||||||
results = result.groupdict()
|
results = result.groupdict()
|
||||||
for captureGroup in results:
|
for captureGroup in results:
|
||||||
story.variables[captureGroup] = results[captureGroup]
|
story.variables[captureGroup] = results[captureGroup]
|
||||||
logger.critical("Regex not implemented yet")
|
|
||||||
# return True
|
# TODO: implement 'instant match' -> don't wait for isFinished()
|
||||||
|
|
||||||
|
if r.isSpeaking():
|
||||||
|
logger.log(LOG_BS, "is speaking")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if 'contains' in self.vars:
|
# print(self.vars)
|
||||||
if self.vars['contains'] == '*':
|
# either there's a match, or nothing to match at all
|
||||||
return True
|
if 'delays' in self.vars:
|
||||||
return self.vars['contains'] in story.currentMessage.getReply()
|
replyDuration = story.timer.getElapsed() - r.getFirstUtterance().endTime
|
||||||
|
timeSinceReply = story.timer.getElapsed() - r.getLastUtterance().endTime
|
||||||
|
delays = sorted(self.vars['delays'], key=lambda k: float(k['minReplyDuration']), reverse=True)
|
||||||
|
for delay in delays:
|
||||||
|
if replyDuration > float(delay['minReplyDuration']):
|
||||||
|
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
|
||||||
|
break # don't check other delays
|
||||||
|
# wait for delay to match
|
||||||
|
return False
|
||||||
|
|
||||||
|
# There is a match and no delay say, person finished speaking. Go ahead sir!
|
||||||
|
return True
|
||||||
|
|
||||||
def getLogSummary(self):
|
def getLogSummary(self):
|
||||||
return {
|
return {
|
||||||
|
@ -245,6 +350,7 @@ class Story(object):
|
||||||
self.commands = [] # queue of commands to send
|
self.commands = [] # queue of commands to send
|
||||||
self.log = [] # all nodes/elements that are triggered
|
self.log = [] # all nodes/elements that are triggered
|
||||||
self.currentMessage = None
|
self.currentMessage = None
|
||||||
|
self.currentReply = None
|
||||||
self.timer = Stopwatch()
|
self.timer = Stopwatch()
|
||||||
self.isRunning = False
|
self.isRunning = False
|
||||||
|
|
||||||
|
@ -312,6 +418,7 @@ class Story(object):
|
||||||
self.events = [] # queue of received events
|
self.events = [] # queue of received events
|
||||||
self.commands = [] # queue of commands to send
|
self.commands = [] # queue of commands to send
|
||||||
self.log = [] # all nodes/elements that are triggered
|
self.log = [] # all nodes/elements that are triggered
|
||||||
|
self.currentReply = None
|
||||||
|
|
||||||
def add(self, obj):
|
def add(self, obj):
|
||||||
if obj.id in self.elements:
|
if obj.id in self.elements:
|
||||||
|
@ -369,26 +476,26 @@ class Story(object):
|
||||||
|
|
||||||
if e['event'] == 'speech':
|
if e['event'] == 'speech':
|
||||||
# message is still playing:
|
# message is still playing:
|
||||||
if self.currentMessage and not self.lastMsgFinishTime:
|
if self.currentMessage and not self.lastMsgFinishTime and self.previousReply and self.previousReply.forMessage.interruptCount < 4:
|
||||||
#interrupt:
|
timeDiff = self.timer.getElapsed() - self.previousReply.forMessage.getFinishedTime()
|
||||||
# FINISH THIS!!!
|
if self.previousReply.forMessage.afterrunTime > timeDiff:
|
||||||
# self.hugvey.sendCommand({
|
#interrupt only in given interval:
|
||||||
# 'action': 'stop',
|
logger.warn("Interrupt message, replay {}".format(self.previousReply.forMessage.id))
|
||||||
# 'id': self.currentMessage.id,
|
self.currentReply = self.previousReply
|
||||||
# })
|
self.previousReply.forMessage.interruptCount += 1
|
||||||
# ....
|
self.currentMessage = self.setCurrentMessage(self.previousReply.forMessage)
|
||||||
pass
|
|
||||||
|
|
||||||
# log if somebody starts speaking
|
# log if somebody starts speaking
|
||||||
# TODO: use pausing timer
|
|
||||||
# TODO: implement interrupt
|
# TODO: implement interrupt
|
||||||
if self.lastSpeechStartTime is None or self.lastSpeechStartTime < self.lastMsgTime:
|
if self.currentReply is None:
|
||||||
self.lastSpeechStartTime = e['time']
|
self.currentReply= Reply(self.currentMessage)
|
||||||
|
|
||||||
|
utterance = self.currentReply.getActiveUtterance(self.timer.getElapsed())
|
||||||
|
utterance.setText(e['transcript'])
|
||||||
|
|
||||||
if e['is_final']:
|
if e['is_final']:
|
||||||
# final result
|
utterance.setFinished(self.timer.getElapsed())
|
||||||
self.lastSpeechEndTime = e['time']
|
|
||||||
self.currentMessage.setReply(e['transcript'], self.timer.getElapsed())
|
|
||||||
|
|
||||||
def _processDirections(self, directions):
|
def _processDirections(self, directions):
|
||||||
for direction in directions:
|
for direction in directions:
|
||||||
|
@ -399,6 +506,7 @@ class Story(object):
|
||||||
direction.setMetCondition(condition)
|
direction.setMetCondition(condition)
|
||||||
self.addToLog(condition)
|
self.addToLog(condition)
|
||||||
self.addToLog(direction)
|
self.addToLog(direction)
|
||||||
|
self.currentMessage.setFinished(self.timer.getElapsed())
|
||||||
self.setCurrentMessage(direction.msgTo)
|
self.setCurrentMessage(direction.msgTo)
|
||||||
return direction
|
return direction
|
||||||
|
|
||||||
|
@ -439,9 +547,19 @@ class Story(object):
|
||||||
logger.info("Stop renderer")
|
logger.info("Stop renderer")
|
||||||
|
|
||||||
def setCurrentMessage(self, message):
|
def setCurrentMessage(self, message):
|
||||||
|
if self.currentMessage and not self.lastMsgFinishTime:
|
||||||
|
logger.info("Interrupt playback {}".format(self.currentMessage.id))
|
||||||
|
# message is playing
|
||||||
|
self.hugvey.sendCommand({
|
||||||
|
'action': 'stop',
|
||||||
|
'id': self.currentMessage.id,
|
||||||
|
})
|
||||||
|
|
||||||
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
|
||||||
|
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))
|
||||||
|
|
|
@ -19,18 +19,20 @@ if __name__ == '__main__':
|
||||||
argParser.add_argument(
|
argParser.add_argument(
|
||||||
'--verbose',
|
'--verbose',
|
||||||
'-v',
|
'-v',
|
||||||
action="store_true",
|
action='count', default=0
|
||||||
)
|
)
|
||||||
|
|
||||||
args = argParser.parse_args()
|
args = argParser.parse_args()
|
||||||
# print(coloredlogs.DEFAULT_LOG_FORMAT)
|
# print(coloredlogs.DEFAULT_LOG_FORMAT)
|
||||||
# exit()
|
# exit()
|
||||||
|
loglevel = logging.NOTSET if args.verbose > 1 else logging.DEBUG if args.verbose > 0 else logging.INFO
|
||||||
|
|
||||||
coloredlogs.install(
|
coloredlogs.install(
|
||||||
level=logging.DEBUG if args.verbose else logging.INFO,
|
level=loglevel,
|
||||||
# default: "%(asctime)s %(hostname)s %(name)s[%(process)d] %(levelname)s %(message)s"
|
# default: "%(asctime)s %(hostname)s %(name)s[%(process)d] %(levelname)s %(message)s"
|
||||||
fmt="%(asctime)s %(hostname)s %(name)s[%(process)d,%(threadName)s] %(levelname)s %(message)s"
|
fmt="%(asctime)s %(hostname)s %(name)s[%(process)d,%(threadName)s] %(levelname)s %(message)s"
|
||||||
)
|
)
|
||||||
|
|
||||||
command = CentralCommand(debug_mode=args.verbose)
|
command = CentralCommand(debug_mode=args.verbose > 0)
|
||||||
command.loadConfig(args.config)
|
command.loadConfig(args.config)
|
||||||
command.start()
|
command.start()
|
||||||
|
|
2
local
2
local
|
@ -1 +1 @@
|
||||||
Subproject commit e799809a59522d0f68f99f668a9ddf0f5f629912
|
Subproject commit 16738c586f1938814b12ad50be2af06ffa2bf37e
|
|
@ -16,6 +16,10 @@ body {
|
||||||
.btn:hover, input[type="submit"]:hover {
|
.btn:hover, input[type="submit"]:hover {
|
||||||
background: #666; }
|
background: #666; }
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
width: 80px;
|
||||||
|
text-align: right; }
|
||||||
|
|
||||||
@keyframes dash-animation {
|
@keyframes dash-animation {
|
||||||
to {
|
to {
|
||||||
stroke-dashoffset: -1000; } }
|
stroke-dashoffset: -1000; } }
|
||||||
|
@ -147,8 +151,8 @@ img.icon {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 -10px;
|
margin: 0 -10px;
|
||||||
padding: 5px 10px; }
|
padding: 5px 10px; }
|
||||||
#story label input, #story label select, #story label .label-value {
|
#story label input, #story label select, #story label .label-value, #story label .label-unit {
|
||||||
float: right; }
|
float: right; }
|
||||||
#story label:nth-child(odd) {
|
#story label:nth-child(odd) {
|
||||||
background-color: rgba(255, 255, 255, 0.3); }
|
background-color: rgba(255, 255, 255, 0.3); }
|
||||||
#story #msg {
|
#story #msg {
|
||||||
|
|
|
@ -282,7 +282,7 @@ class Graph {
|
||||||
|
|
||||||
let startAttributes = {
|
let startAttributes = {
|
||||||
'name': msg['@id'] + '-start',
|
'name': msg['@id'] + '-start',
|
||||||
'disabled': true,
|
'readonly': 'readonly',
|
||||||
'type': 'checkbox',
|
'type': 'checkbox',
|
||||||
'on': {
|
'on': {
|
||||||
'change': this.getEditEventListener()
|
'change': this.getEditEventListener()
|
||||||
|
@ -354,6 +354,19 @@ class Graph {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} )
|
} )
|
||||||
|
),
|
||||||
|
crel( 'label',
|
||||||
|
crel( 'span', {
|
||||||
|
"title": "The time after the reply in which one can still interrupt to continue speaking"
|
||||||
|
}, 'Afterrun time' ),
|
||||||
|
crel( 'input', {
|
||||||
|
'name': msg['@id'] + '-afterrunTime',
|
||||||
|
'value': msg['afterrunTime'],
|
||||||
|
'type': 'number',
|
||||||
|
'on': {
|
||||||
|
'change': this.getEditEventListener()
|
||||||
|
}
|
||||||
|
} )
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
msgEl.appendChild( msgInfoEl );
|
msgEl.appendChild( msgInfoEl );
|
||||||
|
@ -448,6 +461,7 @@ 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 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,11 +478,13 @@ class Graph {
|
||||||
'on': {
|
'on': {
|
||||||
'click': ( e ) => {
|
'click': ( e ) => {
|
||||||
if(confirm("Do you want to remove this condition?")) {
|
if(confirm("Do you want to remove this condition?")) {
|
||||||
|
console.log('remove condition for direction', condition, direction);
|
||||||
panopticon.graph.rmCondition( condition, direction );
|
panopticon.graph.rmCondition( condition, direction );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 'delete')
|
}, 'delete'),
|
||||||
|
...this.getConditionInputsForType(condition['type'], condition['@id'], condition['vars'])
|
||||||
)
|
)
|
||||||
let labelLabel = document.createElement( 'label' );
|
let labelLabel = document.createElement( 'label' );
|
||||||
labelLabel.innerHTML = "Description";
|
labelLabel.innerHTML = "Description";
|
||||||
|
@ -482,50 +498,121 @@ class Graph {
|
||||||
labelLabel.appendChild( labelInput );
|
labelLabel.appendChild( labelInput );
|
||||||
conditionEl.appendChild( labelLabel );
|
conditionEl.appendChild( labelLabel );
|
||||||
|
|
||||||
for ( let v in condition['vars'] ) {
|
|
||||||
let varLabel = document.createElement( 'label' );
|
// for ( let v in condition['vars'] ) {
|
||||||
varLabel.innerHTML = v;
|
// let varLabel = document.createElement( 'label' );
|
||||||
let varInput = document.createElement( 'input' );
|
// varLabel.innerHTML = v;
|
||||||
if ( v == 'seconds' ) {
|
// let varInput = document.createElement( 'input' );
|
||||||
varInput.type = 'number';
|
// if ( v == 'seconds' ) {
|
||||||
}
|
// varInput.type = 'number';
|
||||||
varInput.name = `${condition['@id']}-vars.${v}`;
|
// }
|
||||||
varInput.value = condition['vars'][v];
|
// varInput.name = `${condition['@id']}-vars.${v}`;
|
||||||
varInput.addEventListener( 'change', this.getEditEventListener() );
|
// varInput.value = condition['vars'][v];
|
||||||
varLabel.appendChild( varInput );
|
// varInput.addEventListener( 'change', this.getEditEventListener() );
|
||||||
conditionEl.appendChild( varLabel );
|
// varLabel.appendChild( varInput );
|
||||||
}
|
// conditionEl.appendChild( varLabel );
|
||||||
|
// }
|
||||||
return conditionEl;
|
return conditionEl;
|
||||||
}
|
}
|
||||||
|
|
||||||
getConditionTypes() {
|
getConditionTypes() {
|
||||||
if ( typeof this.conditionTypes === 'undefined' ) {
|
return {
|
||||||
// type: vars: attribtes for crel()
|
|
||||||
this.conditionTypes = {
|
|
||||||
'timeout': {
|
'timeout': {
|
||||||
'seconds': { 'type': 'number', 'value': 10, 'min': 0, 'step': 0.1 }
|
'seconds': { 'type': 'number', 'value': 10, 'min': 0, 'step': 0.1, 'unit': "s" }
|
||||||
},
|
},
|
||||||
'replyContains': {
|
'replyContains': {
|
||||||
'regex': { 'value': '.+' }
|
'delays.0.minReplyDuration': { 'type': 'number', 'value': 0, 'min': 0, 'step': 0.1, 'label': 'Delay 1 - reply duration', 'unit': "s", 'readonly': 'readonly' },
|
||||||
|
'delays.0.waitTime': { 'type': 'number', 'value': 3, 'min': 0, 'step': 0.1 , 'label': 'Delay 1 - wait time', 'unit': "s" },
|
||||||
|
'delays.1.minReplyDuration': { 'type': 'number', 'value': 5, 'min': 0, 'step': 0.1, 'label': 'Delay 2 - reply duration', 'unit': "s" },
|
||||||
|
'delays.1.waitTime': { 'type': 'number', 'value': 1, 'min': 0, 'step': 0.1, 'label': 'Delay 2 - time', 'unit': "s" },
|
||||||
|
'delays.2.minReplyDuration': { 'type': 'number', 'value': 10, 'min': 0, 'step': 0.1, 'label': 'Delay 3 - reply duration', 'unit': "s" },
|
||||||
|
'delays.2.waitTime': { 'type': 'number', 'value': 0, 'min': 0, 'step': 0.1, 'label': 'Delay 3 - time', 'unit': "s" },
|
||||||
|
'regex': { 'value': '','placeholder': "match any input" },
|
||||||
|
'instantMatch': { 'value': '', 'title': "When matched, don't wait for reply to finish. Instantly take this direction.", 'type':'checkbox' },
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
|
||||||
return this.conditionTypes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fillConditionFormForType( conditionForm, type ) {
|
getConditionInputsForType( type, conditionId, values ) {
|
||||||
conditionForm.innerHTML = "";
|
let inputs = [];
|
||||||
let vars = this.getConditionTypes()[type];
|
let vars = this.getConditionTypes()[type];
|
||||||
for ( let v in vars ) {
|
for ( let v in vars ) {
|
||||||
let attr = vars[v];
|
let attr = vars[v];
|
||||||
attr['name'] = v;
|
attr['name'] = typeof conditionId == 'undefined' ? v : `${conditionId}-vars.${v}`;
|
||||||
conditionForm.appendChild(
|
if(typeof values != 'undefined') {
|
||||||
|
let value = this._getValueForPath(v, values);
|
||||||
|
attr['value'] = typeof value == 'undefined' ? "": value;
|
||||||
|
attr['on'] = {
|
||||||
|
'change': this.getEditEventListener()
|
||||||
|
} ;
|
||||||
|
} else {
|
||||||
|
console.log(attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs.push(
|
||||||
crel( 'label',
|
crel( 'label',
|
||||||
crel( 'span', v ),
|
crel( 'span', {
|
||||||
|
'title': attr.hasOwnProperty('title') ? attr['title'] : ""
|
||||||
|
}, attr.hasOwnProperty('label') ? attr['label'] : v ),
|
||||||
crel( 'input', attr )
|
crel( 'input', attr )
|
||||||
|
// crel('span', {'class': 'label-unit'}, attr.hasOwnProperty('unit') ? attr['unit'] : "" )
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return inputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
fillConditionFormForType( conditionForm, type, values ) {
|
||||||
|
conditionForm.innerHTML = "";
|
||||||
|
let inputs = this.getConditionInputsForType(type);
|
||||||
|
for(let i of inputs) {
|
||||||
|
conditionForm.appendChild(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_getValueForPath(path, vars) {
|
||||||
|
path = path.split( '.' ); // use vars.test to set ['vars']['test'] = value
|
||||||
|
let v = vars;
|
||||||
|
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(typeof v == 'undefined') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save an array path (string) with a value to an object. Used to turn
|
||||||
|
* strings into nested arrays
|
||||||
|
* @param string path
|
||||||
|
* @param {any} value
|
||||||
|
* @param array|object vars
|
||||||
|
*/
|
||||||
|
_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])) {
|
||||||
|
// is int, use array, instead of obj
|
||||||
|
path[i+1] = parseInt(path[i+1]);
|
||||||
|
}
|
||||||
|
if(typeof res[path[i]] == 'undefined') {
|
||||||
|
res[path[i]] = typeof path[i+1] == 'number' ? [] : {}
|
||||||
|
}
|
||||||
|
res = res[path[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vars;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAddConditionFormEl( direction ) {
|
getAddConditionFormEl( direction ) {
|
||||||
|
@ -551,8 +638,10 @@ class Graph {
|
||||||
form.delete( 'label' );
|
form.delete( 'label' );
|
||||||
let vars = {};
|
let vars = {};
|
||||||
for ( var pair of form.entries() ) {
|
for ( var pair of form.entries() ) {
|
||||||
vars[pair[0]] = pair[1];
|
vars = g._formPathToVars(pair[0], pair[1], vars);
|
||||||
}
|
}
|
||||||
|
// TODO: checkboxes
|
||||||
|
console.log("Createded", vars);
|
||||||
g.addConditionForDirection( type, label, vars, direction );
|
g.addConditionForDirection( type, label, vars, direction );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -602,12 +691,14 @@ class Graph {
|
||||||
// TODO
|
// TODO
|
||||||
if ( typeof direction != 'undefined' ) {
|
if ( typeof direction != 'undefined' ) {
|
||||||
let pos = direction['conditions'].indexOf(id);
|
let pos = direction['conditions'].indexOf(id);
|
||||||
|
console.log('delete', id, 'on direction');
|
||||||
if(pos > -1) {
|
if(pos > -1) {
|
||||||
direction['conditions'].splice(pos, 1);
|
direction['conditions'].splice(pos, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(let dir of this.directions) {
|
for(let dir of this.directions) {
|
||||||
if(dir['conditions'].indexOf(id) > 0) {
|
// console.log('check if condition exists for dir', dir)
|
||||||
|
if(dir['conditions'].indexOf(id) > -1) {
|
||||||
console.log("Condition still in use");
|
console.log("Condition still in use");
|
||||||
this.updateFromData();
|
this.updateFromData();
|
||||||
this.build();
|
this.build();
|
||||||
|
@ -615,7 +706,8 @@ class Graph {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._rmNode( id );
|
console.log('No use, remove', condition)
|
||||||
|
this._rmNode( condition );
|
||||||
} else {
|
} else {
|
||||||
for(let dir of this.directions) {
|
for(let dir of this.directions) {
|
||||||
let pos = dir['conditions'].indexOf(id);
|
let pos = dir['conditions'].indexOf(id);
|
||||||
|
@ -623,7 +715,9 @@ class Graph {
|
||||||
dir['conditions'].splice(pos, 1);
|
dir['conditions'].splice(pos, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._rmNode( id );
|
|
||||||
|
console.log('remove condition?', id)
|
||||||
|
this._rmNode( condition );
|
||||||
}
|
}
|
||||||
this.updateMsg();
|
this.updateMsg();
|
||||||
}
|
}
|
||||||
|
@ -648,7 +742,8 @@ class Graph {
|
||||||
"@id": this.language_code.substring( 0, 2 ) + "-n" + Date.now().toString( 36 ),
|
"@id": this.language_code.substring( 0, 2 ) + "-n" + Date.now().toString( 36 ),
|
||||||
"@type": "Msg",
|
"@type": "Msg",
|
||||||
"text": "New",
|
"text": "New",
|
||||||
"start": false
|
"start": false,
|
||||||
|
"afterrunTime": 0.5,
|
||||||
}
|
}
|
||||||
this.data.push( msg );
|
this.data.push( msg );
|
||||||
this.updateFromData();
|
this.updateFromData();
|
||||||
|
|
|
@ -23,6 +23,11 @@ body{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
width: 80px;
|
||||||
|
text-align:right;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes dash-animation {
|
@keyframes dash-animation {
|
||||||
to {
|
to {
|
||||||
stroke-dashoffset: -1000;
|
stroke-dashoffset: -1000;
|
||||||
|
@ -244,9 +249,9 @@ img.icon{
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 -10px;
|
margin: 0 -10px;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
}
|
input,select, .label-value, .label-unit{
|
||||||
label input,label select, label .label-value{
|
float: right;
|
||||||
float: right;
|
}
|
||||||
}
|
}
|
||||||
label:nth-child(odd){
|
label:nth-child(odd){
|
||||||
background-color: rgba(255,255,255,0.3);
|
background-color: rgba(255,255,255,0.3);
|
||||||
|
|
Loading…
Reference in a new issue