diff --git a/.coverage b/.coverage
new file mode 100644
index 0000000..67e8ec9
--- /dev/null
+++ b/.coverage
@@ -0,0 +1 @@
+!coverage.py: This is a private format, don't read it directly!{"lines":{"/ravestate/modules/ravestate/__init__.py":[1,2,3,4,5,6,7,8,9,10,11],"/ravestate/modules/ravestate/activation.py":[2,3,5,6,7,9,10,11,12,13,14,15,17,18,21,25,28,30,31,32,33,34,35,36,37,38,39,40,41,43,58,61,64,77,88,127,148,159,173,180,192,203,209,216,234,315,321,325,330,333,44,45,46,47,48,49,50,51,52,53,54,55,56,84,85,86,59,62,142,157,68,69,214,143,331,145,146,75,111,112,113,114,115,123,124,190,207,243,247,250,251,252,304,313,255,258,259,260,261,262,264,265,266,267,268,272,276,278,281,282,283,287,288,291,294,295,298,316,317,318,319,334,335,336,337,339,340,346,347,348,349,350,351,352,301,178,367,368,369,370,371,372,375,328,376,377,378,382,169,170,171,323,270,271,273,305,311,116,117,118,119,120,121,125,144,355,360,362,227,228,229,230,231,232,292,306,307,308,309,356,357,244],"/ravestate/modules/ravestate/property.py":[3,4,5,6,8,9,12,22,26,27,28,29,30,31,32,33,34,35,61,64,78,81,84,100,109,112,115,124,141,159,177,183,189,195,201,207,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,57,58,59,90,91,92,93,94,95,96,62,181,199,187,193,79,212,213,214,216,218,219,220,205,215,217,110,132,135,139,113,136,137,104,105,107,149,152,155,156,157,119,122,167,170,171,172,150,151,168,169,106],"/ravestate/modules/ravestate/constraint.py":[1,2,3,4,5,8,9,12,15,16,18,22,25,27,31,35,39,44,45,49,53,57,61,66,70,72,73,74,75,76,77,84,89,91,93,112,120,131,135,138,141,144,147,150,154,166,170,171,188,196,200,204,207,217,222,227,233,240,242,250,254,263,264,265,266,268,277,281,289,300,303,306,309,312,319,334,338,339,345,351,354,358,369,373,384,385,387,394,398,406,421,424,428,434,438,439,443,447,451,455,459,94,95,96,97,98,99,100,101,102,108,109,110,243,244,245,246,247,248,269,270,273,136,251,274,275,388,389,392,304,19,218,219,220,223,224,225,121,122,145,113,115,116,422,425,426,313,314,315,316,317,310,132,307,429,430,431,320,322,323,324,327,328,155,156,157,159,161,162,163,330,331,332,432,452,453,355,356,201,205,167,202,164,28,29,32,33,36,37,40,41,46,47,50,51,148,114,123,125,126,127,395,396,128,278,279,129,435,335,326,172,173,184,185,186,190,191,192,193,194,142,117,118,124,290,291,282,284,285,286,287,292,293,294,295,296,298,283,399,401,402,400,403,404,407,408,409,411,412,413,414,416,417,418,419,301,440,441,340,341,448,449,352,151,152,133,444,445,346,347,342,343,456,457,362,363,364,365,208,209,210,211,212,213,214,215,366,367,174,175,176,179,348,349],"/ravestate/modules/ravestate/spike.py":[3,4,5,6,8,9,12,17,20,23,26,29,32,37,42,45,48,51,55,56,57,58,94,97,100,103,112,120,132,144,171,179,185,191,201,207,74,75,76,78,79,80,81,82,83,84,85,86,87,88,89,91,92,118,101,189,205,98,95,77,127,128,129,130,156,157,158,160,162,164,165,166,167,168,211,183,110,90,177,161,139,142],"/ravestate/modules/ravestate/causal.py":[3,4,5,6,8,9,12,20,23,25,43,66,67,68,69,70,71,72,83,84,85,86,87,100,101,102,103,104,109,112,114,131,136,148,159,162,165,172,216,247,286,336,348,360,382,398,422,427,444,453,118,119,120,121,122,123,126,128,129,139,140,141,145,146,423,424,149,150,151,153,163,234,237,238,240,241,125,242,428,429,430,431,433,436,437,439,244,166,167,170,245,132,133,134,263,275,264,276,282,284,160,180,183,186,187,367,369,370,373,377,378,380,188,368,189,196,199,205,206,209,210,211,212,213,214,389,394,396,390,300,301,302,303,308,309,310,311,312,313,314,324,333,334,346,406,407,408,409,425,277,278,265,266,267,268,273,279,280,315,316,317,318,325,326,327,328,331,321,322,413,419,420,374,375,376,270,379,438,440,445,446,447,442,460,461,462,448,449,450,451,463,464,465,466,467,468,469,470,473,474,475,476,477,481,485,441,169,471,472,478,480,395,391,392,393],"/ravestate/modules/reggol/__init__.py":[1,2,3,5,6,7,9,10,13,24,38,64,65,66,67,68,71,80,91,92,93,94,21,95,32,34,35,52,53,54,55,56,57,58,59,60,77,88],"/ravestate/modules/reggol/colored_formatter.py":[1,2,5,22,23,24,25,27,29,30,31,32,33,36,38,40,46,41,42,43,44,47,48,49,50,53,13,14,15,16,17,18,19,54],"/ravestate/modules/reggol/logger.py":[1,2,3,5,7,8,10,11,12,13,14,17,19,26,27,28,36,42,46,20,21,22,37,38,39,40,30,31,32,33,34],"/ravestate/modules/ravestate/threadlocal.py":[1,5],"/ravestate/modules/ravestate/state.py":[3,4,6,7,8,9,11,12,15,18,21,24,27,30,38,39,43,47,48,51,57,58,62,67,68,71,75,77,78,79,80,81,82,83,84,85,87,88,89,90,91,95,103,104,105,106,107,162,166,169,172,175,189,197,209,224,225,226,227,228,229,230,231,246,258,248,249,250,251,252,253,254,255,256,257,109,110,111,112,115,120,122,124,125,126,127,128,129,132,139,142,143,144,145,146,147,148,149,150,151,152,153,154,155,158,159,160,123,140,121,173,194,195,170,182,183,163,164,59,202,203,204,220,167,133,134,135,40,207,184,185,186],"/ravestate/modules/ravestate/consumable.py":[4,6,7,10,14,16,17],"/ravestate/modules/ravestate/wrappers.py":[3,4,5,6,7,8,9,11,12,15,22,24,29,45,49,62,101,123,144,154,158,160,182,190,200,217,220,223,228,259,293,161,162,163,164,165,168,31,32,33,34,35,36,37,39,40,41,42,70,73,99,46,47,75,83,92,93,94,95,96,97,98,77,78,79,80,81,82,170,171,172,174,175,176,177,178,179,180,85,86,87,88,89,90,209,215,218,221,240,243,245,246,110,113,114,115,116,117,118,119,120,247,248,249,250,251,252,253,254,297,299,300,148,151,191,193,194,55,58,59,183,185,186,268,270,271,274,275,276,131,134,135,136,137,138,139,140,141,277,279,281,282,285,283,284,43,60,56,57,71,72,121,142,184,224,225,226,192,298,244,195,196,210,211,212,213,214],"/ravestate/modules/ravestate/module.py":[3,4,5,7,8,9,10,12,13,16,27,29,30,32,69,77,80,100,111,47,49,50,51,52,53,54,55,56,57,59,60,61,62,63,67,70,71,74,75,81,82,85,86,87,90,93,94,95,88,89,78,48,91,92,108,122,123,58,72,83,84],"/ravestate/modules/ravestate/argparser.py":[3,4,5,6,8,9,12,26,27,28,30,31,32,48,51,52,58,59,72,73,79,80,81,84,86,87,88,89,95,97,98,99,100,108,109,110,54,56,112,61,63,64,65,66,69,70,67,75,77],"/ravestate/modules/ravestate/config.py":[4,5,6,8,9,11,13,14,17,31,33,45,59,75,94,124,137,162,40,41,42,54,56,57,169,170,85,88,89,92,144,145,146,150,151,152,153,154,155,156,159,160,43,70,73,171,175,177,178,105,108,109,112,113,118,119,120,121,122,55,114,115,116,117,71,72,106,107,110,111,130,131,132,133,134,135,172,173,86,87],"/ravestate/modules/ravestate/context.py":[2,3,4,5,6,7,8,10,12,13,14,15,16,17,18,19,20,22,23,26,27,28,30,31,34,41,48,50,51,52,53,54,55,56,57,58,60,61,62,63,64,65,66,67,68,71,79,89,90,94,97,104,105,106,108,110,111,112,113,114,121,125,126,127,128,129,136,138,139,140,141,143,185,217,236,247,253,262,281,347,369,389,413,433,450,467,484,505,516,535,626,630,633,639,653,668,675,686,692,697,707,733,755,784,789,154,155,156,157,158,159,160,161,162,163,164,167,271,274,276,277,278,279,654,656,658,659,660,661,378,382,384,386,387,634,636,637,662,663,635,664,666,168,446,447,627,171,175,179,180,288,293,294,299,300,307,316,317,322,323,324,325,524,525,526,527,530,531,326,329,693,695,532,332,335,336,698,699,700,701,702,703,708,710,722,723,735,737,740,745,746,747,748,736,724,731,749,750,751,753,728,729,711,712,713,714,715,716,717,718,719,720,704,705,338,340,669,670,671,672,673,344,345,459,461,462,463,514,204,206,207,208,209,210,211,212,213,214,215,205,226,227,228,229,230,240,243,244,785,786,245,257,258,259,260,241,242,251,396,400,401,402,404,405,640,641,642,643,644,646,647,648,649,650,651,407,408,409,410,411,355,358,360,363,676,678,679,682,683,684,365,367,356,357,318,533,741,361,680,681,397,398,289,290,687,688,301,302,306,308,309,313,303,304,310,311,295,296,379,380,428,429,430,75,76,172,176,177,181,182,628,665,550,554,555,559,560,561,580,581,582,584,585,586,587,593,594,595,599,690,600,498,499,502,503,601,631,605,606,607,608,616,617,618,622,624,756,757,758,759,760,761,763,764,765,766,767,768,769,770,771,772,773,774,775,776,777,565,566,567,568,576,583,610,611,612,762,477,478,481,482,569,570,571,572,573,574,431,657,655,778,779,780,781,782,794,795,796,797,798,799,800,801,802,804,803],"/ravestate/modules/ravestate/receptor.py":[2,3,4,5,6,7,10,36,56,38,41,42,43,44,45,46,47,48,50,54,52,39],"/ravestate/modules/ravestate/testfixtures.py":[1,3,4,6,8,9,10,11,12,13,15,16,18,19,20,21,24,33,42,51,60,69,74,75,81,88,93,94,98,99,104,109,26,29,30,71,76,77,78,83,84,85,95,100,101,106,35,38,39,44,47,48,53,56,57,62,65,66,90],"/ravestate/modules/ravestate_conio/__init__.py":[2,3,5,6,9,11,12,17,13,19],"/ravestate/modules/ravestate_rawio/__init__.py":[1,3,5,6,7,8,9,10,12,13,14,15,16,17,18,20,21,22,23,24,25,28],"/ravestate/modules/ravestate_fillers/__init__.py":[1,3,4,5,8,10,11,12],"/ravestate/modules/ravestate_idle/__init__.py":[1,3,4,6,7,10,11,14,16,17,19,20,21,22,23,30,31,32,33,27,34,28],"/ravestate/modules/ravestate_verbaliser/__init__.py":[1,2,3,5,6,8,10,11,12,13,14,15,16,18,24,25,26,27,29],"/ravestate/modules/ravestate_verbaliser/verbaliser.py":[1,2,3,4,6,7,8,15,16,17,18,21,35,68,80,92,104,116,128,140,153,165,178,190,203,215,28,29,30,44,45,46,48,49,50,53,62,54,55,58,89,125,113,77,150,137,101,51,52,162,175,187,200,212,225,63,64,65,56,57],"/ravestate/modules/ravestate_verbaliser/qa_phrases.py":[1,4,10,35,36,37,38,39,40,42,51,52,54,60,53,5,6,55,56,57,58,59,61,62,63,64,65,7],"/ravestate/modules/ravestate_phrases_basic_en/__init__.py":[2,3,5,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21],"/ravestate/modules/ravestate_nlp/__init__.py":[1,3,4,5,7,8,9,10,12,13,16,42,19,20,25,26,29,31,34,35,36,37,38,39,45,47,48,49,50,51,52,53,54,56,57,58,59,60,62,103,108,113,119,125,131,64,65,67,68,70,71,72,74,75,76,78,79,80,82,83,84,86,87,88,90,91,135,136,92,93,95,32,96,97,99,100,101,127,128,121,122,123,110,105,115,116,111,106],"/ravestate/modules/ravestate_nlp/question_word.py":[1,4,5,6,7,8,9,10,11,13,15,16,17,18,19,20,21,24,25,26,27,28,29,30],"/ravestate/modules/ravestate_nlp/triple.py":[1,2,3,4,7,9,10,11,12,14,19,22,25,28,31,34,37,40,43,46,49,52,64,93,101,110,121,124,15,20,16,23,17,26,53,54,55,56,57,58,59,60,61,62,32,103,105,107,125,122,111,113,114,116,117,118,38,108,29,112,119,71,73,85,87,74,75,83,88,89,76,78,79,80,81,82,91,77,104,94,97,98,99,50,95],"/ravestate/modules/ravestate_nlp/triple_match_result.py":[1,3,8,11,4,5,6,9],"/ravestate/modules/ravestate_nlp/extract_triples.py":[1,2,3,5,6,7,8,9,10,13,50,41,42,43,44,55,56,57,62,64,65,66,67,68,70,63,45,46,47,58,59,60,61,69],"/ravestate/modules/ravestate_nlp/yes_no.py":[1,3,4,5,6,7,10,11,12,13,14,15,16,19,20,22,66,74,79,84,89,94,98,23,24,26,27,28,29,30,32,42,55,61,64,102,90,92,43,46,53,54,75,76,56,59,60,62,63,33,36,39,40,41,34,35,37,38,77,80,81,82,47,68,69,70,72,50,51,57,58,85,86,87],"/ravestate/modules/ravestate_interloc/__init__.py":[1,2,3,5,6,8,9,11,12,15,18,19,20,22,35,24,25,26,27,28,29,30,31,32,33],"/ravestate/modules/ravestate_ontology/__init__.py":[1,2,3,5,6,8,9,11,13,14,16,17,18,20,21,22,24,25,26,29,31,62,70,44,46,47,48,49,50,51,59,71,72,75,63,64,67],"/ravestate/modules/ravestate_ontology/dummy_session.py":[1,4,7,9,12,15,18,21],"/ravestate/modules/ravestate_persqa/__init__.py":[1,2,3,4,5,6,7,9,10,11,12,14,15,16,18,19,21,23,24,25,26,27,28,29,30,31,32,39,46,48,49,50,51,52,53,54,56,57,58,59,60,61,62,64,65,66,67,68,69,70,72,73,74,75,76,77,79,81,83,89,98,171,172,173,174,175,182,183,184,185,192,199,201,202,203,204,228,229,230,231,232,179,180,100,102,103,104,105,106,107,108,109,110,148,149,150,151,152,168,169,111,112,114,115,118,119,120,121,122,123,194,195,211,212,214,215,216,218,220,221,224,225,226,237,238,239,240,241,242,244,245,247,249,250,251,90,91,92,93,96,255,256,257,267,268,269,270,197,117,84,85,86,222,223,258,259,260,261,262,263,264,265],"/ravestate/modules/ravestate_roboyqa/__init__.py":[1,2,3,4,5,6,8,9,10,12,13,14,16,17,19,21,24,25,26,27,29,33,151,249,62,63,65,66,67,68,71,152,153,155,156,157,158,159,161,162,163,165,166,167,169,170,171,172,173,175,176,177,178,179,180,182,183,184,185,187,188,189,190,191,193,194,195,197,198,199,200,201,203,204,205,206,207,211,212,214,215,216,219,220,221,222,223,224,225,226,228,229,231,232,233,234,235,236,237,238,239,240,241,242,243,245,246,72,73,74,80,82,83,86,103,110,111,113,115,118,119,135,139,140,141,143,144,69,87,89,91,93,96,97,98,120,124,125,126,127,254,255,256,257,258,261,94,95,88,136,137,104,105,106,107,112,114,116,99,100,92,90],"/ravestate/modules/ravestate_telegramio/__init__.py":[1,2,4,5,6,7,12,14,17,19,21,24,25,26,27,29,30,31,32,33,34],"/ravestate/modules/ravestate_telegramio/telegram_bot.py":[1,2,3,4,5,6,7,8,10,12,13,15,16,17,19,20,22,23,24,25,26,29,30,31,32,33,34,37,38,40,43,46,53,57,59,60,61,62,65,66,306,333,334,342,343,348,349,354,355,360,361,71,72,78,79,85,86,93,132,139,157,170,192,198,241,274,298,299,300,301,246,247,248,249,250,251,252,253,254,255,338,339,311,314,316,317,318,319,324,325,327,330,41,47,44],"/ravestate/modules/ravestate_emotion/__init__.py":[1,3,4,5,6,7,10,11,12,13,15,16,17,18,21,23,25,26,27,28,30,31,37,38,44,45,51,52],"/ravestate/modules/ravestate_visionio/__init__.py":[1,2,3,4,6,7,8,9,10,11,13,14,16,17,18,19,28,30,31,32,33,34,35,38,39,40,41,42,43,46,48,49,50,51,52,53,54,57,58,59,60,61,64,65,74,75,76,78,91,92,93,94,96,162,163,164,166,184,84,89,103,104,107,108,109,111,112,114,116,117,118,120,121,122,133,137,151,152,154,155,156,157,158,159,128,129,130],"/ravestate/modules/ravestate_visionio/faceoraclefilter.py":[1,2,3,4,6,7,9,10,11,21,22,25,26,29,31,63,137,150,170,188,41,46,51,56,61,72,73,76,79,95,109,112,131,132,133,135,157,158,159,160,161,162,163,164,165,166,167,117,141,142,144,148,118,126,128,129],"/ravestate/modules/ravestate_hibye/__init__.py":[1,3,4,5,6,8,10,11,13,15,16,17,26,27,18,19,20,21,22,24],"/ravestate/modules/ravestate_genqa/__init__.py":[1,2,4,5,6,7,9,10,12,13,15,16,18,21,23,32,36,62,25,26,27,28],"/ravestate/modules/ravestate/__main__.py":[],"/ravestate/modules/ravestate_wildtalk/server.py":[],"/ravestate/modules/ravestate_wildtalk/convai_gpt_backend.py":[],"/ravestate/modules/ravestate_wildtalk/__init__.py":[],"/ravestate/modules/ravestate_wildtalk/gpt2_backend.py":[],"/ravestate/modules/ravestate_wildtalk/parlai_backend.py":[]}}
\ No newline at end of file
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..92b5c7e
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,17 @@
+[run]
+omit =
+ # exclude roboyio, too volatile
+ */ravestate_roboyio/*
+ # exclude ui
+ */raveboard/*
+ */ravestate_ui/*
+ # exclude akinator
+ */ravestate_akinator/*
+ # exlude interfaces
+ */ravestate/i*
+ # exlude ros1 abstraction
+ */ravestate_ros1/*
+ # exlude ros2 abstraction
+ */ravestate_ros2/*
+ # exclude sendpics, nobody cares
+ */ravestate_sendpics/*
diff --git a/.gitignore b/.gitignore
index 46846de..621ba4b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,5 @@
*.key
*.default
-.coverage
.pytest_cache
.idea
__pycache__
@@ -17,3 +16,4 @@ ros2/install
ros2/log
venv/*
mkdocs.yml
+db/*
diff --git a/.travis.yml b/.travis.yml
index 49320c4..ad3b178 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,21 +2,19 @@ language: python
os: linux
python:
- "3.6"
-
+
# Enable 3.7 without globally enabling sudo and dist: xenial for other build jobs
matrix:
include:
- python: 3.7
dist: xenial
sudo: true
-
-# command to install dependencies
-install:
- - pip install -r requirements.txt -r requirements-dev.txt
-# command to run tests
-script:
- - ./run_tests.sh
+services: docker
+
+before_install:
+ - docker build -t ravestate .
+
+script: docker run -t -v $(pwd):/ravestate -w /ravestate ravestate ./run_tests.sh
-after_success:
- - codecov
+after_success: codecov
diff --git a/CODEOWNERS b/CODEOWNERS
index 624e4bf..97ded95 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -1,2 +1,3 @@
# These owners will be the default owners for everything in the repo.
-* @toseban @NeginsCode @josephbirkner @l-laura @emlozin
+* @toseban @NeginsCode @josephbirkner @l-laura @emlozin @nbasargin
+
diff --git a/Dockerfile b/Dockerfile
index 39b3666..4eefe3e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,56 @@
-FROM missxa/melodic-crystal-roboy
+FROM missxa/melodic-dashing-roboy
+# PLEASE INSERT ADDITIONAL LAYERS AT THE END OF THE FILE
+
+# ------------------------------------------------------
+# install neo4j
+RUN wget -O - https://debian.neo4j.org/neotechnology.gpg.key | apt-key add -
+RUN echo 'deb https://debian.neo4j.org/repo stable/' | tee /etc/apt/sources.list.d/neo4j.list
+RUN apt-get update && apt-get install -y neo4j
+RUN neo4j-admin set-initial-password test
+
+# ------------------------------------------------------
+# install redis
+RUN apt-get install -y redis
+
+# ------------------------------------------------------
# install ravestate dependencies
ADD requirements.txt /tmp/requirements.txt
ADD requirements-dev.txt /tmp/requirements-dev.txt
RUN pip3 install -r /tmp/requirements.txt
RUN pip3 install -r /tmp/requirements-dev.txt
+
+# ------------------------------------------------------
+# download wildtalk and spacy models
+RUN python3 -c "from roboy_parlai import wildtalk"
+RUN python3 -c "from pytorch_pretrained_bert import cached_path; \
+ cached_path('https://s3.amazonaws.com/models.huggingface.co/transfer-learning-chatbot/finetuned_chatbot_gpt.tar.gz')"
+RUN python3 -c "from pytorch_transformers import GPT2Tokenizer, GPT2LMHeadModel; \
+ GPT2Tokenizer.from_pretrained('gpt2-medium'); GPT2LMHeadModel.from_pretrained('gpt2-medium')"
+RUN python3 -c "from spacy.cli import download as spacy_download; spacy_download('en_core_web_sm')"
+
+# ------------------------------------------------------
+# install pyroboy with melodic
+# add github repo metadata to bust cache when repo is updated
+# => bad idea due to github API rate limit, fails Travis builds now and then.
+# ADD https://api.github.com/repos/roboy/pyroboy/git/refs/heads/melodic pyroboy_version.json
+RUN cd ~/melodic_ws/src && git clone https://github.com/Roboy/pyroboy.git && \
+ cd ~/melodic_ws/src/pyroboy && git checkout melodic && \
+ cd ~/melodic_ws/src/roboy_communication && git pull && \
+ cd ~/melodic_ws && . /opt/ros/melodic/setup.sh && catkin_make && . /opt/ros/melodic/setup.sh
+
+# ------------------------------------------------------
+# install face_oracle
+RUN pip3 install ecdsa
+RUN pip install -U face_recognition websocket_client pillow opencv-python numpy
+# add github repo metadata to bust cache when repo is updated
+# => bad idea due to github API rate limit, fails Travis builds now and then.
+# ADD https://api.github.com/repos/roboy/face_oracle/git/refs/heads/visionio_messages faceoracle_version.json
+RUN cd ~/melodic_ws/src && git clone https://github.com/Roboy/face_oracle.git && \
+ cd ~/melodic_ws/src/face_oracle && git checkout visionio_messages && git pull && cd ~/melodic_ws && \
+ . /opt/ros/melodic/setup.sh && catkin_make && . /opt/ros/melodic/setup.sh
+
+# install speech recognition requirements and download speech recognition
+# RUN apt install -y libasound-dev portaudio19-dev libportaudio2 libportaudiocpp0 python3-pyaudio
+# RUN pip3 install webrtcvad monotonic SpeechRecognition pyaudio
+# RUN cd /root/ros2_ws/src && git clone https://github.com/Roboy/ros2_speech_recognition.git
diff --git a/README.md b/README.md
index 27f5cf7..c236736 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,12 @@
\____/ \____/ Olà! -
```
-Ravestate is a reactive library for real-time natural language dialog systems. It combines elements from event-based and reactive programming into an API, where application states are defined as functions that are run when a certain boolean set of criteria (signals) in the current application context is satisfied. It is the first reactive API to allow for boolean combinations of events. You may find a short introductory video [here](http://www.youtube.com/watch?v=6GMmY-xvA_Y "Introduction to Ravestate").
+Ravestate is a reactive library for real-time natural language dialog systems.
+It combines elements from event-based and reactive programming into an API,
+where application states are defined as functions that are run when a certain
+boolean set of criteria (signals) in the current application context is satisfied.
+It is the first reactive API to allow for boolean combinations of events.
+You may find a short introductory video [here](http://www.youtube.com/watch?v=6GMmY-xvA_Y "Introduction to Ravestate").
### Reactive Hello World
@@ -31,7 +36,7 @@ import ravestate_conio
# Ravestate applications should always be wrapped in a Module.
# This allows easier scoping, and enables separation of concerns
# beyond states.
-with rs.Module(name="hi!"):
+with rs.Module(name="hi!", depends=(rawio.mod,)):
# Create an application state which reacts to the `:startup` signal,
# and writes a string to raw:out. Note: State functions are
@@ -41,14 +46,22 @@ with rs.Module(name="hi!"):
context[rawio.prop_out] = "Waddup waddup waddup!"
# Run context with console input/output and our 'hi!' module.
-rs.Context("conio", "hi!").run()
+rs.Context("hi!").run()
```
-### Visualization
+### Raveboard
-Ravestate has a [d3.js](https://d3js.org)-based visualization. When using `ravestate_ui.UIContext` instead of `Context`, or `python3 -m ravestate_ui` instead of `python3 -m ravestate`, a real-time visualization of all states/properties/signals in the state machine will be hosted on port 5001. Here is the view of `http://localhost:5001` after launching `python3 ravestate_ui -f generic.yml`:
+Ravestate has an [angular](https://angular.io)/[socket.io](https://socket.io)-based
+interactive (beta) UI called __Raveboard__. It shows the events (spikes) that are
+currently relevant, as well as potential state activations that are referencing these spikes.
-
+When using `raveboard.UIContext` instead of `Context`, or `python3 -m raveboard` instead of
+`python3 -m ravestate`, a real-time visualization of all spikes/activations, as well as a chat window,
+will be hosted on a configurable port. You can find dedicated docs [here](modules/raveboard/README.md).
+
+The following GIF shows raveboard together with [ravestate_visionio](modules/ravestate_visionio/README.md):
+
+![Raveboard](resources/docs/raveboard.gif)
## Installation
@@ -68,15 +81,87 @@ For reliability, we recommend using an environment virtualization tool,
like [virtualenv](https://virtualenv.pypa.io/en/latest/)
or [conda](https://conda.io/en/latest/).
-### For developers
+### Via Docker/Docker-compose
+
+#### Why Docker?
+
+Ravestate offers a docker image that bundles runtime dependencies that are required
+for advanced cognitive dialog systems/chatbots:
+
+* [📦 Neo4j](https://neo4j.com): The Neo4j Graph DBMS is used by [Scientio](https://github.com/roboy/scientio) for long-term memory.
+* [💡 Redis](https://redis.io): A Redis in-memory DB is used for fast short-term memory, e.g. to store/recall facial feature vectors.
+* [🤦 FaceOracle](https://github.com/roboy/face_oracle): A Roboy-developed server-client architecture used by `ravestate_visionio` for real-time face recognition.
+* [🤖 ROS Melodic](https://ros.org): Version 1 of the *Robot Operating System* for distributed real-time communication.
+ This version of ROS requires a broker process (`roscore`), which is started automatically inside the container.
+* [🤖 ROS2 Dashing](https://index.ros.org/doc/ros2): Version 2 of the *Robot Operating System* for distributed real-time communication.
+* [🤗 HuggingFace Transformer Models](https://github.com/huggingface/transformers): Language models (ConvAI GPT/OpenAI GPT2)
+ for neural-network-generated conversation.
+* [💌 Roboy ROS Messages](https://github.com/roboy/roboy_communication): Message defs. that are required to interact with Roboy hardware.
+
+Installing these dependencies by hand is time-consuming and error-prone, so using Docker
+to ship them makes everyone's lives easier!
+
+#### How to build?
+
+Clone ravestate:
+
+```bash
+git clone git@github.com:roboy/ravestate && cd ravestate
+```
+
+You can build the ravestate container using the provided `Dockerfile`:
+
+```bash
+docker build -t ravestate .
+```
+
+__Note: Building the container takes time and requires a good internet connection, since
+all of the dependencies are several Gigabytes in size.__
+
+#### How to run?
+
+Use one of the following docker-compose commands to run ravestate in Docker:
+
+Platform | Command
+---------|---------------------
+Linux | `docker-compose up -d rs-linux`
+macOS | `docker-compose up -d rs-macos`
+Windows | Not supported yet.
+
+The container is now running and a shell inside the container can be opened with:
+
+```bash
+docker exec -it rs bash
+```
+
+You can now start ravestate or raveboard as described in the section [Running Hello World](#running-hello-world).
+
+```bash
+python3 -m ravestate [...]
+```
+
+#### Which services are exposed from the container?
+
+Service | Port | Description
+---------|------|-------------------------------------
+Neo4j UI | 7474 | Neo4j UI for DB stored under `/db/neo4j`
+Neo4j Bolt Interface | 7687 | Communication with Neo4j DBMS
+Redis Database Dump | - | A dump of the Redis DB in the container can be found under `/db/redis`
+FaceOracle Client Interface | 8088 | Visualisation for the FaceOracle client.
+Raveboard | 42424 | Default port for raveboard, the ravestate debug UI.
+
+
+### For development
#### Initial configuration and setup
Clone the repository and install dependencies:
```bash
+cd ~
+
# Create a virtual python environment to not pollute the global setup
-python3 -m virtualenv python-ravestate
+python3 -m virtualenv -p python3 python-ravestate
# Source the virtual environment
. python-ravestate/bin/activate
@@ -94,18 +179,9 @@ pip install -r requirements-dev.txt
pip install -e .
```
-Now, launch a Neo4j docker instance to serve [Scientio](https://github.com/roboy/scientio), so the dialog system has a memory:
-```bash
-docker run \
- --publish=7474:7474 --publish=7687:7687 \
- --volume=$HOME/neo4j/data:/data \
- --volume=$HOME/neo4j/logs:/logs \
- neo4j:latest
-
-# Open the address localhost:7474 in a browser, and enter the
-# credentials `neo4j`/`neo4j`. You will then be prompted to enter
-# your own password. Remember this password.
-```
+Launch the ravestate docker container as described above. It will serve you Neo4j,
+which is a backend for [Scientio](https://github.com/roboy/scientio), Roboy's
+long-term memory system.
In the `config` folder, create a file called `keys.yml`. It should have the following content:
@@ -114,19 +190,16 @@ module: telegramio
config:
telegram-token: # This is where your own telegram bot token
# will go later
----
-module: ontology
-config:
- neo4j_address: bolt://localhost:7687 # Your neo4j server uri here
- neo4j_username: neo4j # Your neo4j user here
- neo4j_pw: test # Your neo4j pw here
```
You may now conduct your first conversation with ravestate:
```bash
-python3 -m ravestate -f config/generic.yml -f config/keys.yml
+python3 -m raveboard -f config/generic.yml -f config/keys.yml
```
+Open raveboard on `localhost:42424/ravestate/index.html?rs-sio-url=http%3A//localhost%3A42424`
+to conduct your first conversation with ravestate.
+
After the conversation, check the Neo4j interface under `localhost:7474`. It should now contain some nodes!
__Reminder: Whenever you use ravestate from the command line, source the virtual environment first!__
@@ -143,10 +216,10 @@ just run `telegram_test.yml` instead of `generic.yml`. This will load the `raves
3. Mark the `modules` folder as sources root via the right-click context menu.
4. Create a run config via the "Edit configurations menu":
• Create a new Python configuration.
- • Set `modules/ravestate/__main__.py` as the script to execute
+ • Set `raveboard` as the __module__ to execute
• Set the working directory to the git clone directory.
• Set parameters to `-f config/generic.yml -f config/keys.yml`.
-5. You should now be able to run the generic ravestate config from pycharm.
+5. You should now be able to run the generic ravestate config from PyCharm.
## Running Hello World
@@ -191,7 +264,7 @@ Then, run `ravestate` with this config file:
python3 -m ravestate -f hello_world.yml
```
-## Module overview
+## Modules
Ravestate offers a landscape of fine-grained modules
for different aspects of dialog application tasks, which
@@ -202,7 +275,6 @@ External (Red) and Skills (Green):
#### Core Modules
-
| Module name | Description |
|----------------------|-------------|
@@ -213,7 +285,9 @@ External (Red) and Skills (Green):
| ravestate_idle | Provides `bored` and `impatient` signals, as specified [here](https://github.com/Roboy/ravestate/issues/12).
| ravestate_verbaliser | Utilities for easy management of conversational text, documented [here](modules/ravestate_verbaliser/README.md).
| ravestate_nlp | Spacy-based NLP properties and signals, documented [here](modules/ravestate_nlp/README.md).
- | ravestate_ros2 | Provides specific `Ros2PubProperty`, `Ros2SubProperty` and `Ros2CallProperty` context props., which greatly simplify working with ROS2 in ravestate.
+ | ravestate_emotion | Generates signals for, and recognizes specific emotions (`sig_shy`, `sig_surprise`, `sig_happy`, `sig_affectionate`).
+ | ravestate_ros1 | Provides specific `Ros1PubProperty`, `Ros1SubProperty` and `Ros1CallProperty` context properties, which greatly simplify working with ROS1 in ravestate. Documentation [here](modules/ravestate_ros1/README.md).
+ | ravestate_ros2 | Provides specific `Ros2PubProperty`, `Ros2SubProperty` and `Ros2CallProperty` context properties, which greatly simplify working with ROS2 in ravestate.
#### IO Modules
@@ -223,66 +297,31 @@ External (Red) and Skills (Green):
| ravestate_conio | Simple command-line based IO for development purposes.
| ravestate_telegramio | Single- or Multi-process Telegram server module, documented [here](modules/ravestate_telegramio/README.md).
| ravestate_roboyio | [PyroBoy](https://github.com/roboy/pyroboy) -based STT/TTS with ROS2.
+ | ravestate_visionio | See dedicated docs [here](modules/ravestate_visionio/README.md). Enables face-recognition based dialog interactions.
#### Skill Modules
| Module name | Description |
|----------------------|-------------|
- | ravestate_wildtalk | [ParlAI](https://github.com/roboy/parlai) -based generative conversational module.
+ | ravestate_wildtalk | See docs [here](modules/ravestate_wildtalk/README.md) - runs generative language models (GPT-2, ConvAi, ParlAi)!
| ravestate_hibye | Simply voices __Hi!__ (or the likes thereof) when an interlocutor is added, and __Bye__ when one is removed.
| ravestate_persqa | Conducts personalized smalltalk with interlocutors, interacts with Scientio to persist trivia.
| ravestate_genqa | [DrQA](https://github.com/roboy/drqa) -based general question answering module.
| ravestate_roboyqa | QA module which provides answers to questions about Roboy, such as __Who is your dad?__
- | ravestate_akinator | Enables dialog-based play of [Akinator!](modules/ravestate_akinator/README.md)
- | ravestate_sendpics | Uses face recognition to extract facial features and an assiciated Person with `pic_in` and ontology, which are then persisted in Redis and Scientio.
- | ravestate_stalker | Uses `facial feature <-> person` tuples generated by sendpics, to surprise people in front of a camera with knowledge of their names.
+ | ravestate_akinator __(*)__ | Enables dialog-based play of [Akinator!](modules/ravestate_akinator/README.md)
+ | ravestate_sendpics __(*)__ | Uses face recognition to extract facial features and an assiciated Person with `pic_in` and ontology, which are then persisted in Redis and Scientio.
| ravestate_fillers | Recognize when the dialog context is taking a long time to produce an answer, and voice a filler like __"Uhm"__ or __"Let's see..."__.
+**Note:** __(*)__ = deprecated.
## Running tests
-If you have installed the dependencies from ``requirements-dev.txt`` you
-may run the ravestate test suite as follows:
-
-``
-./run_tests.sh
-``
-
-## Docker for ROS and ROS2
-
-There is a Dockerfile for ROS and ROS2 support which can be built with
-```bash
-docker build -t ravestate-ros2-image .
-```
-The image contains ROS, ROS2 and a ROS Bridge to connect ROS with ROS2.
-Furthermore the roboy_communication message and service types are installed.
-
-A container can then be created with the docker-compose.yml:
-```bash
-docker-compose up --detach ravestate
-```
-The container is now running and a connection into the container can be
-established with:
-```bash
-docker exec -it ravestate-ros2-container bash
-```
-Inside the container, first source the ROS2 setups and then
-ravestate can be run with ROS2 and rclpy available.
-```bash
-source ~/ros2_ws/install/setup.sh
-python3 -m ravestate [...]
-```
+If you have built the ravestate docker image as described above,
+you may run the test suite as follows:
-### Start ROS Bridge
-In order to start ROS Bridge, the image and container have to be set up
-as above. After connecting into the container run from inside the container:
```bash
-export ROS_IP=192.168.0.105
-source ~/melodic_ws/devel/setup.sh
-source ~/ros2_ws/install/setup.sh
-source ~/ros1_bridge_ws/install/setup.sh
-ros2 run ros1_bridge dynamic_bridge
+docker run -t -v $(pwd):/ravestate -w /ravestate ravestate ./run_tests.sh
```
## Building/maintaining the docs
@@ -300,4 +339,4 @@ git add docs/*
# For inspection: python3 -m http.server --directory docs
```
-The structure and content of the docs are defined in the file ``pydocmd.yml``.
+The structure and content of the docs are defined in the file `pydocmd.yml`.
diff --git a/codecov.yml b/codecov.yml
index af37d9e..14cf94c 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -16,3 +16,15 @@ comment:
layout: "reach, diff, flags, files, footer"
behavior: default
require_changes: no
+
+#ignore:
+# - raveboard # exclude ui
+# - ravestate_ui #
+# - akinator # exclude akinator
+# - ravestate/i* # exlude interfaces
+# - ravestate_ros1 # exlude interfaces
+# - ravestate_ros2 # exlude interfaces
+# - "path/to/folder" # ignore folders and all its contents
+# - "test_*.rb" # wildcards accepted
+# - "**/*.py" # glob accepted
+# - "[a-z]+/test_.*" # regexp accepted
\ No newline at end of file
diff --git a/config/roboy.yml b/config/roboy.yml
index a9a7fe8..1d6ecde 100644
--- a/config/roboy.yml
+++ b/config/roboy.yml
@@ -18,17 +18,23 @@ module: core
config:
tickrate: 10
import:
- - ravestate_conio
+ #- ravestate_conio
- ravestate_roboyio
- ravestate_roboyqa
- ravestate_genqa
- ravestate_persqa
- ravestate_wildtalk
- ravestate_hibye
- - ravestate_stalker
+ #- ravestate_stalker
- ravestate_fillers
#- ravestate_akinator
+---
+module: idle
+config:
+ impatience_threshold: 9.0 # number of seconds of "pressure" after which a filler should be sent
+ bored_threshold: 12.0 # number of seconds of "no activity" after which active engagement should activate
+
---
module: genqa
config:
@@ -36,6 +42,37 @@ config:
roboy_answer_sanity: 1000
---
-module: akinator
+module: roboyqa
+config:
+ roboy_node_id: 356
+
+---
+module: roboyio
+config:
+ head_axis0_lower_limit: -0.3 # minimum lean backwards
+ head_axis0_upper_limit: 0.3 # maximum lean forwards
+ head_axis1_lower_limit: -0.3 # minimum lean right
+ head_axis1_upper_limit: 0.3 # maximum lean left
+ head_axis2_lower_limit: -0.3 # minimum turn right
+ head_axis2_upper_limit: 0.3 # maximum turn left
+ head_movement_probability: 0.5 # probability for a head axis to move on input/bored; decided separately for each axis
+ eye_movement_probability: 0.5 # probability for eyes to look left/right on input/bored
+
+---
+module: emotion
+config:
+ shy_probability: 0.2 # Probability for being shy when input is about Roboy
+ surprise_probability: 0.1 # Probability for being surprised when input is question
+ happy_probability: 0.1 # Probability for being happy when output is generated
+ affectionate_probability: 0.5 # Probability for being affectionate when keyword is in input and input is about Roboy
+---
+module: wildtalk
config:
- certainty_percentage: 90
+ model: "convai_gpt" # one of "convai_gpt", "gpt2", "parlai"
+ server_address: "http://35.246.158.89" # can be changed if server is running on its own on a separate machine
+ server_port: 5100
+ temperature: 0.7 # convai_gpt, gpt2: higher value -> more variation in output
+ max_length: 20 # convai_gpt, gpt2: maximal length of generated output
+ top_k: 0 # convai_gpt, gpt2: <=0: no filtering, >0: keep only top k tokens with highest probability.
+ top_p: 0.9 # convai_gpt: <=0.0 no filtering, >0.0: keep smallest subset whose total probability mass >= top_p
+ max_history: 4 # convai_gpt: maximal number of previous dialog turns to be used for output generation
diff --git a/config/roboy_telegram_bot_child.yml b/config/roboy_telegram_bot_child.yml
index ea63e9d..d0d5095 100644
--- a/config/roboy_telegram_bot_child.yml
+++ b/config/roboy_telegram_bot_child.yml
@@ -23,7 +23,7 @@ config:
- ravestate_roboyqa
- ravestate_genqa
- ravestate_persqa
- - ravestate_sendpics
+ #- ravestate_sendpics
#- ravestate_akinator
---
@@ -36,3 +36,30 @@ config:
module: akinator
config:
certainty_percentage: 90
+---
+module: roboyqa
+config:
+ roboy_node_id: 356
+---
+module: idle
+config:
+ impatience_threshold: 6.0 # number of seconds of "pressure" after which a filler should be sent
+ bored_threshold: 12.0 # number of seconds of "no activity" after which active engagement should activate
+---
+module: emotion
+config:
+ shy_probability: 0.2 # Probability for being shy when input is about Roboy
+ surprise_probability: 0.1 # Probability for being surprised when input is question
+ happy_probability: 0.1 # Probability for being happy when output is generated
+ affectionate_probability: 0.5 # Probability for being affectionate when keyword is in input and input is about Roboy
+---
+module: wildtalk
+config:
+ model: "convai_gpt" # one of "convai_gpt", "gpt2", "parlai"
+ server_address: "http://35.246.158.89" # can be changed if server is running on its own on a separate machine
+ server_port: 5100
+ temperature: 0.7 # convai_gpt, gpt2: higher value -> more variation in output
+ max_length: 20 # convai_gpt, gpt2: maximal length of generated output
+ top_k: 0 # convai_gpt, gpt2: <=0: no filtering, >0: keep only top k tokens with highest probability.
+ top_p: 0.9 # convai_gpt: <=0.0 no filtering, >0.0: keep smallest subset whose total probability mass >= top_p
+ max_history: 4 # convai_gpt: maximal number of previous dialog turns to be used for output generation
diff --git a/config/roboy_telegram_bot_master.yml b/config/roboy_telegram_bot_master.yml
index ee4cd4b..69e8024 100644
--- a/config/roboy_telegram_bot_master.yml
+++ b/config/roboy_telegram_bot_master.yml
@@ -10,7 +10,7 @@
---
module: core
config:
- tickrate: 2
+ tickrate: 10
import:
- ravestate_telegramio
diff --git a/config/visionio-docker.yml b/config/visionio-docker.yml
new file mode 100644
index 0000000..eb2deae
--- /dev/null
+++ b/config/visionio-docker.yml
@@ -0,0 +1,37 @@
+# This configuration is designed to be used with ravestate inside
+# the ravestate docker container, run with docker-compose
+# as described in README.md.
+
+---
+module: core
+config:
+ tickrate: 10
+ import:
+ - ravestate_conio
+ - ravestate_visionio
+ - ravestate_fillers
+ - ravestate_wildtalk
+ - ravestate_hibye
+ - ravestate_genqa
+ - ravestate_roboyqa
+ - ravestate_persqa
+ ros2-node-name: "ros2-node-name"
+
+---
+module: genqa
+config:
+ drqa_server_address: http://35.246.158.89:5000
+ roboy_answer_sanity: 1000
+
+---
+module: ontology
+config:
+ neo4j_address: bolt://localhost:7687
+ neo4j_username: neo4j
+ neo4j_pw: test
+
+---
+module: visionio
+config:
+ redis_host: localhost
+ redis_pass: ""
diff --git a/docker-compose.yml b/docker-compose.yml
index 0d8941a..b61106a 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,24 +1,82 @@
version: '2.1'
services:
- ravestate:
- image: ravestate-ros2-image
- container_name: ravestate-ros2-container
+
+ rs-linux:
+ image: ravestate
+ container_name: rs
build:
context: .
dockerfile: Dockerfile
network_mode: host
volumes:
- .:/ravestate
+ - ./db/neo4j:/var/lib/neo4j/data/databases
+ - ./db/redis:/redis_db
+ devices:
+ - /dev/snd:/dev/snd
+ - /dev/video0:/dev/video0
+ ports:
+ - "10002:10002"
+ - "9000:9000"
+ - "9001:9001"
+ - "8088:8088"
+ - "5000:5000"
+ - "4200:4200"
+ - "42424:42424"
+ - "7687:7687"
+ - "7474:7474"
+ environment:
+ - PYTHONPATH=$PYTHONPATH:/ravestate/modules
+ - PYTHONUNBUFFERED=1
+ - NEO4J_ADDRESS=bolt://localhost:7687
+ - NEO4J_USERNAME=neo4j
+ - NEO4J_PASSWORD=test
+ - REDIS_HOST=localhost
+ - REDIS_PASSWORD=
+ # - FACEORACLE_VIDEO_DEVICE=/ravestate/resources/obama.mp4
+ - FACEORACLE_VIDEO_DEVICE=0
+ # After starting container, attach console and enter python3 -m ravestate [...]
+ # This enables "hot reload" in the running container because the source directory is mounted
+ tty: true
+ stdin_open: true
+ entrypoint:
+ - /ravestate/docker-entrypoint.sh
+
+ rs-macos:
+ image: ravestate
+ container_name: rs
+ build:
+ context: .
+ dockerfile: Dockerfile
+ network_mode: bridge
+ volumes:
+ - .:/ravestate
+ - ./db/neo4j:/var/lib/neo4j/data/databases
+ - ./db/redis:/redis_db
ports:
- "10002:10002"
- "9000:9000"
- "9001:9001"
+ - "8088:8088"
+ - "5000:5000"
+ - "4200:4200"
+ - "42424:42424"
+ - "7687:7687"
+ - "7474:7474"
environment:
- PYTHONPATH=$PYTHONPATH:/ravestate/modules
- PYTHONUNBUFFERED=1
+ - NEO4J_ADDRESS=bolt://localhost:7687
+ - NEO4J_USERNAME=neo4j
+ - NEO4J_PASSWORD=test
+ - REDIS_HOST=localhost
+ - REDIS_PASSWORD=
+ # - FACEORACLE_VIDEO_DEVICE=/ravestate/resources/obama.mp4
+ - FACEORACLE_VIDEO_DEVICE=rtmp://host.docker.internal/live/faceoracle
# After starting container, attach console and enter python3 -m ravestate [...]
# This enables "hot reload" in the running container because the source directory is mounted
- command: bash
tty: true
stdin_open: true
+ entrypoint:
+ - /ravestate/docker-entrypoint.sh
diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh
new file mode 100755
index 0000000..2189fea
--- /dev/null
+++ b/docker-entrypoint.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+#################################################################################
+#
+# NOTE: This file is designed to be run inside the ravestate docker container via
+#
+# `docker-compose start rs-{macos|linux}`
+#
+#################################################################################
+
+set -x
+
+# Make sure ROS is sourced, also in future terminal sessions
+. ~/melodic_ws/devel/setup.bash
+
+# Prepare .bashrc
+echo ". ~/melodic_ws/devel/setup.bash" >> /root/.bashrc
+echo "echo '============================================================='" >> /root/.bashrc
+echo "echo '>>>>>>>> Welcome to the Ravestate Docker Container <<<<<<<<<<'" >> /root/.bashrc
+echo "echo ' This container is intended for development purposes only. '" >> /root/.bashrc
+echo "echo '============================================================='" >> /root/.bashrc
+
+# Make sure Neo4j is advertised outside of the container
+echo dbms.connectors.default_listen_address=0.0.0.0 >> /etc/neo4j/neo4j.conf
+
+# Start Neo4j, Roscore, Redis
+neo4j start
+roscore &
+cd /redis_db && redis-server &
+
+echo "----------------------------------------------"
+echo "Sleeping 10 s to wait for neo4j and roscore ..."
+echo "----------------------------------------------"
+sleep 10
+
+# Start face_oracle server and client
+cd /root/melodic_ws/src/face_oracle
+python3 ws_server.py --redis-host "$REDIS_HOST" --redis-pass "$REDIS_PASSWORD" &
+python webcam_video_processor.py -i $FACEORACLE_VIDEO_DEVICE -q ws://localhost:8765 &
+
+# Start to keep the container open
+bash
diff --git a/docs/404.html b/docs/404.html
index 8ac8fbe..72edc8c 100644
--- a/docs/404.html
+++ b/docs/404.html
@@ -194,6 +194,18 @@
+
Emit a signal to the signal processing loop. Note:
- The signal will only be processed if run() has been called!
+ The spike will only be picked up by activations once run_once/run is called!
signal: The signal to be emitted.
@@ -382,11 +469,17 @@
emit
payload: Value that should be embedded in the new spike.
+
+
boring: Flag which indicates, whether the new spike is boring. Activations which
+ acquire boring spikes will not count against the core:activity flag.
Delete all spikes for the given signal. Partially fulfilled states
that have acquired an affected spike will be forced to reject it.
@@ -397,26 +490,30 @@
wipe
run
-
Context.run(self) -> None
-
+
Context.run(self)->None
+
+
Creates a signal processing thread, starts it, and emits the core:startup signal.
shutting_down
-
Context.shutting_down(self) -> bool
-
+
Context.shutting_down(self)->bool
+
+
Retrieve the shutdown flag value, which indicates whether shutdown() has been called.
shutdown
-
Context.shutdown(self) -> None
-
+
Context.shutdown(self)->None
+
+
Sets the shutdown flag and waits for the signal processing thread to join.
Add a state to this context. It will be indexed wrt/ the properties/signals
it depends on. Error messages will be generated for unknown signals/properties.
Add a copy of a property to this context. An error message will be generated, if a property with
the same name has already been added previously. Note: Context will adopt a copy
@@ -459,8 +559,9 @@
Called by activation, to indicate, that it needs a new Spike
for the specified signal, and should for this purpose be referenced by context.
@@ -513,8 +617,9 @@
This class encapsulates a single spike, to track ...
... it's consumption for different output properties (through CausalGroup).
... it's offspring instances (causal group -> spikes caused by this spike)
+
boring
+
+
Spike.boring(self)
+
+
+
+
Get the boring-flag of this spike, which indicates whether state
+ of the activation that produced this spike was boring.
+
:return: True if the activation boring, false otherwise.
Called either in Context run loop when the spike is found to be stale
(with wiped_in_causal_group=True), or in Context.wipe(spike),
@@ -630,61 +753,70 @@
wipe
has_offspring
-
Spike.has_offspring(self)
-
+
Spike.has_offspring(self)
+
+
Called by CausalGroup.stale(spike).
Returns: True if the spike has active offspring, false otherwise.
Encapsulates the potential activation of a state. Tracks the collection
of Spikes to fulfill of the state-defined activation constraints.
resources
-
Activation.resources(self) -> Set[str]
-
+
Activation.resources(self)->Set[str]
+
+
Return's the set of the activation's write-access property names.
specificity
-
Activation.specificity(self) -> float
-
+
Activation.specificity(self)->float
+
+
Returns the lowest specificity among the specificity values of the
activation's conjunct constraints. The specificity for a single conjunction
@@ -692,8 +824,9 @@
specificity
which in turn is calculated as one over the signal's subscriber count.
Notify the activation, that a single or all spike(s) are not available
anymore, and should therefore not be referenced anymore by the activation.
@@ -723,8 +856,9 @@
Returns true, if the activation has acquired any spikes at all.
+
+
filter_boring: Flag to indicate, whether boring spikes should
+ should NOT be counted against a true return value.
+
Returns: True, if any of this activation's constraint's
signal is referencing a spike.
+
boring
+
+
Activation.boring(self)->bool
+
+
+
+
Returns True, if the activation's state is boring. Called by
+ context, to figure out whether this activation counts towards
+ the system not setting the idle:bored property to True.
+
Returns: True, if the state assigned to this activation has
+ the boring field set to true, False otherwise.
Notify the activation, that a follow-up signal will not be produced
by the given causal group. The activation will go through it's constraint,
@@ -804,8 +960,9 @@
effect_not_caused
update
-
Activation.update(self) -> bool
-
+
Activation.update(self)->bool
+
+
Called once per tick on this activation, to give it a chance to activate
itself, or auto-eliminate, or reject spikes which have become too old.
@@ -815,8 +972,9 @@
ravestate.causal
CausalGroup
-
CausalGroup(self, resources: Set[str])
-
+
CausalGroup(self,resources:Set[str])
+
+
Class which represents a causal group graph of spike parent/offspring
spikes (a "superspike"). These must synchronize wrt/ their (un)written
@@ -825,8 +983,9 @@
CausalGroup
Otherwise, undefined behavior may occur due to race conditions.
merge
-
CausalGroup.merge(self, other: 'CausalGroup')
-
+
CausalGroup.merge(self,other:'CausalGroup')
+
+
Merge this causal group with another. Unwritten props will become
the set intersection of this group's unwritten props and
@@ -835,8 +994,9 @@
merge
Afterwards, other's member objects will be set to this's.
Called by a state activation, to notify the group that a member spike
is no longer being referenced for the given state's write props.
@@ -881,8 +1042,9 @@
Make sure, that the refcount-per-act-per-spike-per-resource value sum
is equal to the number of spikes from this causal group acquired per activation
diff --git a/docs/index.html b/docs/index.html
index 4d3382e..6ebfdd5 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -206,8 +206,8 @@
Ravestate is a reactive library for real-time natural language dialog systems. It combines elements from event-based and reactive programming into an API, where application states are defined as functions that are run when a certain boolean set of criteria (signals) in the current application context is satisfied. It is the first reactive API to allow for boolean combinations of events. You may find a short introductory video here.
Ravestate is a reactive library for real-time natural language dialog systems.
+It combines elements from event-based and reactive programming into an API,
+where application states are defined as functions that are run when a certain
+boolean set of criteria (signals) in the current application context is satisfied.
+It is the first reactive API to allow for boolean combinations of events.
+You may find a short introductory video here.
Reactive Hello World
-
import ravestate as rs
-
-# We want to write some text output, so we
-# need the raw:out context property from ravestate_rawio.
-import ravestate_rawio as rawio
-
-# Make sure that we use some i/o implementation,
-# so we can actually see stuff that is written to rawio:out.
-import ravestate_conio
-
-# Ravestate applications should always be wrapped in a Module.
-# This allows easier scoping, and enables separation of concerns
-# beyond states.
-with rs.Module(name="hi!"):
-
- # Create an application state which reacts to the `:startup` signal,
- # and writes a string to raw:out. Note: State functions are
- # always run asynchronously!
- @rs.state(cond=rs.sig_startup, write=rawio.prop_out)
- def hello_world(context):
- context[rawio.prop_out] = "Waddup waddup waddup!"
-
-# Run context with console input/output and our 'hi!' module.
-rs.Context("conio", "hi!").run()
-
-
-
Visualization
-
Ravestate has a d3.js-based visualization. When using ravestate_ui.UIContext instead of Context, or python3 -m ravestate_ui instead of python3 -m ravestate, a real-time visualization of all states/properties/signals in the state machine will be hosted on port 5001. Here is the view of http://localhost:5001 after launching python3 ravestate_ui -f generic.yml:
-
+
importravestateasrs
+
+# We want to write some text output, so we
+# need the raw:out context property from ravestate_rawio.
+importravestate_rawioasrawio
+
+# Make sure that we use some i/o implementation,
+# so we can actually see stuff that is written to rawio:out.
+importravestate_conio
+
+# Ravestate applications should always be wrapped in a Module.
+# This allows easier scoping, and enables separation of concerns
+# beyond states.
+withrs.Module(name="hi!",depends=(rawio.mod,)):
+
+ # Create an application state which reacts to the `:startup` signal,
+ # and writes a string to raw:out. Note: State functions are
+ # always run asynchronously!
+ @rs.state(cond=rs.sig_startup,write=rawio.prop_out)
+ defhello_world(context):
+ context[rawio.prop_out]="Waddup waddup waddup!"
+
+# Run context with console input/output and our 'hi!' module.
+rs.Context("hi!").run()
+
+
+
+
Raveboard
+
Ravestate has an angular/socket.io-based
+interactive (beta) UI called Raveboard. It shows the events (spikes) that are
+currently relevant, as well as potential state activations that are referencing these spikes.
+
When using raveboard.UIContext instead of Context, or python3 -m raveboard instead of
+python3 -m ravestate, a real-time visualization of all spikes/activations, as well as a chat window,
+will be hosted on a configurable port. You can find dedicated docs here.
The easiest way to install ravestate is through pip:
@@ -799,57 +938,150 @@
Via PIP
For reliability, we recommend using an environment virtualization tool,
like virtualenv
or conda.
-
For developers
+
Via Docker/Docker-compose
+
Why Docker?
+
Ravestate offers a docker image that bundles runtime dependencies that are required
+for advanced cognitive dialog systems/chatbots:
+
+
📦 Neo4j: The Neo4j Graph DBMS is used by Scientio for long-term memory.
+
💡 Redis: A Redis in-memory DB is used for fast short-term memory, e.g. to store/recall facial feature vectors.
+
🤦 FaceOracle: A Roboy-developed server-client architecture used by ravestate_visionio for real-time face recognition.
+
🤖 ROS Melodic: Version 1 of the Robot Operating System for distributed real-time communication.
+ This version of ROS requires a broker process (roscore), which is started automatically inside the container.
+
🤖 ROS2 Dashing: Version 2 of the Robot Operating System for distributed real-time communication.
You can build the ravestate container using the provided Dockerfile:
+
docker build -t ravestate .
+
+
+
+
Note: Building the container takes time and requires a good internet connection, since
+all of the dependencies are several Gigabytes in size.
+
How to run?
+
Use one of the following docker-compose commands to run ravestate in Docker:
+
+
+
+
Platform
+
Command
+
+
+
+
+
Linux
+
docker-compose up -d rs-linux
+
+
+
macOS
+
docker-compose up -d rs-macos
+
+
+
Windows
+
Not supported yet.
+
+
+
+
The container is now running and a shell inside the container can be opened with:
+
docker exec -it rs bash
+
+
+
+
You can now start ravestate or raveboard as described in the section Running Hello World.
+
python3 -m ravestate [...]
+
+
+
+
Which services are exposed from the container?
+
+
+
+
Service
+
Port
+
Description
+
+
+
+
+
Neo4j UI
+
7474
+
Neo4j UI for DB stored under <ravestate>/db/neo4j
+
+
+
Neo4j Bolt Interface
+
7687
+
Communication with Neo4j DBMS
+
+
+
Redis Database Dump
+
-
+
A dump of the Redis DB in the container can be found under <ravestate>/db/redis
+
+
+
FaceOracle Client Interface
+
8088
+
Visualisation for the FaceOracle client.
+
+
+
Raveboard
+
42424
+
Default port for raveboard, the ravestate debug UI.
+
+
+
+
For development
Initial configuration and setup
Clone the repository and install dependencies:
-
# Create a virtual python environment to not pollute the global setup
-python3 -m virtualenv python-ravestate
+
cd ~
+
+# Create a virtual python environment to not pollute the global setup
+python3 -m virtualenv -p python3 python-ravestate
-# Source the virtual environment
+# Source the virtual environment
. python-ravestate/bin/activate
-# Clone the repo
-git clone git@github.com:roboy/ravestate && cd ravestate
+# Clone the repo
+git clone git@github.com:roboy/ravestate &&cd ravestate
-# Install normal requirements
+# Install normal requirements
pip install -r requirements.txt
-# To run tests & build docs, install pytest, mocking, fixtures, pydoc, ...
+# To run tests & build docs, install pytest, mocking, fixtures, pydoc, ...
pip install -r requirements-dev.txt
-# Link your local ravestate clone into your virtualenv
+# Link your local ravestate clone into your virtualenv
pip install -e .
-
-
-
Now, launch a Neo4j docker instance to serve Scientio, so the dialog system has a memory:
-# Open the address localhost:7474 in a browser, and enter the
-# credentials `neo4j`/`neo4j`. You will then be prompted to enter
-# your own password. Remember this password.
-
+
Launch the ravestate docker container as described above. It will serve you Neo4j,
+which is a backend for Scientio, Roboy's
+long-term memory system.
In the config folder, create a file called keys.yml. It should have the following content:
-
module: telegramio
-config:
- telegram-token: <sexycactus> # This is where your own telegram bot token
- # will go later
----
-module: ontology
-config:
- neo4j_address: bolt://localhost:7687 # Your neo4j server uri here
- neo4j_username: neo4j # Your neo4j user here
- neo4j_pw: test # Your neo4j pw here
-
+
module:telegramio
+config:
+ telegram-token:<sexycactus># This is where your own telegram bot token
+ # will go later
+
+
You may now conduct your first conversation with ravestate:
Open raveboard on localhost:42424/ravestate/index.html?rs-sio-url=http%3A//localhost%3A42424
+to conduct your first conversation with ravestate.
After the conversation, check the Neo4j interface under localhost:7474. It should now contain some nodes!
Reminder: Whenever you use ravestate from the command line, source the virtual environment first!
Running your Telegram bot
@@ -858,13 +1090,14 @@
Running your Telegram bot
Setting up PyCharm
Open your local ravestate clone as a project in pycharm.
+
Under Project Preferences > Python interpreter, set your virtual environment.
Mark the modules folder as sources root via the right-click context menu.
-
Create a run config alà the "Edit configurations menu":
+
Create a run config via the "Edit configurations menu":
• Create a new Python configuration.
- • Set modules/ravestate/__main__.py as the script to execute
+ • Set raveboard as the module to execute
• Set the working directory to the git clone directory.
• Set parameters to -f config/generic.yml -f config/keys.yml.
-
You should now be able to run the generic ravestate config from pycharm.
+
You should now be able to run the generic ravestate config from PyCharm.
Running Hello World
Ravestate applications are defined by a configuration,
@@ -874,32 +1107,35 @@
Running Hello World
Running with command line spec
You can easily run a combination of ravestate modules in a shared context,
by listing them as arguments to python3 -m ravestate:
Ravestate offers a landscape of fine-grained modules
for different aspects of dialog application tasks, which
may be seen in the following dependency diagram. Broadly,
@@ -944,8 +1180,16 @@
Core Modules
Spacy-based NLP properties and signals, documented here.
+
ravestate_emotion
+
Generates signals for, and recognizes specific emotions (sig_shy, sig_surprise, sig_happy, sig_affectionate).
+
+
+
ravestate_ros1
+
Provides specific Ros1PubProperty, Ros1SubProperty and Ros1CallProperty context properties, which greatly simplify working with ROS1 in ravestate. Documentation here.
+
+
ravestate_ros2
-
Provides specific Ros2PubProperty, Ros2SubProperty and Ros2CallProperty context props., which greatly simplify working with ROS2 in ravestate.
+
Provides specific Ros2PubProperty, Ros2SubProperty and Ros2CallProperty context properties, which greatly simplify working with ROS2 in ravestate.
Atomic class, which encapsulates a named set of states, properties and config entries,
which form a coherent bundle.
Example:
-
with Module(name="my_module", config={"paramA": 42}):
- # define properties
- # define states
-
+
withModule(name="my_module",config={"paramA":42}):
+ # define properties
+ # define states
+
+
+
modules_per_python_module
+
+
defaultdict(default_factory[, ...]) --> dict with default factory
+
The default factory is called without arguments to produce
+a new value when a key is not present, in getitem only.
+A defaultdict compares equal to a dict with the same items.
+All remaining arguments are treated the same as if they were
+passed to the dict constructor, including keyword arguments.
registered_modules
dict() -> new empty dictionary
@@ -374,23 +468,10 @@
registered_modules
d[k] = v
dict(**kwargs) -> new dictionary initialized with the name=value pairs
in the keyword argument list. For example: dict(one=1, two=2)
-
import_module
-
import_module(*, module_name: str, callback)
-
+
has_module
+
has_module(module_name:str)->bool
+
-
Called by context to import a particular ravestate python module.
-
-
-
module_name: The name of the python module to be imported (must be in pythonpath).
-
-
-
callback: A callback which should be called when a module calls register() while it is being imported.
-
-
-
has_module
-
-
has_module(module_name: str)
-
Check whether a module with a particular name has been registered.
@@ -399,14 +480,17 @@
has_module
Returns: True if a module with the given name has been registered, false otherwise.
If you did not override the raveboard_port config for the core module, you may
+access the UI in any browser under localhost:42424/ravestate/index.html
+Otherwise, just substitute 42424 with your configured port.
+
Setup, Development and Building
+
This project is built with Angular 8 and TypeScript.
+
Initial setup:
+- install Angular CLI globally by running npm install -g @angular/cli (don't forget to add <home>/.npm-global/bin to your path)
+- Install project dependencies by running npm install in the root folder (= folder with package.json)
+
To start development:
+- run ng serve in the root folder to start a dev server
+- Navigate to http://localhost:4200/ in the browser to open the UI
+- The app will automatically reload if you change any of the source files
+
To build a production version:
+- run ng build --prod in the root folder to build a bundle for the browser
+- the bundle is saved in the dist folder
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/modules/ravestate_akinator/index.html b/docs/modules/ravestate_akinator/index.html
index ab603a9..684278c 100644
--- a/docs/modules/ravestate_akinator/index.html
+++ b/docs/modules/ravestate_akinator/index.html
@@ -200,6 +200,18 @@
+
The natural language processing (NLP) module enables Roboy to process and understand the human language.
That way he can interpret the meaning of the detected sentences.
We use a free, open-source NLP library for advanced NLP in Python: spaCy
Each feature is stored in a ravestate property.
A state which wants to access a property needs read permissions for that property.
Example: State that reads the "yesno" property
-
import ravestate as rs
-import ravestate_nlp as nlp
-import ravestate_rawio as rawio
+
importravestateasrs
+importravestate_nlpasnlp
+importravestate_rawioasrawio
-@rs.state(
- cond=nlp.prop_yesno.changed(), # state reacts to a change in the 'yesno' property
- read=nlp.prop_yesno, # state is allowed to read the 'yesno' property
- write=rawio.prop_out) # state is allowed to write to the output property
-def postive_chicken(ctx: ContextWrapper):
- if ctx[nlp.prop_yesno] == "yes":
- ctx[rawio.prop_out] = "You seem to be a positive chicken!"
+@rs.state(
+ cond=nlp.prop_yesno.changed(),# state reacts to a change in the 'yesno' property
+ read=nlp.prop_yesno,# state is allowed to read the 'yesno' property
+ write=rawio.prop_out)# state is allowed to write to the output property
+defpostive_chicken(ctx:ContextWrapper):
+ ifctx[nlp.prop_yesno].yes():
+ ctx[rawio.prop_out]="You seem to be a positive chicken!"
+
-
React to Signals
For 'Sentence Type: Question' a signal is emitted.
Example: State that reacts to the "is-question" signal
-
import ravestate as rs
-import ravestate_nlp as nlp
-import ravestate_rawio as rawio
+
importravestateasrs
+importravestate_nlpasnlp
+importravestate_rawioasrawio
-@rs.state(
- cond=nlp.sig_is_question, # state reacts to the is-question signal
- write=rawio.prop_out) # state is allowed to write to the output property
-def curious_chicken(ctx: ContextWrapper):
- ctx[nlp.prop_out] = "You seem to ask a lot of questions, chicken!"
+@rs.state(
+ cond=nlp.sig_is_question,# state reacts to the is-question signal
+ write=rawio.prop_out)# state is allowed to write to the output property
+defcurious_chicken(ctx:ContextWrapper):
+ ctx[nlp.prop_out]="You seem to ask a lot of questions, chicken!"
+
-
Using the Triples for Sentence Analysis
The triple extraction is done in extract_triples.py by using the dependency tree of the sentence.
@@ -673,22 +799,57 @@
Using the Triples for Sentence
Analyzing a Sentence
Example: 'Chickens like revolutions' reaction state
Triple: subject: 'Chickens', predicate: 'like', object: 'revolutions'
-
import ravestate as rs
-import ravestate_nlp as nlp
-import ravestate_rawio as rawio
-
-@rs.state(
- cond=nlp.prop_triples.changed(), # state reacts to a change in the 'triples' property
- read=nlp.prop_triples, # state is allowed to read the 'triples' property
- write=rawio.prop_out) # state is allowed to write to the output chanel
-def postive_chicken(ctx: ContextWrapper):
- triple = ctx[nlp.prop_triples][0] # gives you the first Triple object
- # check predicate and object correspondingly
- # match_either_lemma() is a method in the Triple class
- if triple.match_either_lemma(subj={"chicken", "dinosaur"}):
- # returns true when subj, pred or obj have the desired value
- ctx[rawio.prop_out] = "You said something about a chicken, i like chickens!"
-
+
importravestateasrs
+importravestate_nlpasnlp
+importravestate_rawioasrawio
+
+@rs.state(
+ cond=nlp.prop_triples.changed(),# state reacts to a change in the 'triples' property
+ read=nlp.prop_triples,# state is allowed to read the 'triples' property
+ write=rawio.prop_out)# state is allowed to write to the output chanel
+defpostive_chicken(ctx:ContextWrapper):
+ triple=ctx[nlp.prop_triples][0]# gives you the first Triple object
+ # check predicate and object correspondingly
+ # match_either_lemma() is a method in the Triple class
+ iftriple.match_either_lemma(subj={"chicken","dinosaur"}):
+ # returns true when subj, pred or obj have the desired value
+ ctx[rawio.prop_out]="You said something about a chicken, i like chickens!"
+
+
+
+
Using User-defined Signals and Implementing Q/A-States
+
A user can define signals as needed and implement states reacting on them.
+If both a signal is emitted and something is written to rawio in one state, the state needs to be defined with emit_detached=True.
+
React to User-defined States and Implement Q/A
+
Example: In one state a question is asks and then a user-defined signal is emitted that shows that the question has been stated. The other states reacts on the answer to this question.
Sentence Processing and Feature Extraction Techniques for Ice Cream Selling
+
This document discusses the possible sentence processing and feature extraction techniques
+applied with NLP module of ravestate library for ice cream selling.
+
Yes-No Property
+
This feature can be used to understand a given question's positive/negative answer to proceed such as:
+
+
Do you want the usual?
+
Do you want ice cream?
+
+
Example: State that reads the "yesno" property
+
importravestateasrs
+importravestate_nlpasnlp
+importravestate_rawioasrawio
+
+@rs.state(
+ cond=nlp.prop_yesno.changed(),# state reacts to a change in the 'yesno' property
+ read=nlp.prop_yesno,# state is allowed to read the 'yesno' property
+ write=rawio.prop_out)# state is allowed to write to the output property
+defcustomer_wants_ice_cream(ctx:ContextWrapper):
+ ifctx[nlp.prop_yesno]=="yes":
+ ctx[rawio.prop_out]="Your ice cream is coming right up!"
+
+
+
+
Lemmatization
+
After tokenizing the sentences, roboy needs to match the strings in order to understand the context.
+Lemmatization allows to get the base form of words which is the required form for matching.
+
Named Entity Recognition (NER)
+
NER can be used in order to understand the quantity of ice cream, payment or any matter that requires specifics.
+The following figure includes the most possible types of use, please check
+https://spacy.io/api/annotation#named-entities for further types and for Spacy's interactive NER detector https://explosion.ai/demos/displacy-ent
+
+
+
+
Type
+
DESCRIPTION
+
+
+
+
+
DATE
+
Absolute or relative dates or periods.
+
+
+
TIME
+
Times smaller than a day.
+
+
+
PERCENT
+
Percentage, including ”%“.
+
+
+
MONEY
+
Monetary values, including unit.
+
+
+
QUANTITY
+
Measurements, as of weight or distance.
+
+
+
ORDINAL
+
“first”, “second”, etc.
+
+
+
CARDINAL
+
Numerals that do not fall under another type.
+
+
+
+
Triple Extraction
+
Triple Extraction provides object and subject applied with the verb.
+This allows us to have a wider perspective over the request because
+we can specifically understand which flavor is additionally or less requested.
+Consider the following scenarios:
+
+
I need more vanilla ice cream
+
Drop the chocolate please.
+
+
This feature can be used to understand the requested flavors or payment options.
+Roboy's flavor and payment options are predefined so any match between our database and incoming token
+can help us to identify the requested options. Such as:
+
+
"vanilla"
+
"chocolate"
+
"lemon"
+
"strawberry"
+
+
importravestateasrs
+importravestate_nlpasnlp
+importravestate_rawioasrawio
+
+prop_flavor=rs.Property(name="flavor",allow_read=True,allow_write=True,always_signal_changed=True)
+
+@rs.state(
+ cond=nlp.prop_triples.changed(),# state reacts to a change in the 'prop_triples' property
+ read=nlp.prop_triples,# state is allowed to read the 'prop_triples' property
+ write=prop_flavor,# state is allowed to write to the 'prop_flavor' property
+ signal=prop_flavor.changed_signal)# state signals 'prop_flavor' change signal
+defflavor_recognition_state(ctx:ContextWrapper):
+ triple_match_result=ctx[nlp.prop_triples][0].match_either_lemma(obj={"vanilla","strawberry","lemon","chocolate"}):
+ objects=triple_match_result.objs
+ ifobjects:
+ ctx[prop_flavor]=objects
+ returnrs.Emit()
+
+
+
+
or ...
+
+
"credit card"
+
"cash"
+
"bitcoin"
+
+
importravestateasrs
+importravestate_nlpasnlp
+importravestate_rawioasrawio
+
+prop_payment=rs.Property(name="payment",allow_read=True,allow_write=True,always_signal_changed=True)
+
+@rs.state(
+ cond=nlp.prop_triples.changed(),# state reacts to a change in the 'prop_triples' property
+ read=nlp.prop_triples,# state is allowed to read the 'prop_triples' property
+ write=prop_payment,# state is allowed to write to the 'prop_payment' property
+ signal=prop_payment.changed_signal)# state signals 'prop_payment' change signal
+defpayment_recognition_state(ctx:ContextWrapper):
+ triple_match_result=ctx[nlp.prop_triples][0].match_either_lemma(obj={"bitcoin","cash","card"}):
+ objects=triple_match_result.objs
+ ifobjectsandlen(objects)==1:
+ ctx[prop_payment]=objects[0]
+ returnrs.Emit()
+
+
+
+
Alternatively by using triples, Roboy can understand whether the sentence is a request or not by only parsing the verb.
+For example if the customer uses one of the following phrases, it is easy to detect that customer has a request:
The ROS1 module enables easy integration of Publishers, Subscribers and Service Calls into Ravestate.
+It provides 3 Subclasses of property: Ros1SubProperty, Ros1PubProperty and Ros1CallProperty
+
Using the Properties
+
Ros1SubProperty
+
Subclass of property that is synchronized with a given ROS1-Topic.
+Whenever it receives a new message from the topic,
+the value is written to the property and a changed-Signal is emitted.
+
Example: Property that subscribes to the ROS topic chatter of type std_msgs/String
+and state that reacts upon new messages
+
importravestateasrs
+importravestate_rawioasrawio
+fromravestate_ros1importRos1SubProperty
+
+fromstd_msgs.msgimportString# Import ROS message type
+
+withrs.Module(name="ros_chatter"):
+
+ prop_subscriber=Ros1SubProperty(name="subscriber",topic='chatter',msg_type=String)
+
+ @rs.state(read=prop_subscriber,write=rawio.prop_out)
+ defreact_to_message(ctx:rs.ContextWrapper):
+ message=ctx[prop_subscriber.changed()]# message is of type std_msgs.msg.String
+ ctx[rawio.prop_out]=f"Received on ROS-topic chatter: {message.data}"
+
+
+
+
Ros1PubProperty
+
Subclass of property that publishes all values written to it to a given ROS1-Topic.
+
Example: Property that publishes to the ROS topic chatter of type std_msgs/String
+and state that writes all outputs of the dialog system to the property
Subclass of property that calls a ROS-Service when a value is written to it,
+blocks until it gets the response, writes back the response into the property and returns.
+
+
Build Request, can be of different form (see example):
+
Parameters as a dict
+
Parameters as ordered sequence
+
Matching Request-Type
+
+
+
Write Request to property, this blocks until a response is received and written back into the property.
+If no response is received (timeout is 10 seconds per default, can be set with call_timeout),
+None is written back into the property.
+
Now the response can be read from the property. Check if it is None because service was unavailable!
+
+
Example: Property connected to the ROS service /add_two_ints of type rospy_tutorials/AddTwoInts
+and state that calls the Service through the property when the input is "add"
+
importravestateasrs
+importravestate_rawioasrawio
+fromravestate_ros1importRos1CallProperty
+
+# Import ROS service type
+fromrospy_tutorials.srvimportAddTwoInts,AddTwoIntsRequest
+
+
+withrs.Module(name="add_two_ints"):
+
+ prop_addtwoints=Ros1CallProperty(name="addtwoints",
+ service_name="/add_two_ints",
+ service_type=AddTwoInts,
+ call_timeout=5.0)
+
+ # prop_addtwoints has to be in read and write
+ @rs.state(cond=rawio.prop_in.changed(),
+ read=(rawio.prop_in,prop_addtwoints),
+ write=(rawio.prop_out,prop_addtwoints))
+ defadd_two_ints(ctx):
+ ifctx[rawio.prop_in.changed()]=="add":
+ # 1. Build Request
+ # Option 1: Parameters as dict
+ request={'a':11,'b':232}
+ # Option 2: Parameters as ordered sequence
+ request=(1,2)
+ # Option 3: Matching Request-Type
+ request=AddTwoIntsRequest(1,2)
+
+ # 2. Write Request to property.
+ # This blocks until Response is written into property or service call timed out.
+ ctx[prop_addtwoints]=request
+
+ # 3. Read Response (of type rospy_tutorials.srv.AddTwoIntsResponse) from property
+ response=ctx[prop_addtwoints]
+
+ # Check if response is None
+ ifresponse:
+ ctx[rawio.prop_out]=response.sum
+
+
+
+
Happy ROS-Ravestate Integration
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/modules/ravestate_telegramio/index.html b/docs/modules/ravestate_telegramio/index.html
index 1696b32..e391cea 100644
--- a/docs/modules/ravestate_telegramio/index.html
+++ b/docs/modules/ravestate_telegramio/index.html
@@ -200,6 +200,18 @@
+
Example for Answering the Question: What happened to the Dinosaurs?
@@ -548,18 +633,19 @@
Examp
Creating the YAML file:
Fill in all the possible answers.
-
type: qa
-name: "DINO"
-A:
- SUCCESS:
- - "I am sure it was a mind-boggingly huge meteorite!"
- - "They smoked too much ash!"
- - "A vulcano had flatulences."
- - "The chicken were stronger."
- FAILURE:
- - "I have no idea what you just said."
- - "Sorry, I am only interested in dinosaurs."
-
+
type:qa
+name:"DINO"
+A:
+ SUCCESS:
+ -"I am sure it was a mind-boggingly huge meteorite!"
+ -"They smoked too much ash!"
+ -"A vulcano had flatulences."
+ -"The chicken were stronger."
+ FAILURE:
+ -"I have no idea what you just said."
+ -"Sorry, I am only interested in dinosaurs."
+
+
Adding this file to the Verbaliser:
@@ -567,69 +653,74 @@
Examp
In this case the file is going to be located in a folder of important facts.
However, the single file can similarly be added by itself.
The folder is in the same path as the python file adding it.
Interlocutor: "What happend to the Dinosaurs?"
-Roboy: "The chicken were stronger."
-
+
Interlocutor:"What happend to the Dinosaurs?"
+Roboy:"The chicken were stronger."
+
+
Example for Extracting Phrase Lists
The Verbaliser can also be used to get all the imported phrases for a specific intent as a list.
Creating the phrases.yml:
-
type: phrases
-name: "dino"
-opts:
-- "Dinos can not scratch their backs."
-- "Once upon a time these mind-bogglingly huge creatures wandered the earth."
-- "The longest Dinosaur was the Argentiosaurus."
----
-type: phrases
-name: "chicken"
-opts:
-- " Chickens are not completely flightless."
-- " There are more chickens out there than programmers."
-- " If I were a chicken for one day I would say: 'Puk Puk Pukaaak'.
-
-
+
type: phrases
+name: "dino"
+opts:
+-"Dinos can not scratch their backs."
+-"Once upon a time these mind-bogglingly huge creatures wandered the earth."
+-"The longest Dinosaur was the Argentiosaurus."
+---
+type: phrases
+name: "chicken"
+opts:
+-" Chickens are not completely flightless."
+-" There are more chickens out there than programmers."
+-" If I were a chicken for one day I would say: 'Puk Puk Pukaaak'.
+
+
Adding the file to the Verbaliser:
The YAML file is assumed to be located in the important_phrases folder.
The folder is again in the same path as this python script:
The verbaliser:react_to_intent state produces a random phrase output for a given intent.
@@ -638,10 +729,10 @@
The verbaliser:intent Property
The state reads the verbaliser:intent property and outputs one random phrase in the list with that specific intent.
It can therefor be triggered as follows:
Let's assume that phrases.yml is now located in avestate_phrases_basic_en.
The VisionIO module enables ravestate to connect to a Face Recognition
+ROS topic provided by face_oracle, based on which conversations can be initiated.
VisionIO requires the following components to be running:
+
+
ravestate or raveboard with ravestate_visionio module
+
Neo4j backend for Scientio
+
redis for persisting facial feature vectors
+
face_oracle client and server
+
+
Configuration
+
VisionIO provides the following config keys:
+
+
+
+
Key
+
Default
+
Description
+
+
+
+
+
redis_host
+
Host for Redis database.
+
localhost
+
+
+
redis_port
+
Port for Redis database.
+
6379
+
+
+
redis_pass
+
Password for Redis database
+
Empty
+
+
+
ros1-node
+
Topic for Faces messages.
+
/roboy/cognition/vision/visible_face_names
+
+
+
min-confidence
+
Minimum confidence below which someone will be a stranger.
+
0.85
+
+
+
+
How to run
+
We recommend running VisionIO through one of the ravestate
+docker-compose profiles, which will start Neo4j, redis,
+and the face_oracle client and server automatically.
+
Start the profile and visionio in docker as follows:
To start face recognition, open localhost:8088/index.html in a browser (Chrome works best).
+This will give a visualisation of recognised faces, and simultaneously keep recognition running.
+Face recognition will only work as long as you can see it doing so!
+
The docker-compose profiles differ per operating system:
+
Profile rs-linux
+
On Linux, a Webcam for VisionIO can simply be mapped into
+docker as a device. Per default, this will be video0.
+
If you want to change the device, map a new device in
+docker-compose.yml, and don't forget to change the
+FACEORACLE_VIDEO_DEVICE variable.
+
Profile rs-macos
+
On Mac, Docker can not natively access USB devices. Instead,
+live video can be streamed into the container via RTMP:
Stream webcam via RTMP by starting ravestate/run_ffmpeg_stream.sh
+
+
You can now start docker-compose up -d rs-macos.
+
Using a video instead of a webcam feed
+
If you don't have a webcam, you can use a video instead for
+debugging. Just set FACEORACLE_VIDEO_DEVICE for your
+particular platform profile to /ravestate/resources/obama.mp4.
+Note, that after changing docker-compose.yml, you have to run
+docker-compose up -d with the --force-recreate flag.
Base class for context properties. Controls read/write/push/pop/delete permissions,
property name basic impls. for the property value, parent/child mechanism.
Example (Creating a module containing a property named my_property):
-
with Module(name="my_module"):
- my_property = Property(name="my_property")
-
Yields all signals that may be emitted because of
this property, given it's write/push/pop permissions.
diff --git a/docs/resources/docs/modules_sm.png b/docs/resources/docs/modules_sm.png
index 6dbf365..d56acf6 100644
Binary files a/docs/resources/docs/modules_sm.png and b/docs/resources/docs/modules_sm.png differ
diff --git a/docs/resources/docs/modules_sm_old.png b/docs/resources/docs/modules_sm_old.png
new file mode 100644
index 0000000..6dbf365
Binary files /dev/null and b/docs/resources/docs/modules_sm_old.png differ
diff --git a/docs/resources/docs/raveboard.gif b/docs/resources/docs/raveboard.gif
new file mode 100644
index 0000000..efa1076
Binary files /dev/null and b/docs/resources/docs/raveboard.gif differ
diff --git a/docs/search/search_index.json b/docs/search/search_index.json
index ff47059..907712f 100644
--- a/docs/search/search_index.json
+++ b/docs/search/search_index.json
@@ -1 +1 @@
-{"config":{"lang":["en"],"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"About ____ __ __ _____ _____ / _ \\____ __ ______ ______/ /_____/ /___ /_ _\\ /_ _\\ / /_/ / __ \\/ / / / __ \\/ ___\\, / __ \\, / __ \\ 0> 0> <0 <0 / ,\\ ,/ /_/ /\\ \\/ / /_/ /\\__, / / /_/ / / /_/ / \\__\u22bd__/ \\__\u22bd__/ \\/ \\/\\__/\\/ \\__/ ,___/\\____/\\/\\__/\\/\\/ ,___/ \u22c2 - Hey! \u22c2 \\____/ \\____/ Ol\u00e0! - Ravestate is a reactive library for real-time natural language dialog systems. It combines elements from event-based and reactive programming into an API, where application states are defined as functions that are run when a certain boolean set of criteria (signals) in the current application context is satisfied. It is the first reactive API to allow for boolean combinations of events. You may find a short introductory video here . Reactive Hello World import ravestate as rs # We want to write some text output, so we # need the raw:out context property from ravestate_rawio. import ravestate_rawio as rawio # Make sure that we use some i/o implementation, # so we can actually see stuff that is written to rawio:out. import ravestate_conio # Ravestate applications should always be wrapped in a Module. # This allows easier scoping, and enables separation of concerns # beyond states. with rs.Module(name=\"hi!\"): # Create an application state which reacts to the `:startup` signal, # and writes a string to raw:out. Note: State functions are # always run asynchronously! @rs.state(cond=rs.sig_startup, write=rawio.prop_out) def hello_world(context): context[rawio.prop_out] = \"Waddup waddup waddup!\" # Run context with console input/output and our 'hi!' module. rs.Context(\"conio\", \"hi!\").run() Visualization Ravestate has a d3.js -based visualization. When using ravestate_ui.UIContext instead of Context , or python3 -m ravestate_ui instead of python3 -m ravestate , a real-time visualization of all states/properties/signals in the state machine will be hosted on port 5001. Here is the view of http://localhost:5001 after launching python3 ravestate_ui -f generic.yml : Installation Via PIP The easiest way to install ravestate is through pip: pip install ravestate Note: Ravestate requires Python 3.6 or higher. It is tested on Ubuntu 16.04 and 18.04, as well as macOS > High Sierra. It is currently not tested on Windows. For reliability, we recommend using an environment virtualization tool, like virtualenv or conda . For developers Initial configuration and setup Clone the repository and install dependencies: # Create a virtual python environment to not pollute the global setup python3 -m virtualenv python-ravestate # Source the virtual environment . python-ravestate/bin/activate # Clone the repo git clone git@github.com:roboy/ravestate && cd ravestate # Install normal requirements pip install -r requirements.txt # To run tests & build docs, install pytest, mocking, fixtures, pydoc, ... pip install -r requirements-dev.txt # Link your local ravestate clone into your virtualenv pip install -e . Now, launch a Neo4j docker instance to serve Scientio , so the dialog system has a memory: docker run \\ --publish=7474:7474 --publish=7687:7687 \\ --volume=$HOME/neo4j/data:/data \\ --volume=$HOME/neo4j/logs:/logs \\ neo4j:latest # Open the address localhost:7474 in a browser, and enter the # credentials `neo4j`/`neo4j`. You will then be prompted to enter # your own password. Remember this password. In the config folder, create a file called keys.yml . It should have the following content: module: telegramio config: telegram-token: # This is where your own telegram bot token # will go later --- module: ontology config: neo4j_address: bolt://localhost:7687 # Your neo4j server uri here neo4j_username: neo4j # Your neo4j user here neo4j_pw: test # Your neo4j pw here You may now conduct your first conversation with ravestate: python3 -m ravestate -f config/generic.yml -f config/keys.yml After the conversation, check the Neo4j interface under localhost:7474 . It should now contain some nodes! Reminder: Whenever you use ravestate from the command line, source the virtual environment first! Running your Telegram bot To test your telegram bot with a custom bot token in your keys.yml , just run telegram_test.yml instead of generic.yml . This will load the ravestate_telegramio module. Setting up PyCharm Open your local ravestate clone as a project in pycharm. Mark the modules folder as sources root via the right-click context menu. Create a run config al\u00e0 the \"Edit configurations menu\": \u2022 Create a new Python configuration. \u2022 Set modules/ravestate/__main__.py as the script to execute \u2022 Set the working directory to the git clone directory. \u2022 Set parameters to -f config/generic.yml -f config/keys.yml . You should now be able to run the generic ravestate config from pycharm. Running Hello World Ravestate applications are defined by a configuration, which specifies the ravestate modules that should be loaded. To run the basic hello world application, run ravestate with a config file or command line arguments: Running with command line spec You can easily run a combination of ravestate modules in a shared context, by listing them as arguments to python3 -m ravestate : python3 -m ravestate \\ ravestate_wildtalk \\ ravestate_conio \\ ravestate_hibye \\ ravestate_persqa Run python3 -m ravestate -h to see more options! Running with config file(s) You may specify a series of config files to configure ravestate context, when specifying everything through the command line becomes too laborious: # In file hello_world.yml module: core config: import: - ravestate_wildtalk - ravestate_conio - ravestate_hibye - ravestate_persqa Then, run ravestate with this config file: python3 -m ravestate -f hello_world.yml Module overview Ravestate offers a landscape of fine-grained modules for different aspects of dialog application tasks, which may be seen in the following dependency diagram. Broadly, the modules are categorized into Core (Blue), I/O (Yellow), External (Red) and Skills (Green): Core Modules Module name Description ravestate Core ravestate library. ravestate_rawio Provides raw_in , raw_out , pic_in properties, which are served by the IO modules. ravestate_ontology Connects to scientio to serve a built-in ontology. ravestate_interloc Provides the all_interlocutors property, where present interlocutors are registered by the IO modules. ravestate_idle Provides bored and impatient signals, as specified here . ravestate_verbaliser Utilities for easy management of conversational text, documented here . ravestate_nlp Spacy-based NLP properties and signals, documented here . ravestate_ros2 Provides specific Ros2PubProperty , Ros2SubProperty and Ros2CallProperty context props., which greatly simplify working with ROS2 in ravestate. IO Modules Module name Description ravestate_conio Simple command-line based IO for development purposes. ravestate_telegramio Single- or Multi-process Telegram server module, documented here . ravestate_roboyio PyroBoy -based STT/TTS with ROS2. Skill Modules Module name Description ravestate_wildtalk ParlAI -based generative conversational module. ravestate_hibye Simply voices Hi! (or the likes thereof) when an interlocutor is added, and Bye when one is removed. ravestate_persqa Conducts personalized smalltalk with interlocutors, interacts with Scientio to persist trivia. ravestate_genqa DrQA -based general question answering module. ravestate_roboyqa QA module which provides answers to questions about Roboy, such as Who is your dad? ravestate_akinator Enables dialog-based play of Akinator! ravestate_sendpics Uses face recognition to extract facial features and an assiciated Person with pic_in and ontology, which are then persisted in Redis and Scientio. ravestate_stalker Uses facial feature <-> person tuples generated by sendpics, to surprise people in front of a camera with knowledge of their names. Running tests If you have installed the dependencies from requirements-dev.txt you may run the ravestate test suite as follows: ./run_tests.sh Docker for ROS and ROS2 There is a Dockerfile for ROS and ROS2 support which can be built with docker build -t ravestate-ros2-image . The image contains ROS, ROS2 and a ROS Bridge to connect ROS with ROS2. Furthermore the roboy_communication message and service types are installed. A container can then be created with the docker-compose.yml: docker-compose up --detach ravestate The container is now running and a connection into the container can be established with: docker exec -it ravestate-ros2-container bash Inside the container, first source the ROS2 setups and then ravestate can be run with ROS2 and rclpy available. source ~/ros2_ws/install/setup.sh python3 -m ravestate [...] Start ROS Bridge In order to start ROS Bridge, the image and container have to be set up as above. After connecting into the container run from inside the container: export ROS_IP=192.168.0.105 source ~/melodic_ws/devel/setup.sh source ~/ros2_ws/install/setup.sh source ~/ros1_bridge_ws/install/setup.sh ros2 run ros1_bridge dynamic_bridge Building/maintaining the docs If you have installed the dependencies from requirements-dev.txt , generate the docs by running this command at project root: export PYTHONPATH=$PYTHONPATH:$(pwd)/modules git rm -rf docs rm -rf _build docs pydocmd build mkdir -p docs/resources/docs && cp resources/docs/*.png docs/resources/docs && cp resources/docs/*.gif docs/resources/docs git add docs/* # For inspection: python3 -m http.server --directory docs The structure and content of the docs are defined in the file pydocmd.yml .","title":"Home"},{"location":"#about","text":"____ __ __ _____ _____ / _ \\____ __ ______ ______/ /_____/ /___ /_ _\\ /_ _\\ / /_/ / __ \\/ / / / __ \\/ ___\\, / __ \\, / __ \\ 0> 0> <0 <0 / ,\\ ,/ /_/ /\\ \\/ / /_/ /\\__, / / /_/ / / /_/ / \\__\u22bd__/ \\__\u22bd__/ \\/ \\/\\__/\\/ \\__/ ,___/\\____/\\/\\__/\\/\\/ ,___/ \u22c2 - Hey! \u22c2 \\____/ \\____/ Ol\u00e0! - Ravestate is a reactive library for real-time natural language dialog systems. It combines elements from event-based and reactive programming into an API, where application states are defined as functions that are run when a certain boolean set of criteria (signals) in the current application context is satisfied. It is the first reactive API to allow for boolean combinations of events. You may find a short introductory video here .","title":"About"},{"location":"#reactive-hello-world","text":"import ravestate as rs # We want to write some text output, so we # need the raw:out context property from ravestate_rawio. import ravestate_rawio as rawio # Make sure that we use some i/o implementation, # so we can actually see stuff that is written to rawio:out. import ravestate_conio # Ravestate applications should always be wrapped in a Module. # This allows easier scoping, and enables separation of concerns # beyond states. with rs.Module(name=\"hi!\"): # Create an application state which reacts to the `:startup` signal, # and writes a string to raw:out. Note: State functions are # always run asynchronously! @rs.state(cond=rs.sig_startup, write=rawio.prop_out) def hello_world(context): context[rawio.prop_out] = \"Waddup waddup waddup!\" # Run context with console input/output and our 'hi!' module. rs.Context(\"conio\", \"hi!\").run()","title":"Reactive Hello World"},{"location":"#visualization","text":"Ravestate has a d3.js -based visualization. When using ravestate_ui.UIContext instead of Context , or python3 -m ravestate_ui instead of python3 -m ravestate , a real-time visualization of all states/properties/signals in the state machine will be hosted on port 5001. Here is the view of http://localhost:5001 after launching python3 ravestate_ui -f generic.yml :","title":"Visualization"},{"location":"#installation","text":"","title":"Installation"},{"location":"#via-pip","text":"The easiest way to install ravestate is through pip: pip install ravestate Note: Ravestate requires Python 3.6 or higher. It is tested on Ubuntu 16.04 and 18.04, as well as macOS > High Sierra. It is currently not tested on Windows. For reliability, we recommend using an environment virtualization tool, like virtualenv or conda .","title":"Via PIP"},{"location":"#for-developers","text":"","title":"For developers"},{"location":"#initial-configuration-and-setup","text":"Clone the repository and install dependencies: # Create a virtual python environment to not pollute the global setup python3 -m virtualenv python-ravestate # Source the virtual environment . python-ravestate/bin/activate # Clone the repo git clone git@github.com:roboy/ravestate && cd ravestate # Install normal requirements pip install -r requirements.txt # To run tests & build docs, install pytest, mocking, fixtures, pydoc, ... pip install -r requirements-dev.txt # Link your local ravestate clone into your virtualenv pip install -e . Now, launch a Neo4j docker instance to serve Scientio , so the dialog system has a memory: docker run \\ --publish=7474:7474 --publish=7687:7687 \\ --volume=$HOME/neo4j/data:/data \\ --volume=$HOME/neo4j/logs:/logs \\ neo4j:latest # Open the address localhost:7474 in a browser, and enter the # credentials `neo4j`/`neo4j`. You will then be prompted to enter # your own password. Remember this password. In the config folder, create a file called keys.yml . It should have the following content: module: telegramio config: telegram-token: # This is where your own telegram bot token # will go later --- module: ontology config: neo4j_address: bolt://localhost:7687 # Your neo4j server uri here neo4j_username: neo4j # Your neo4j user here neo4j_pw: test # Your neo4j pw here You may now conduct your first conversation with ravestate: python3 -m ravestate -f config/generic.yml -f config/keys.yml After the conversation, check the Neo4j interface under localhost:7474 . It should now contain some nodes! Reminder: Whenever you use ravestate from the command line, source the virtual environment first!","title":"Initial configuration and setup"},{"location":"#running-your-telegram-bot","text":"To test your telegram bot with a custom bot token in your keys.yml , just run telegram_test.yml instead of generic.yml . This will load the ravestate_telegramio module.","title":"Running your Telegram bot"},{"location":"#setting-up-pycharm","text":"Open your local ravestate clone as a project in pycharm. Mark the modules folder as sources root via the right-click context menu. Create a run config al\u00e0 the \"Edit configurations menu\": \u2022 Create a new Python configuration. \u2022 Set modules/ravestate/__main__.py as the script to execute \u2022 Set the working directory to the git clone directory. \u2022 Set parameters to -f config/generic.yml -f config/keys.yml . You should now be able to run the generic ravestate config from pycharm.","title":"Setting up PyCharm"},{"location":"#running-hello-world","text":"Ravestate applications are defined by a configuration, which specifies the ravestate modules that should be loaded. To run the basic hello world application, run ravestate with a config file or command line arguments:","title":"Running Hello World"},{"location":"#running-with-command-line-spec","text":"You can easily run a combination of ravestate modules in a shared context, by listing them as arguments to python3 -m ravestate : python3 -m ravestate \\ ravestate_wildtalk \\ ravestate_conio \\ ravestate_hibye \\ ravestate_persqa Run python3 -m ravestate -h to see more options!","title":"Running with command line spec"},{"location":"#running-with-config-files","text":"You may specify a series of config files to configure ravestate context, when specifying everything through the command line becomes too laborious: # In file hello_world.yml module: core config: import: - ravestate_wildtalk - ravestate_conio - ravestate_hibye - ravestate_persqa Then, run ravestate with this config file: python3 -m ravestate -f hello_world.yml","title":"Running with config file(s)"},{"location":"#module-overview","text":"Ravestate offers a landscape of fine-grained modules for different aspects of dialog application tasks, which may be seen in the following dependency diagram. Broadly, the modules are categorized into Core (Blue), I/O (Yellow), External (Red) and Skills (Green):","title":"Module overview"},{"location":"#core-modules","text":"Module name Description ravestate Core ravestate library. ravestate_rawio Provides raw_in , raw_out , pic_in properties, which are served by the IO modules. ravestate_ontology Connects to scientio to serve a built-in ontology. ravestate_interloc Provides the all_interlocutors property, where present interlocutors are registered by the IO modules. ravestate_idle Provides bored and impatient signals, as specified here . ravestate_verbaliser Utilities for easy management of conversational text, documented here . ravestate_nlp Spacy-based NLP properties and signals, documented here . ravestate_ros2 Provides specific Ros2PubProperty , Ros2SubProperty and Ros2CallProperty context props., which greatly simplify working with ROS2 in ravestate.","title":"Core Modules"},{"location":"#io-modules","text":"Module name Description ravestate_conio Simple command-line based IO for development purposes. ravestate_telegramio Single- or Multi-process Telegram server module, documented here . ravestate_roboyio PyroBoy -based STT/TTS with ROS2.","title":"IO Modules"},{"location":"#skill-modules","text":"Module name Description ravestate_wildtalk ParlAI -based generative conversational module. ravestate_hibye Simply voices Hi! (or the likes thereof) when an interlocutor is added, and Bye when one is removed. ravestate_persqa Conducts personalized smalltalk with interlocutors, interacts with Scientio to persist trivia. ravestate_genqa DrQA -based general question answering module. ravestate_roboyqa QA module which provides answers to questions about Roboy, such as Who is your dad? ravestate_akinator Enables dialog-based play of Akinator! ravestate_sendpics Uses face recognition to extract facial features and an assiciated Person with pic_in and ontology, which are then persisted in Redis and Scientio. ravestate_stalker Uses facial feature <-> person tuples generated by sendpics, to surprise people in front of a camera with knowledge of their names.","title":"Skill Modules"},{"location":"#running-tests","text":"If you have installed the dependencies from requirements-dev.txt you may run the ravestate test suite as follows: ./run_tests.sh","title":"Running tests"},{"location":"#docker-for-ros-and-ros2","text":"There is a Dockerfile for ROS and ROS2 support which can be built with docker build -t ravestate-ros2-image . The image contains ROS, ROS2 and a ROS Bridge to connect ROS with ROS2. Furthermore the roboy_communication message and service types are installed. A container can then be created with the docker-compose.yml: docker-compose up --detach ravestate The container is now running and a connection into the container can be established with: docker exec -it ravestate-ros2-container bash Inside the container, first source the ROS2 setups and then ravestate can be run with ROS2 and rclpy available. source ~/ros2_ws/install/setup.sh python3 -m ravestate [...]","title":"Docker for ROS and ROS2"},{"location":"#start-ros-bridge","text":"In order to start ROS Bridge, the image and container have to be set up as above. After connecting into the container run from inside the container: export ROS_IP=192.168.0.105 source ~/melodic_ws/devel/setup.sh source ~/ros2_ws/install/setup.sh source ~/ros1_bridge_ws/install/setup.sh ros2 run ros1_bridge dynamic_bridge","title":"Start ROS Bridge"},{"location":"#buildingmaintaining-the-docs","text":"If you have installed the dependencies from requirements-dev.txt , generate the docs by running this command at project root: export PYTHONPATH=$PYTHONPATH:$(pwd)/modules git rm -rf docs rm -rf _build docs pydocmd build mkdir -p docs/resources/docs && cp resources/docs/*.png docs/resources/docs && cp resources/docs/*.gif docs/resources/docs git add docs/* # For inspection: python3 -m http.server --directory docs The structure and content of the docs are defined in the file pydocmd.yml .","title":"Building/maintaining the docs"},{"location":"config/","text":"ravestate.argparser handle_args handle_args(*args) -> Tuple[List[str], List[Tuple[str, str, Any]], List[str]] Runs an argument parser for the given args. Returns modules-to-load, config value-overrides and config file-pathes. Note: If the arguments are ill-formatted, or the -h argument is passed, help will be printed to the console and the program will abort. args : Argument list which will be fed into argparse.parse_args. Returns: A Tuple with three items: 1.) A list of module names which should be imported. 2.) A list of tuples, where each tuple is a module name, a config key name, and a value. 3.) A list of yaml file paths. ravestate.config Configuration Configuration(self, paths: List[str]) The Configuration class maintains a dictionary of key-value stores, which represent configuration entries for specific named modules. The key-value stores may be successively updated with consecutive yaml files, where each yaml document has the following content: module: module-name config: key-a: value-a key-b: value-b # etc add_conf Configuration.add_conf(self, mod: ravestate.module.Module) Register a set of allowed config entries for a specific module. Correctly typed values for allowed keys, that were previously parsed during construction from the yaml files, will be applied immediately. mod : A module object with a name and a conf dict. get_conf Configuration.get_conf(self, module_name: str) Retrieve updated config values for a module that was previously registered with add_conf. module_name : The module name for which configuration should be retrieved. Returns: A dictionary which contains exactly the keys that were contained in the module configuration dictionary during add_conf, or an empty dictionary if the module name is unknown. get Configuration.get(self, module_name: str, key: str) -> Any Get the current value of a config entry. module_name : The module that provides the config entry. key : A config key for the module that was previously added through add_conf. Returns: The current value, or None, if the entry does not exist. set Configuration.set(self, module_name: str, key: str, value: Any) Set the current value of a config entry. module_name : The module of the config entry. key : A config key for the module that was previously added through add_conf. value : The new value for the config entry. An error will be raised, if the type of the new value does not match the type of the old value. write Configuration.write(self, path: str) Write all current config entries to a yaml file. path : The file path to write. Will be overwritten! read Configuration.read(self, path: str) Loads all documents from a yaml file and tries to interpret them as configuration objects as described above. path : The yaml file path from which to load config documents.","title":"Configuration"},{"location":"context/","text":"ravestate.context create_and_run_context create_and_run_context(*args, runtime_overrides=None) Creates a new Context with the given parameters and runs it Context Context(self, *arguments, runtime_overrides: List[Tuple[str, str, Any]] = None) emit Context.emit(self, signal: ravestate.constraint.Signal, parents: Set[ravestate.spike.Spike] = None, wipe: bool = False, payload: Any = None) -> None Emit a signal to the signal processing loop. Note: The signal will only be processed if run() has been called! signal : The signal to be emitted. parents : The signal's parents, if it is supposed to be integrated into a causal group. wipe : Boolean to control, whether wipe (signal) should be called before the new spike is created. payload : Value that should be embedded in the new spike. wipe Context.wipe(self, signal: ravestate.constraint.Signal) Delete all spikes for the given signal. Partially fulfilled states that have acquired an affected spike will be forced to reject it. Wiping a parent spike will also wipe all child spikes. signal : The signal for which all existing spikes (and their children) should be invalidated and forgotten. run Context.run(self) -> None Creates a signal processing thread, starts it, and emits the core:startup signal. shutting_down Context.shutting_down(self) -> bool Retrieve the shutdown flag value, which indicates whether shutdown() has been called. shutdown Context.shutdown(self) -> None Sets the shutdown flag and waits for the signal processing thread to join. add_module Context.add_module(self, module_name: str) -> None Add a module by python module folder name, or by ravestate module name. module_name : The name of the module to be added. If it is the name of a python module that has not been imported yet, the python module will be imported, and any ravestate modules registered during the python import will also be added to this context. add_state Context.add_state(self, *, st: ravestate.state.State) -> None Add a state to this context. It will be indexed wrt/ the properties/signals it depends on. Error messages will be generated for unknown signals/properties. st : The state which should be added to this context. rm_state Context.rm_state(self, *, st: ravestate.state.State) -> None Remove a state from this context. Note, that any state which is constrained on the signal that is emitted by the deleted state will also be deleted. st : The state to remove. An error message will be generated, if the state was not previously added to this context with add_state(). add_prop Context.add_prop(self, *, prop: ravestate.property.Property) -> None Add a copy of a property to this context. An error message will be generated, if a property with the same name has already been added previously. Note: Context will adopt a copy of the given property, the actual property will not be changed. prop : The property object that should be added. rm_prop Context.rm_prop(self, *, prop: ravestate.property.Property) -> None Remove a property from this context. Generates error message, if the property was not added with add_prop() to the context previously prop : The property to remove.object conf Context.conf(self, *, mod: str, key: Union[str, NoneType] = None) -> Any Get a single config value, or all config values for a particular module. mod : The module whose configuration should be retrieved. key : A specific config key of the given module, if only a single config value should be retrieved. Returns: The value of a single config entry if key and module are both specified and valid, or a dictionary of config entries if only the module name is specified (and valid). signal_specificity Context.signal_specificity(self, sig: ravestate.constraint.Signal) -> float Called by state activation to determine it's constraint's specificity. sig : The signal whose specificity should be returned. Returns: The given signal's specificity. reacquire Context.reacquire(self, act: ravestate.iactivation.IActivation, sig: ravestate.constraint.Signal) Called by activation, to indicate, that it needs a new Spike for the specified signal, and should for this purpose be referenced by context. Note: Not thread-safe, sync must be guaranteed by caller. act : The activation that needs a new spike of the specified nature. sig : Signal type for which a new spike is needed. withdraw Context.withdraw(self, act: ravestate.iactivation.IActivation, sig: ravestate.constraint.Signal) Called by activation to make sure that it isn't referenced anymore as looking for the specified signal. This might be, because the activation chose to eliminate itself due to activation pressure, or because one of the activations conjunctions was fulfilled, so it is no longer looking for signals to fulfill the remaining conjunctions. Note: Not thread-safe, sync must be guaranteed by caller. act : The activation that has lost interest in the specified signal. sig : Signal type for which interest is lost. secs_to_ticks Context.secs_to_ticks(self, seconds: float) -> int Convert seconds to an equivalent integer number of ticks, given this context's tick rate. seconds : Seconds to convert to ticks. Returns: An integer tick count. possible_signals Context.possible_signals(self, state: ravestate.state.State) -> Generator[ravestate.constraint.Signal, NoneType, NoneType] Yields all signals, for which spikes may be created if the given state is executed. state : The state, which should be analyzed for it's possibly generated signals (declared signal + property-changed signals). run_once Context.run_once(self, seconds_passed=1.0, debug=False) -> None Run a single update for this context, which will ... (0) progress cooled down state weights. (1) reduce redundant candidate activations. (2) associate new spikes with state activations. (3) update state activations. (4) forget spikes which have no suitors in their causal groups. (5) age spikes. (6) invoke garbage collection. (7) update the core:activity and core:pressure variables. seconds_passed : Seconds, as floatiing point, since the last update. Will be used to determine the number of ticks to add/subtract to/from spike/activation age/cooldown/deathclock. test Context.test(self) -> bool Execute internal integrity checks. ravestate.spike Spike Spike(self, *, sig: str, parents: Set[ForwardRef('Spike')] = None, consumable_resources: Set[str] = None, payload: Any = None) This class encapsulates a single spike, to track ... ... it's consumption for different output properties (through CausalGroup ). ... it's offspring instances (causal group -> spikes caused by this spike) causal_group Spike.causal_group(self) -> ravestate.causal.CausalGroup Get this spike's causal group. Returns: This instances causal group. Should never be None. adopt Spike.adopt(self, child: 'Spike') -> None Called in spike constructor, for instances which claim to be caused by this spike. child : The child to add to this spike's causal group. wiped Spike.wiped(self, child: 'ISpike') -> None Called by an offspring signal, to notify the spike that it was wiped, and should therefore be removed from the children set. child : The child to be forgotten. wipe Spike.wipe(self, already_wiped_in_causal_group: bool = False) -> None Called either in Context run loop when the spike is found to be stale (with wiped_in_causal_group=True), or in Context.wipe(spike), or by parent (recursively). After this function is called, the spike should be cleaned up by GC. already_wiped_in_causal_group : Boolean which indicates, whether wiped(spike) must still be called on the group to make sure sure that no dangling references to the spike are maintained by any state activations. has_offspring Spike.has_offspring(self) Called by CausalGroup.stale(spike). Returns: True if the spike has active offspring, false otherwise. tick Spike.tick(self) -> None Increment this spike's age by 1. age Spike.age(self) -> int Obtain this spike's age (in ticks). offspring Spike.offspring(self) -> Generator[ForwardRef('Spike'), NoneType, NoneType] Recursively yields this spike's offspring and it's children's offspring. Returns: All of this spike's offspring spikes. is_wiped Spike.is_wiped(self) Check, whether this spike has been wiped, and should therefore not be acquired anymore. payload Spike.payload(self) -> Any Get payload for this spike. The payload is an arbitrary value passed in Context.emit() . ravestate.activation Activation Activation(self, st: ravestate.state.State, ctx: ravestate.icontext.IContext) Encapsulates the potential activation of a state. Tracks the collection of Spikes to fulfill of the state-defined activation constraints. resources Activation.resources(self) -> Set[str] Return's the set of the activation's write-access property names. specificity Activation.specificity(self) -> float Returns the lowest specificity among the specificity values of the activation's conjunct constraints. The specificity for a single conjunction is calculated as the sum of it's component signal's specificities, which in turn is calculated as one over the signal's subscriber count. dereference Activation.dereference(self, *, spike: Union[ravestate.iactivation.ISpike, NoneType] = None, reacquire: bool = False, reject: bool = False, pressured: bool = False) -> None Notify the activation, that a single or all spike(s) are not available anymore, and should therefore not be referenced anymore by the activation. This is called by ... ... context when a state is deleted. ... causal group, when a referenced signal was consumed for a required property. ... causal group, when a referenced signal was wiped. ... this activation (with reacquire=True and pressured=True), if it gives in to activation pressure. spike : The spike that should be forgotten by the activation, or none, if all referenced spikes should be forgotten. reacquire : Flag which tells the function, whether for every rejected spike, the activation should hook into context for reacquisition of a replacement spike. reject : Flag which controls, whether de-referenced spikes should be explicitely rejected through their causal groups. pressured : Flag which controls, whether de-referencing should only occur for spikes of causal groups in the pressuring_causal_groups set. acquire Activation.acquire(self, spike: ravestate.spike.Spike) -> bool Let the activation acquire a signal it is registered to be interested in. spike : The signal which should fulfill at least one of this activation's signal constraints. Returns: True if the spike was acquired by at least one signal, false if acquisition failed: This may happen for multiple reasons: (1) Acquisition failed, because the spike is too old (2) Acquisition failed, because the spike's causal group does not match it's completions. (3) Acquisition failed, because the spike's causal group does not offer all of this activation's state's write-props. secs_to_ticks Activation.secs_to_ticks(self, seconds: float) -> int Convert seconds to an equivalent integer number of ticks, given this activation's tick rate. seconds : Seconds to convert to ticks. Returns: An integer tick count. pressure Activation.pressure(self, give_me_up: ravestate.iactivation.ICausalGroup) Called by CausalGroup, to pressure the activation to make a decision on whether it is going to retain a reference to the given spike, given that there is a lower- specificity activation which is ready to run. give_me_up : Causal group that wishes to be de-referenced by this activation. is_pressured Activation.is_pressured(self) Called by context, to figure out whether the activation is pressured, and therefore the idle:bored signal should be emitted. spiky Activation.spiky(self) -> bool Returns true, if the activation has acquired any spikes at all. Returns: True, if any of this activation's constraint's signal is referencing a spike. spikes Activation.spikes(self) -> Generator[ravestate.spike.Spike, NoneType, NoneType] Returns iterable for all the spikes currently acquired by the activation. possible_signals Activation.possible_signals(self) -> Generator[ForwardRef('Signal'), NoneType, NoneType] Yields all signals, for which spikes may be created if this activation's state is executed. effect_not_caused Activation.effect_not_caused(self, group: ravestate.iactivation.ICausalGroup, effect: str) -> None Notify the activation, that a follow-up signal will not be produced by the given causal group. The activation will go through it's constraint, and reject all completion spikes for signals of name effect , if the completion spikes are from the given causal group. group : The causal group which will not contain a spike for signal effect . effect : Name of the signal for which no spike will be produced. update Activation.update(self) -> bool Called once per tick on this activation, to give it a chance to activate itself, or auto-eliminate, or reject spikes which have become too old. Returns: True, if the target state is activated and teh activation be forgotten, false if needs further attention in the form of updates() by context in the future. ravestate.causal CausalGroup CausalGroup(self, resources: Set[str]) Class which represents a causal group graph of spike parent/offspring spikes (a \"superspike\"). These must synchronize wrt/ their (un)written properties and state activation candidates, such that they don't cause output races. Note: Always use a with ... construct to interact with a causal group. Otherwise, undefined behavior may occur due to race conditions. merge CausalGroup.merge(self, other: 'CausalGroup') Merge this causal group with another. Unwritten props will become the set intersection of this group's unwritten props and other's unwritten props. consumed() will be called with all properties that are consumed by other, but not this. Afterwards, other's member objects will be set to this's. acquired CausalGroup.acquired(self, spike: 'ISpike', acquired_by: ravestate.iactivation.IActivation, detached: bool) -> bool Called by Activation to notify the causal group, that it is being referenced by an activation constraint for a certain member spike. spike : State activation instance, which is now being referenced by the specified causal group. acquired_by : State activation instance, which is interested in this property. detached : Tells the causal group, whether the reference is detached, and should therefore receive special treatment. Returns: Returns True if all of the acquiring's write-props are free, and the group now refs. the activation, False otherwise. rejected CausalGroup.rejected(self, spike: 'ISpike', rejected_by: ravestate.iactivation.IActivation, reason: int) -> None Called by a state activation, to notify the group that a member spike is no longer being referenced for the given state's write props. This may be because ... ... the activation's dereference function was called. (reason=0) ... the spike got too old. (reason=1) ... the activation happened and is dereferencing it's spikes. (reason=2) spike : The member spike whose ref-set should be reduced. rejected_by : State activation instance, which is no longer interested in this property. reason : See about. consent CausalGroup.consent(self, ready_suitor: ravestate.iactivation.IActivation) -> bool Called by constraint, to inquire whether this causal group would happily be consumed for the given state activation's properties. This will be called periodically on the group by state activations that are ready to go. Therefore, a False return value from this function is never a final judgement (more like a \"maybe later\"). ready_suitor : The state activation which would like to consume this instance for it's write props. Returns: True if this instance agrees to proceeding with the given consumer for the consumer's write props, False otherwise. activated CausalGroup.activated(self, act: ravestate.iactivation.IActivation) Called by activation which previously received a go-ahead from consent(), when it is truly proceeding with running (after it got the go-ahead from all it's depended=on causal groups). act : The activation that is now running. resigned CausalGroup.resigned(self, act: ravestate.iactivation.IActivation) -> None Called by activation, to let the causal group know that it failed, and a less specific activation may now be considered for the resigned state's write props. act : The activation that is unexpectedly not consuming it's resources, because it's state resigned/failed. consumed CausalGroup.consumed(self, resources: Set[str]) -> None Called by activation to notify the group, that it has been consumed for the given set of properties. resources : The properties which have been consumed. wiped CausalGroup.wiped(self, spike: 'ISpike') -> None Called by a spike, to notify the causal group that the instance was wiped and should no longer be remembered. spike : The instance that should be henceforth forgotten. stale CausalGroup.stale(self, spike: 'ISpike') -> bool Determine, whether a spike is stale (has no remaining interested activations and no children). Returns: True, if no activations reference the given spike for any unwritten property. False otherwise. check_reference_sanity CausalGroup.check_reference_sanity(self) -> bool Make sure, that the refcount-per-act-per-spike-per-resource value sum is equal to the number of spikes from this causal group acquired per activation for each activation in the index. :return: True if the criterion is fulfilled, False otherwise.","title":"Context"},{"location":"modules/","text":"ravestate.module Module Module(self, *, name: str, config: Dict[str, Any] = None) Atomic class, which encapsulates a named set of states, properties and config entries, which form a coherent bundle. Example: with Module(name=\"my_module\", config={\"paramA\": 42}): # define properties # define states registered_modules dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2) import_module import_module(*, module_name: str, callback) Called by context to import a particular ravestate python module. module_name : The name of the python module to be imported (must be in pythonpath). callback : A callback which should be called when a module calls register() while it is being imported. has_module has_module(module_name: str) Check whether a module with a particular name has been registered. module_name : The name which should be checked for beign registered. Returns: True if a module with the given name has been registered, false otherwise. get_module get_module(module_name: str) Get a registered module with a particular name module_name : The name of the moduke which should be retrieved. Returns: The module with the given name if it was registered, false if otherwise.","title":"Modules"},{"location":"properties/","text":"ravestate.property Property Property(self, *, name='', allow_read=True, allow_write=True, allow_push=True, allow_pop=True, default_value=None, always_signal_changed=False, is_flag_property=False, wipe_on_changed=True) Base class for context properties. Controls read/write/push/pop/delete permissions, property name basic impls. for the property value, parent/child mechanism. Example (Creating a module containing a property named my_property): with Module(name=\"my_module\"): my_property = Property(name=\"my_property\") set_parent_path Property.set_parent_path(self, path) Set the ancestors (including modulename) for a property path : ancestry in the form of modulename:parent_prop_name (or simply modulename) gather_children Property.gather_children(self) -> List[ForwardRef('Property')] Collect this property, and all of it's children. read Property.read(self) Read the current property value write Property.write(self, value) Write a new value to the property value : The new value. Returns: True if the value has changed and :changed should be signaled, false otherwise. push Property.push(self, child: 'Property') Add a child to the property child : The child object Returns: True if the child was added successfully, false otherwise. pop Property.pop(self, child_name: str) Remove a child from the property by it's name. child_name : Name of the child to be removed. Returns: True if the pop was successful, False otherwise changed Property.changed(self) -> ravestate.constraint.Signal Signal that is emitted by PropertyWrapper when write() returns True. pushed Property.pushed(self) -> ravestate.constraint.Signal Signal that is emitted by PropertyWrapper when push() returns True. popped Property.popped(self) -> ravestate.constraint.Signal Signal that is emitted by PropertyWrapper when pop() returns True. true Property.true(self) -> ravestate.constraint.Signal Signal that is emitted by PropertyWrapper when it is a flag-property and self.value is set to True. false Property.false(self) -> ravestate.constraint.Signal Signal that is emitted by PropertyWrapper when it is a flag-property and self.value is set to False. signals Property.signals(self) -> Generator[ravestate.constraint.Signal, NoneType, NoneType] Yields all signals that may be emitted because of this property, given it's write/push/pop permissions.","title":"Properties"},{"location":"states/","text":"state state(*, signal: Union[ravestate.constraint.Signal, NoneType] = '', write: Tuple[ravestate.property.Property] = (), read: Tuple[ravestate.property.Property] = (), cond: ravestate.constraint.Constraint = None, emit_detached: bool = False, weight: float = 1.0, cooldown: float = 0.0) Decorator to declare a new state, which may emit a certain signal, write to a certain set of properties (calling write, push, pop), and read from certain properties (calling read). Example (Module that outputs \"Don't Panic\" after startup): with Module(name=\"my_module\"): @state(cond=startup()) def after_startup(context, write=OUTPUT_PROPERTY): context[OUTPUT_PROPERTY] = \"Don't Panic\" ravestate.constraint ConfigurableAge ConfigurableAge(self, key: str) Class for having min/max_age parameters for Constraints configurable with a config key key str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object. str () (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'. Constraint Constraint(self, /, *args, **kwargs) Superclass for Signal, Conjunct and Disjunct Signal Signal(self, name: str, *, min_age=0.0, max_age=5.0, detached=False, _skip_module_context=False) Class that represents a Signal. Should be constructed in a with Module(..) context, such that it's module scope is set automatically. SignalRef SignalRef(self, name: str, *, min_age=0.0, max_age=5.0, detached=False) Signal reference. Almost the same as a signal, except that it will not try to auto-discover it's module out of thread-local context (module_name will stay unfilled). Needed, because sometimes you need to reference a singal within a module scope without assigning that signal to the contextual module. Conjunct Conjunct(self, *args) Class that represents a Conjunction of Signals. Can be constructed using an overloaded & operator. Example: signal_A & signal_B Disjunct Disjunct(self, *args) Class that represents a Disjunction of Conjunctions Can be constructed using an overloaded | operator. Examples: conjunction_A | conjunction_B (signal_A & signal_B) | (signal_C & signal_D) receptor receptor(*, ctx_wrap: Union[ravestate.wrappers.ContextWrapper, ravestate.context.Context], write: Union[str, ravestate.property.Property, Tuple[Union[str, ravestate.property.Property]]]) A receptor is a special state which can be invoked from outside, to push values into the context. ctx_wrap : A context wrapper as is always given into the state functions as their first argument. write : The property, or tuple of properties, which are going to be written. Example: # Module that outputs \"Don't Panic\" one minute after startup # Because a receptor is used, the OUTPUT_PROPERTY is not blocked the whole time with Module(name=\"my_module\"): @state(cond=startup()) def after_startup(context): @receptor(ctx_wrap=context, write=OUTPUT_PROPERTY) def dont_panic(ctx_write): ctx_write[OUTPUT_PROPERTY] = \"Don't Panic\" sleep(60) dont_panic()","title":"States"},{"location":"modules/ravestate_akinator/","text":"_ _ | | (_) _ _____| | _ _ ____ _____ _| |_ ___ ____ (____ | |_/ ) | _ \\(____ (_ _) _ \\ / ___) / ___ | _ (| | | | / ___ | | || |_| | | \\_____|_| \\_)_|_| |_\\_____| \\__)___/|_| 20 Questions with Akinator The Ravestate Akinator module offers an implementation of the 20 questions game. It consists of a wrapper for the API calls to the Akinator web game and several states to handle the game flow with the interlocutor. Architecture This is an overview of the dialog flow for the 20 questions game. Starting the Game There are two possibilities to trigger the game: 1. Interlocutor input: \"I want to play\", \"I like games\" or something similar 2. Automatically through Ravestate: When the system gets bored (no dialog states are active but an interlocutor is present) then Akinator is one of the possible modules that will be triggered. The API Wrapper api.py handles the requests to the online Akinator game. There are three different types of get requests to handle the game: 1. Question asking phase: post the answer to the previous question and retrieve the next question 2. Guessing phase: retrieve the guess 3. Game finishing phase: give Akinator feedback on his guess","title":"Akinator"},{"location":"modules/ravestate_akinator/#20-questions-with-akinator","text":"The Ravestate Akinator module offers an implementation of the 20 questions game. It consists of a wrapper for the API calls to the Akinator web game and several states to handle the game flow with the interlocutor.","title":"20 Questions with Akinator"},{"location":"modules/ravestate_akinator/#architecture","text":"This is an overview of the dialog flow for the 20 questions game.","title":"Architecture"},{"location":"modules/ravestate_akinator/#starting-the-game","text":"There are two possibilities to trigger the game: 1. Interlocutor input: \"I want to play\", \"I like games\" or something similar 2. Automatically through Ravestate: When the system gets bored (no dialog states are active but an interlocutor is present) then Akinator is one of the possible modules that will be triggered.","title":"Starting the Game"},{"location":"modules/ravestate_akinator/#the-api-wrapper","text":"api.py handles the requests to the online Akinator game. There are three different types of get requests to handle the game: 1. Question asking phase: post the answer to the previous question and retrieve the next question 2. Guessing phase: retrieve the guess 3. Game finishing phase: give Akinator feedback on his guess","title":"The API Wrapper"},{"location":"modules/ravestate_nlp/","text":"_ | | ____ | | ____ | _ \\| || _ \\ | | | | || |_| | |_| |_|\\_) __/ |_| NLP The natural language processing (NLP) module enables Roboy to process and understand the human language. That way he can interpret the meaning of the detected sentences. We use a free, open-source NLP library for advanced NLP in Python: spaCy Extracted Features Feature Ravestate Properties/Signals Description Example Sentence: 'Revolutions need six fancy chickens!' Tokenization nlp.prop_tokens Segmenting text into words, punctuation marks etc. 'Revolutions', 'need', 'six', 'fancy', 'chickens', '!' Part-of-Speech (POS) Tagging nlp.prop_postags Assigning word types to tokens 'NOUN', 'VERB', 'NUM', 'ADJ', 'NOUN', 'PUNCT' Detailed POS Tag nlp.prop_tags Fine-grained part-of-speech 'Revolutions' has the tag: 'NNS', which stand for: noun, plural 'need' has the tag: 'VBP', which stands for: verb, non-3rd person singular present List of POS tags Lemmatization nlp.prop_lemmas Assigning the base forms of words 'Revolutions' has the lemma: 'revolution' 'was' would have the lemma: 'be' Named Entity Recognition (NER) nlp.prop_ner Labelling \"real-world\" objects (\"NE\"=Named Entity) 'six' has the NE: 'CARDINAL', which are numerals that do not fall under another type List of NEs Triple Extraction nlp.prop_triples A triple consists of subject, predicate, object of sentence Triple: subject: 'Revolutions', predicate: 'need', object: 'chickens' About Roboy nlp.prop_roboy Detecting whether sentence is about Roboy 'you', 'roboy', 'robot', 'roboboy', ... Yes-No nlp.prop_yesno Detecting answers to yes-no questions Checking for 'yes', 'no', 'i don't know', 'probably', 'probably not' and synonyms of these Sentence Type: Question nlp.sig_is_question Emitted if the input sentence is a question Play Game nlp.sig_intent_play Emitted when the interlocutor wants to play a game input: \"I want to play\", \"I like games\" or something similar Additional features can be added and published to the system. All existing features can be found here . Using the Features React to Property Change Each feature is stored in a ravestate property. A state which wants to access a property needs read permissions for that property. Example: State that reads the \"yesno\" property import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio @rs.state( cond=nlp.prop_yesno.changed(), # state reacts to a change in the 'yesno' property read=nlp.prop_yesno, # state is allowed to read the 'yesno' property write=rawio.prop_out) # state is allowed to write to the output property def postive_chicken(ctx: ContextWrapper): if ctx[nlp.prop_yesno] == \"yes\": ctx[rawio.prop_out] = \"You seem to be a positive chicken!\" React to Signals For 'Sentence Type: Question' a signal is emitted. Example: State that reacts to the \"is-question\" signal import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio @rs.state( cond=nlp.sig_is_question, # state reacts to the is-question signal write=rawio.prop_out) # state is allowed to write to the output property def curious_chicken(ctx: ContextWrapper): ctx[nlp.prop_out] = \"You seem to ask a lot of questions, chicken!\" Using the Triples for Sentence Analysis The triple extraction is done in extract_triples.py by using the dependency tree of the sentence. A dependency tree shows the relation between the words of a sentence. The finite verb (predicate) is the structural center of the sentence and therefor of the tree. So starting with the predicate the algorithm searches through the dependency tree to find subject and object. Analyzing a Sentence Example: 'Chickens like revolutions' reaction state Triple: subject: 'Chickens', predicate: 'like', object: 'revolutions' import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio @rs.state( cond=nlp.prop_triples.changed(), # state reacts to a change in the 'triples' property read=nlp.prop_triples, # state is allowed to read the 'triples' property write=rawio.prop_out) # state is allowed to write to the output chanel def postive_chicken(ctx: ContextWrapper): triple = ctx[nlp.prop_triples][0] # gives you the first Triple object # check predicate and object correspondingly # match_either_lemma() is a method in the Triple class if triple.match_either_lemma(subj={\"chicken\", \"dinosaur\"}): # returns true when subj, pred or obj have the desired value ctx[rawio.prop_out] = \"You said something about a chicken, i like chickens!\" Happy language processing to all chickens out there!","title":"NLP"},{"location":"modules/ravestate_nlp/#nlp","text":"The natural language processing (NLP) module enables Roboy to process and understand the human language. That way he can interpret the meaning of the detected sentences. We use a free, open-source NLP library for advanced NLP in Python: spaCy","title":"NLP"},{"location":"modules/ravestate_nlp/#extracted-features","text":"Feature Ravestate Properties/Signals Description Example Sentence: 'Revolutions need six fancy chickens!' Tokenization nlp.prop_tokens Segmenting text into words, punctuation marks etc. 'Revolutions', 'need', 'six', 'fancy', 'chickens', '!' Part-of-Speech (POS) Tagging nlp.prop_postags Assigning word types to tokens 'NOUN', 'VERB', 'NUM', 'ADJ', 'NOUN', 'PUNCT' Detailed POS Tag nlp.prop_tags Fine-grained part-of-speech 'Revolutions' has the tag: 'NNS', which stand for: noun, plural 'need' has the tag: 'VBP', which stands for: verb, non-3rd person singular present List of POS tags Lemmatization nlp.prop_lemmas Assigning the base forms of words 'Revolutions' has the lemma: 'revolution' 'was' would have the lemma: 'be' Named Entity Recognition (NER) nlp.prop_ner Labelling \"real-world\" objects (\"NE\"=Named Entity) 'six' has the NE: 'CARDINAL', which are numerals that do not fall under another type List of NEs Triple Extraction nlp.prop_triples A triple consists of subject, predicate, object of sentence Triple: subject: 'Revolutions', predicate: 'need', object: 'chickens' About Roboy nlp.prop_roboy Detecting whether sentence is about Roboy 'you', 'roboy', 'robot', 'roboboy', ... Yes-No nlp.prop_yesno Detecting answers to yes-no questions Checking for 'yes', 'no', 'i don't know', 'probably', 'probably not' and synonyms of these Sentence Type: Question nlp.sig_is_question Emitted if the input sentence is a question Play Game nlp.sig_intent_play Emitted when the interlocutor wants to play a game input: \"I want to play\", \"I like games\" or something similar Additional features can be added and published to the system. All existing features can be found here .","title":"Extracted Features"},{"location":"modules/ravestate_nlp/#using-the-features","text":"","title":"Using the Features"},{"location":"modules/ravestate_nlp/#react-to-property-change","text":"Each feature is stored in a ravestate property. A state which wants to access a property needs read permissions for that property. Example: State that reads the \"yesno\" property import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio @rs.state( cond=nlp.prop_yesno.changed(), # state reacts to a change in the 'yesno' property read=nlp.prop_yesno, # state is allowed to read the 'yesno' property write=rawio.prop_out) # state is allowed to write to the output property def postive_chicken(ctx: ContextWrapper): if ctx[nlp.prop_yesno] == \"yes\": ctx[rawio.prop_out] = \"You seem to be a positive chicken!\"","title":"React to Property Change"},{"location":"modules/ravestate_nlp/#react-to-signals","text":"For 'Sentence Type: Question' a signal is emitted. Example: State that reacts to the \"is-question\" signal import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio @rs.state( cond=nlp.sig_is_question, # state reacts to the is-question signal write=rawio.prop_out) # state is allowed to write to the output property def curious_chicken(ctx: ContextWrapper): ctx[nlp.prop_out] = \"You seem to ask a lot of questions, chicken!\"","title":"React to Signals"},{"location":"modules/ravestate_nlp/#using-the-triples-for-sentence-analysis","text":"The triple extraction is done in extract_triples.py by using the dependency tree of the sentence. A dependency tree shows the relation between the words of a sentence. The finite verb (predicate) is the structural center of the sentence and therefor of the tree. So starting with the predicate the algorithm searches through the dependency tree to find subject and object.","title":"Using the Triples for Sentence Analysis"},{"location":"modules/ravestate_nlp/#analyzing-a-sentence","text":"Example: 'Chickens like revolutions' reaction state Triple: subject: 'Chickens', predicate: 'like', object: 'revolutions' import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio @rs.state( cond=nlp.prop_triples.changed(), # state reacts to a change in the 'triples' property read=nlp.prop_triples, # state is allowed to read the 'triples' property write=rawio.prop_out) # state is allowed to write to the output chanel def postive_chicken(ctx: ContextWrapper): triple = ctx[nlp.prop_triples][0] # gives you the first Triple object # check predicate and object correspondingly # match_either_lemma() is a method in the Triple class if triple.match_either_lemma(subj={\"chicken\", \"dinosaur\"}): # returns true when subj, pred or obj have the desired value ctx[rawio.prop_out] = \"You said something about a chicken, i like chickens!\"","title":"Analyzing a Sentence"},{"location":"modules/ravestate_nlp/#happy-language-processing-to-all-chickens-out-there","text":"","title":"Happy language processing to all chickens out there!"},{"location":"modules/ravestate_telegramio/","text":"_______ _ _____ ____ |__ __| | | |_ _/ __ \\ | | ___| | ___ __ _ _ __ __ _ _ __ ___ | || | | | | |/ _ \\ |/ _ \\/ _` | '__/ _` | '_ ` _ \\ | || | | | | | __/ | __/ (_| | | | (_| | | | | | |_| || |__| | |_|\\___|_|\\___|\\__, |_| \\__,_|_| |_| |_|_____\\____/ __/ | |___/ TelegramIO The TelegramIO module enables ravestate to connect to a Telegram-Bot and chat to people there. The connection to Telegram is managed with python-telegram-bot Architecture There are two main modes for this module: * Single-Process-Mode: All chats share the same context * Multiprocess-Mode: Every chat creates its own context in a separate process Single-Process-Mode In this mode the module handles incoming text messages and pictures from all chats. Outgoing messages are sent to every currently active chat. Multiprocess-Mode In this mode the \"Master\" part of the module is running in the main process of ravestate. Whenever there is a new chat, a new instance of ravestate is started in a new process and a bidirectional pipe is set up to enable communication between the main process and the new child process. Only the main process is connected to the Telegram-Bot and therefore any incoming messages get forwarded to the corresponding child process via the pipe. The main process also forwards any incoming messages it receives through the pipe to the corresponding telegram chat. In order to clean up unused child processes, the main process kills child processes after a configurable amount of inactivity. Child processes running the TelegramIO-Module listen for incoming text messages or pictures on the pipe and write output to the pipe. They only exchange messages with a single chat (indirectly via the pipe). Abilities The TelegramIO module is able to handle incoming text messages as well as incoming pictures. The module creates an interlocutor node for every user it is chatting with, containing the user's telegram-id, username and full name if it is set by the user. Incoming text messages are simply written into the RawIO Input property. For incoming pictures, the picture is saved locally as a file and the filepath is written to the RawIO Pic_In Context property. Messages in the RawIO Output are sent to the Telegram Chat(s). Configuration There are 5 configurable parameters (see init .py ): * telegram-token : Put the Token of your Telegram-Bot here * all_in_one_context : True if Single-Process-Mode should be used, False if Multiprocess-Mode should be used. * child_conn : Not to be set in a config file or via the command line. Will be set by master process as a runtime_override. * child_config_files : If in Multiprocess-Mode, the config-paths listed here will be used when creating a new context for a new chat. * chat_lifetime : The timespan in minutes in which a chat will be kept active after the last message","title":"TelegramIO"},{"location":"modules/ravestate_telegramio/#telegramio","text":"The TelegramIO module enables ravestate to connect to a Telegram-Bot and chat to people there. The connection to Telegram is managed with python-telegram-bot","title":"TelegramIO"},{"location":"modules/ravestate_telegramio/#architecture","text":"There are two main modes for this module: * Single-Process-Mode: All chats share the same context * Multiprocess-Mode: Every chat creates its own context in a separate process","title":"Architecture"},{"location":"modules/ravestate_telegramio/#single-process-mode","text":"In this mode the module handles incoming text messages and pictures from all chats. Outgoing messages are sent to every currently active chat.","title":"Single-Process-Mode"},{"location":"modules/ravestate_telegramio/#multiprocess-mode","text":"In this mode the \"Master\" part of the module is running in the main process of ravestate. Whenever there is a new chat, a new instance of ravestate is started in a new process and a bidirectional pipe is set up to enable communication between the main process and the new child process. Only the main process is connected to the Telegram-Bot and therefore any incoming messages get forwarded to the corresponding child process via the pipe. The main process also forwards any incoming messages it receives through the pipe to the corresponding telegram chat. In order to clean up unused child processes, the main process kills child processes after a configurable amount of inactivity. Child processes running the TelegramIO-Module listen for incoming text messages or pictures on the pipe and write output to the pipe. They only exchange messages with a single chat (indirectly via the pipe).","title":"Multiprocess-Mode"},{"location":"modules/ravestate_telegramio/#abilities","text":"The TelegramIO module is able to handle incoming text messages as well as incoming pictures. The module creates an interlocutor node for every user it is chatting with, containing the user's telegram-id, username and full name if it is set by the user. Incoming text messages are simply written into the RawIO Input property. For incoming pictures, the picture is saved locally as a file and the filepath is written to the RawIO Pic_In Context property. Messages in the RawIO Output are sent to the Telegram Chat(s).","title":"Abilities"},{"location":"modules/ravestate_telegramio/#configuration","text":"There are 5 configurable parameters (see init .py ): * telegram-token : Put the Token of your Telegram-Bot here * all_in_one_context : True if Single-Process-Mode should be used, False if Multiprocess-Mode should be used. * child_conn : Not to be set in a config file or via the command line. Will be set by master process as a runtime_override. * child_config_files : If in Multiprocess-Mode, the config-paths listed here will be used when creating a new context for a new chat. * chat_lifetime : The timespan in minutes in which a chat will be kept active after the last message","title":"Configuration"},{"location":"modules/ravestate_verbaliser/","text":"_ _ _ | | | |(_) _ _ _____ ____| |__ _____| | _ ___ _____ ____ | | | | ___ |/ ___) _ \\(____ | || |/___) ___ |/ ___) \\ V /| ____| | | |_) ) ___ | || |___ | ____| | \\_/ |_____)_| |____/\\_____|\\_)_(___/|_____)_| Verbaliser The Verbaliser produces Roboy's utterances. It diversifies the interactions with Roboy by randomizing the output given a specific intent. Using the Verbaliser Question-Answer Lists YAML files are used to define the actual utterances. In other words: they store everything Roboy can vocalise. To diversify his remarks the Verbaliser randomises similar outputs. The class QAPhrases retrieves the values from a YAML file and parses the containing phrases. Here is a template for such a YAML file: type: qa # remark types: question answering (qa), phrases name: \"INTENT\" # intent or topic Q: # possible questions - \"Question phrasing 1\" - \"Question phrasing 2\" - \"Question phrasing 3\" A: # answers SUCCESS: - \"Possible answer on success 1\" - \"Possible answer on success 2\" FAILURE: - \"Possible answer on failure\" FUP: # follow up questions (for interlocutors that are already known to Roboy) Q: - \"Possible follow up question\" A: - \"Possible follow up answer\" See more examples here . Example for Answering the Question: What happened to the Dinosaurs? Creating the YAML file: Fill in all the possible answers. type: qa name: \"DINO\" A: SUCCESS: - \"I am sure it was a mind-boggingly huge meteorite!\" - \"They smoked too much ash!\" - \"A vulcano had flatulences.\" - \"The chicken were stronger.\" FAILURE: - \"I have no idea what you just said.\" - \"Sorry, I am only interested in dinosaurs.\" Adding this file to the Verbaliser: In this case the file is going to be located in a folder of important facts. However, the single file can similarly be added by itself. The folder is in the same path as the python file adding it. from ravestate_verbaliser import verbaliser from os.path import realpath, dirname, join verbaliser.add_folder(join(dirname(realpath(__file__)), \"important_facts_folder\")) Using the Verbaliser for fancy outputs: This outputs an answer to the question. To understand how to analyse the context of the question have a look at the Natural Language Processing README if input_had_something_to_do_with_dinos and was_a_question: ctx[\"rawio:out\"] = verbaliser.get_random_successful_answer(\"DINO\") else: ctx[\"rawio:out\"] = verbaliser.get_random_failure_answer(\"DINO\") Possible conversation flow: Interlocutor: \"What happend to the Dinosaurs?\" Roboy: \"The chicken were stronger.\" Example for Extracting Phrase Lists The Verbaliser can also be used to get all the imported phrases for a specific intent as a list. Creating the phrases.yml: type: phrases name: \"dino\" opts: - \"Dinos can not scratch their backs.\" - \"Once upon a time these mind-bogglingly huge creatures wandered the earth.\" - \"The longest Dinosaur was the Argentiosaurus.\" --- type: phrases name: \"chicken\" opts: - \" Chickens are not completely flightless.\" - \" There are more chickens out there than programmers.\" - \" If I were a chicken for one day I would say: 'Puk Puk Pukaaak'. Adding the file to the Verbaliser: The YAML file is assumed to be located in the important_phrases folder. The folder is again in the same path as this python script: from ravestate_verbaliser import verbaliser from os.path import realpath, dirname, join verbaliser.add_file(join(dirname(realpath(__file__)), \"important_phrases\", \"phrases.yml\")) Using the Verbaliser to get a list of phrases: Given a specific intent the Verbaliser can be used to return a list of phrases. import ravestate_verbaliser dino_list = ravestate_verbaliser.verbaliser.get_phrase_list('dino') The verbaliser:intent Property The verbaliser:react_to_intent state produces a random phrase output for a given intent. All the possible intents are specified in YAML files that are located in the ravestate_phrases_basic_en folder . The state reads the verbaliser:intent property and outputs one random phrase in the list with that specific intent. It can therefor be triggered as follows: Let's assume that phrases.yml is now located in avestate_phrases_basic_en. @state(cond=s(\"triggered_by_some_signal\"), write=\"verbaliser:intent\") def say_some_nice_chicken_suff(ctx: ContextWrapper): ctx[\"verbaliser:intent\"] = \"chicken\"","title":"Verbaliser"},{"location":"modules/ravestate_verbaliser/#verbaliser","text":"The Verbaliser produces Roboy's utterances. It diversifies the interactions with Roboy by randomizing the output given a specific intent.","title":"Verbaliser"},{"location":"modules/ravestate_verbaliser/#using-the-verbaliser","text":"","title":"Using the Verbaliser"},{"location":"modules/ravestate_verbaliser/#question-answer-lists","text":"YAML files are used to define the actual utterances. In other words: they store everything Roboy can vocalise. To diversify his remarks the Verbaliser randomises similar outputs. The class QAPhrases retrieves the values from a YAML file and parses the containing phrases. Here is a template for such a YAML file: type: qa # remark types: question answering (qa), phrases name: \"INTENT\" # intent or topic Q: # possible questions - \"Question phrasing 1\" - \"Question phrasing 2\" - \"Question phrasing 3\" A: # answers SUCCESS: - \"Possible answer on success 1\" - \"Possible answer on success 2\" FAILURE: - \"Possible answer on failure\" FUP: # follow up questions (for interlocutors that are already known to Roboy) Q: - \"Possible follow up question\" A: - \"Possible follow up answer\" See more examples here .","title":"Question-Answer Lists"},{"location":"modules/ravestate_verbaliser/#example-for-answering-the-question-what-happened-to-the-dinosaurs","text":"Creating the YAML file: Fill in all the possible answers. type: qa name: \"DINO\" A: SUCCESS: - \"I am sure it was a mind-boggingly huge meteorite!\" - \"They smoked too much ash!\" - \"A vulcano had flatulences.\" - \"The chicken were stronger.\" FAILURE: - \"I have no idea what you just said.\" - \"Sorry, I am only interested in dinosaurs.\" Adding this file to the Verbaliser: In this case the file is going to be located in a folder of important facts. However, the single file can similarly be added by itself. The folder is in the same path as the python file adding it. from ravestate_verbaliser import verbaliser from os.path import realpath, dirname, join verbaliser.add_folder(join(dirname(realpath(__file__)), \"important_facts_folder\")) Using the Verbaliser for fancy outputs: This outputs an answer to the question. To understand how to analyse the context of the question have a look at the Natural Language Processing README if input_had_something_to_do_with_dinos and was_a_question: ctx[\"rawio:out\"] = verbaliser.get_random_successful_answer(\"DINO\") else: ctx[\"rawio:out\"] = verbaliser.get_random_failure_answer(\"DINO\") Possible conversation flow: Interlocutor: \"What happend to the Dinosaurs?\" Roboy: \"The chicken were stronger.\"","title":"Example for Answering the Question: What happened to the Dinosaurs?"},{"location":"modules/ravestate_verbaliser/#example-for-extracting-phrase-lists","text":"The Verbaliser can also be used to get all the imported phrases for a specific intent as a list. Creating the phrases.yml: type: phrases name: \"dino\" opts: - \"Dinos can not scratch their backs.\" - \"Once upon a time these mind-bogglingly huge creatures wandered the earth.\" - \"The longest Dinosaur was the Argentiosaurus.\" --- type: phrases name: \"chicken\" opts: - \" Chickens are not completely flightless.\" - \" There are more chickens out there than programmers.\" - \" If I were a chicken for one day I would say: 'Puk Puk Pukaaak'. Adding the file to the Verbaliser: The YAML file is assumed to be located in the important_phrases folder. The folder is again in the same path as this python script: from ravestate_verbaliser import verbaliser from os.path import realpath, dirname, join verbaliser.add_file(join(dirname(realpath(__file__)), \"important_phrases\", \"phrases.yml\")) Using the Verbaliser to get a list of phrases: Given a specific intent the Verbaliser can be used to return a list of phrases. import ravestate_verbaliser dino_list = ravestate_verbaliser.verbaliser.get_phrase_list('dino')","title":"Example for Extracting Phrase Lists"},{"location":"modules/ravestate_verbaliser/#the-verbaliserintent-property","text":"The verbaliser:react_to_intent state produces a random phrase output for a given intent. All the possible intents are specified in YAML files that are located in the ravestate_phrases_basic_en folder . The state reads the verbaliser:intent property and outputs one random phrase in the list with that specific intent. It can therefor be triggered as follows: Let's assume that phrases.yml is now located in avestate_phrases_basic_en. @state(cond=s(\"triggered_by_some_signal\"), write=\"verbaliser:intent\") def say_some_nice_chicken_suff(ctx: ContextWrapper): ctx[\"verbaliser:intent\"] = \"chicken\"","title":"The verbaliser:intent Property"}]}
\ No newline at end of file
+{"config":{"lang":["en"],"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"About ____ __ __ _____ _____ / _ \\ ____ __ ______ ______ / / _____ / / ___ / _ _ \\ / _ _ \\ / / _ / / __ \\ / / / / __ \\ / ___ \\ , / __ \\ , / __ \\ 0 > 0 > < 0 < 0 / , \\ , / / _ / / \\ \\ / / / _ / / \\ __ , / / / _ / / / / _ / / \\ __ \u22bd __ / \\ __ \u22bd __ / \\ / \\ / \\ __ / \\ / \\ __ / , ___ / \\ ____ / \\ / \\ __ / \\ / \\ / , ___ / \u22c2 - Hey ! \u22c2 \\ ____ / \\ ____ / Ol\u00e0 ! - Ravestate is a reactive library for real-time natural language dialog systems. It combines elements from event-based and reactive programming into an API, where application states are defined as functions that are run when a certain boolean set of criteria (signals) in the current application context is satisfied. It is the first reactive API to allow for boolean combinations of events. You may find a short introductory video here . Reactive Hello World import ravestate as rs # We want to write some text output, so we # need the raw:out context property from ravestate_rawio. import ravestate_rawio as rawio # Make sure that we use some i/o implementation, # so we can actually see stuff that is written to rawio:out. import ravestate_conio # Ravestate applications should always be wrapped in a Module. # This allows easier scoping, and enables separation of concerns # beyond states. with rs . Module ( name = \"hi!\" , depends = ( rawio . mod ,)): # Create an application state which reacts to the `:startup` signal, # and writes a string to raw:out. Note: State functions are # always run asynchronously! @rs.state ( cond = rs . sig_startup , write = rawio . prop_out ) def hello_world ( context ): context [ rawio . prop_out ] = \"Waddup waddup waddup!\" # Run context with console input/output and our 'hi!' module. rs . Context ( \"hi!\" ) . run () Raveboard Ravestate has an angular / socket.io -based interactive (beta) UI called Raveboard . It shows the events (spikes) that are currently relevant, as well as potential state activations that are referencing these spikes. When using raveboard.UIContext instead of Context , or python3 -m raveboard instead of python3 -m ravestate , a real-time visualization of all spikes/activations, as well as a chat window, will be hosted on a configurable port. You can find dedicated docs here . The following GIF shows raveboard together with ravestate_visionio : Installation Via PIP The easiest way to install ravestate is through pip: pip install ravestate Note: Ravestate requires Python 3.6 or higher. It is tested on Ubuntu 16.04 and 18.04, as well as macOS > High Sierra. It is currently not tested on Windows. For reliability, we recommend using an environment virtualization tool, like virtualenv or conda . Via Docker/Docker-compose Why Docker? Ravestate offers a docker image that bundles runtime dependencies that are required for advanced cognitive dialog systems/chatbots: \ud83d\udce6 Neo4j : The Neo4j Graph DBMS is used by Scientio for long-term memory. \ud83d\udca1 Redis : A Redis in-memory DB is used for fast short-term memory, e.g. to store/recall facial feature vectors. \ud83e\udd26 FaceOracle : A Roboy-developed server-client architecture used by ravestate_visionio for real-time face recognition. \ud83e\udd16 ROS Melodic : Version 1 of the Robot Operating System for distributed real-time communication. This version of ROS requires a broker process ( roscore ), which is started automatically inside the container. \ud83e\udd16 ROS2 Dashing : Version 2 of the Robot Operating System for distributed real-time communication. \ud83e\udd17 HuggingFace Transformer Models : Language models (ConvAI GPT/OpenAI GPT2) for neural-network-generated conversation. \ud83d\udc8c Roboy ROS Messages : Message defs. that are required to interact with Roboy hardware. Installing these dependencies by hand is time-consuming and error-prone, so using Docker to ship them makes everyone's lives easier! How to build? Clone ravestate: git clone git@github.com:roboy/ravestate && cd ravestate You can build the ravestate container using the provided Dockerfile : docker build -t ravestate . Note: Building the container takes time and requires a good internet connection, since all of the dependencies are several Gigabytes in size. How to run? Use one of the following docker-compose commands to run ravestate in Docker: Platform Command Linux docker-compose up -d rs-linux macOS docker-compose up -d rs-macos Windows Not supported yet. The container is now running and a shell inside the container can be opened with: docker exec -it rs bash You can now start ravestate or raveboard as described in the section Running Hello World . python3 -m ravestate [ ... ] Which services are exposed from the container? Service Port Description Neo4j UI 7474 Neo4j UI for DB stored under /db/neo4j Neo4j Bolt Interface 7687 Communication with Neo4j DBMS Redis Database Dump - A dump of the Redis DB in the container can be found under /db/redis FaceOracle Client Interface 8088 Visualisation for the FaceOracle client. Raveboard 42424 Default port for raveboard, the ravestate debug UI. For development Initial configuration and setup Clone the repository and install dependencies: cd ~ # Create a virtual python environment to not pollute the global setup python3 -m virtualenv -p python3 python-ravestate # Source the virtual environment . python-ravestate/bin/activate # Clone the repo git clone git@github.com:roboy/ravestate && cd ravestate # Install normal requirements pip install -r requirements.txt # To run tests & build docs, install pytest, mocking, fixtures, pydoc, ... pip install -r requirements-dev.txt # Link your local ravestate clone into your virtualenv pip install -e . Launch the ravestate docker container as described above. It will serve you Neo4j, which is a backend for Scientio , Roboy's long-term memory system. In the config folder, create a file called keys.yml . It should have the following content: module : telegramio config : telegram-token : # This is where your own telegram bot token # will go later You may now conduct your first conversation with ravestate: python3 -m raveboard -f config/generic.yml -f config/keys.yml Open raveboard on localhost:42424/ravestate/index.html?rs-sio-url=http%3A//localhost%3A42424 to conduct your first conversation with ravestate. After the conversation, check the Neo4j interface under localhost:7474 . It should now contain some nodes! Reminder: Whenever you use ravestate from the command line, source the virtual environment first! Running your Telegram bot To test your telegram bot with a custom bot token in your keys.yml , just run telegram_test.yml instead of generic.yml . This will load the ravestate_telegramio module. Setting up PyCharm Open your local ravestate clone as a project in pycharm. Under Project Preferences > Python interpreter , set your virtual environment. Mark the modules folder as sources root via the right-click context menu. Create a run config via the \"Edit configurations menu\": \u2022 Create a new Python configuration. \u2022 Set raveboard as the module to execute \u2022 Set the working directory to the git clone directory. \u2022 Set parameters to -f config/generic.yml -f config/keys.yml . You should now be able to run the generic ravestate config from PyCharm. Running Hello World Ravestate applications are defined by a configuration, which specifies the ravestate modules that should be loaded. To run the basic hello world application, run ravestate with a config file or command line arguments: Running with command line spec You can easily run a combination of ravestate modules in a shared context, by listing them as arguments to python3 -m ravestate : python3 -m ravestate \\ ravestate_wildtalk \\ ravestate_conio \\ ravestate_hibye \\ ravestate_persqa Run python3 -m ravestate -h to see more options! Running with config file(s) You may specify a series of config files to configure ravestate context, when specifying everything through the command line becomes too laborious: # In file hello_world.yml module : core config : import : - ravestate_wildtalk - ravestate_conio - ravestate_hibye - ravestate_persqa Then, run ravestate with this config file: python3 -m ravestate -f hello_world.yml Modules Ravestate offers a landscape of fine-grained modules for different aspects of dialog application tasks, which may be seen in the following dependency diagram. Broadly, the modules are categorized into Core (Blue), I/O (Yellow), External (Red) and Skills (Green): Core Modules Module name Description ravestate Core ravestate library. ravestate_rawio Provides raw_in , raw_out , pic_in properties, which are served by the IO modules. ravestate_ontology Connects to scientio to serve a built-in ontology. ravestate_interloc Provides the all_interlocutors property, where present interlocutors are registered by the IO modules. ravestate_idle Provides bored and impatient signals, as specified here . ravestate_verbaliser Utilities for easy management of conversational text, documented here . ravestate_nlp Spacy-based NLP properties and signals, documented here . ravestate_emotion Generates signals for, and recognizes specific emotions ( sig_shy , sig_surprise , sig_happy , sig_affectionate ). ravestate_ros1 Provides specific Ros1PubProperty , Ros1SubProperty and Ros1CallProperty context properties, which greatly simplify working with ROS1 in ravestate. Documentation here . ravestate_ros2 Provides specific Ros2PubProperty , Ros2SubProperty and Ros2CallProperty context properties, which greatly simplify working with ROS2 in ravestate. IO Modules Module name Description ravestate_conio Simple command-line based IO for development purposes. ravestate_telegramio Single- or Multi-process Telegram server module, documented here . ravestate_roboyio PyroBoy -based STT/TTS with ROS2. ravestate_visionio See dedicated docs here . Enables face-recognition based dialog interactions. Skill Modules Module name Description ravestate_wildtalk See docs here - runs generative language models (GPT-2, ConvAi, ParlAi)! ravestate_hibye Simply voices Hi! (or the likes thereof) when an interlocutor is added, and Bye when one is removed. ravestate_persqa Conducts personalized smalltalk with interlocutors, interacts with Scientio to persist trivia. ravestate_genqa DrQA -based general question answering module. ravestate_roboyqa QA module which provides answers to questions about Roboy, such as Who is your dad? ravestate_akinator (*) Enables dialog-based play of Akinator! ravestate_sendpics (*) Uses face recognition to extract facial features and an assiciated Person with pic_in and ontology, which are then persisted in Redis and Scientio. ravestate_fillers Recognize when the dialog context is taking a long time to produce an answer, and voice a filler like \"Uhm\" or \"Let's see...\" . Note: (*) = deprecated. Running tests If you have built the ravestate docker image as described above, you may run the test suite as follows: docker run -t -v $( pwd ) :/ravestate -w /ravestate ravestate ./run_tests.sh Building/maintaining the docs If you have installed the dependencies from requirements-dev.txt , generate the docs by running this command at project root: export PYTHONPATH = $PYTHONPATH : $( pwd ) /modules git rm -rf docs rm -rf _build docs pydocmd build mkdir -p docs/resources/docs && cp resources/docs/*.png docs/resources/docs && cp resources/docs/*.gif docs/resources/docs git add docs/* # For inspection: python3 -m http.server --directory docs The structure and content of the docs are defined in the file pydocmd.yml .","title":"Home"},{"location":"#about","text":"____ __ __ _____ _____ / _ \\ ____ __ ______ ______ / / _____ / / ___ / _ _ \\ / _ _ \\ / / _ / / __ \\ / / / / __ \\ / ___ \\ , / __ \\ , / __ \\ 0 > 0 > < 0 < 0 / , \\ , / / _ / / \\ \\ / / / _ / / \\ __ , / / / _ / / / / _ / / \\ __ \u22bd __ / \\ __ \u22bd __ / \\ / \\ / \\ __ / \\ / \\ __ / , ___ / \\ ____ / \\ / \\ __ / \\ / \\ / , ___ / \u22c2 - Hey ! \u22c2 \\ ____ / \\ ____ / Ol\u00e0 ! - Ravestate is a reactive library for real-time natural language dialog systems. It combines elements from event-based and reactive programming into an API, where application states are defined as functions that are run when a certain boolean set of criteria (signals) in the current application context is satisfied. It is the first reactive API to allow for boolean combinations of events. You may find a short introductory video here .","title":"About"},{"location":"#reactive-hello-world","text":"import ravestate as rs # We want to write some text output, so we # need the raw:out context property from ravestate_rawio. import ravestate_rawio as rawio # Make sure that we use some i/o implementation, # so we can actually see stuff that is written to rawio:out. import ravestate_conio # Ravestate applications should always be wrapped in a Module. # This allows easier scoping, and enables separation of concerns # beyond states. with rs . Module ( name = \"hi!\" , depends = ( rawio . mod ,)): # Create an application state which reacts to the `:startup` signal, # and writes a string to raw:out. Note: State functions are # always run asynchronously! @rs.state ( cond = rs . sig_startup , write = rawio . prop_out ) def hello_world ( context ): context [ rawio . prop_out ] = \"Waddup waddup waddup!\" # Run context with console input/output and our 'hi!' module. rs . Context ( \"hi!\" ) . run ()","title":"Reactive Hello World"},{"location":"#raveboard","text":"Ravestate has an angular / socket.io -based interactive (beta) UI called Raveboard . It shows the events (spikes) that are currently relevant, as well as potential state activations that are referencing these spikes. When using raveboard.UIContext instead of Context , or python3 -m raveboard instead of python3 -m ravestate , a real-time visualization of all spikes/activations, as well as a chat window, will be hosted on a configurable port. You can find dedicated docs here . The following GIF shows raveboard together with ravestate_visionio :","title":"Raveboard"},{"location":"#installation","text":"","title":"Installation"},{"location":"#via-pip","text":"The easiest way to install ravestate is through pip: pip install ravestate Note: Ravestate requires Python 3.6 or higher. It is tested on Ubuntu 16.04 and 18.04, as well as macOS > High Sierra. It is currently not tested on Windows. For reliability, we recommend using an environment virtualization tool, like virtualenv or conda .","title":"Via PIP"},{"location":"#via-dockerdocker-compose","text":"","title":"Via Docker/Docker-compose"},{"location":"#why-docker","text":"Ravestate offers a docker image that bundles runtime dependencies that are required for advanced cognitive dialog systems/chatbots: \ud83d\udce6 Neo4j : The Neo4j Graph DBMS is used by Scientio for long-term memory. \ud83d\udca1 Redis : A Redis in-memory DB is used for fast short-term memory, e.g. to store/recall facial feature vectors. \ud83e\udd26 FaceOracle : A Roboy-developed server-client architecture used by ravestate_visionio for real-time face recognition. \ud83e\udd16 ROS Melodic : Version 1 of the Robot Operating System for distributed real-time communication. This version of ROS requires a broker process ( roscore ), which is started automatically inside the container. \ud83e\udd16 ROS2 Dashing : Version 2 of the Robot Operating System for distributed real-time communication. \ud83e\udd17 HuggingFace Transformer Models : Language models (ConvAI GPT/OpenAI GPT2) for neural-network-generated conversation. \ud83d\udc8c Roboy ROS Messages : Message defs. that are required to interact with Roboy hardware. Installing these dependencies by hand is time-consuming and error-prone, so using Docker to ship them makes everyone's lives easier!","title":"Why Docker?"},{"location":"#how-to-build","text":"Clone ravestate: git clone git@github.com:roboy/ravestate && cd ravestate You can build the ravestate container using the provided Dockerfile : docker build -t ravestate . Note: Building the container takes time and requires a good internet connection, since all of the dependencies are several Gigabytes in size.","title":"How to build?"},{"location":"#how-to-run","text":"Use one of the following docker-compose commands to run ravestate in Docker: Platform Command Linux docker-compose up -d rs-linux macOS docker-compose up -d rs-macos Windows Not supported yet. The container is now running and a shell inside the container can be opened with: docker exec -it rs bash You can now start ravestate or raveboard as described in the section Running Hello World . python3 -m ravestate [ ... ]","title":"How to run?"},{"location":"#which-services-are-exposed-from-the-container","text":"Service Port Description Neo4j UI 7474 Neo4j UI for DB stored under /db/neo4j Neo4j Bolt Interface 7687 Communication with Neo4j DBMS Redis Database Dump - A dump of the Redis DB in the container can be found under /db/redis FaceOracle Client Interface 8088 Visualisation for the FaceOracle client. Raveboard 42424 Default port for raveboard, the ravestate debug UI.","title":"Which services are exposed from the container?"},{"location":"#for-development","text":"","title":"For development"},{"location":"#initial-configuration-and-setup","text":"Clone the repository and install dependencies: cd ~ # Create a virtual python environment to not pollute the global setup python3 -m virtualenv -p python3 python-ravestate # Source the virtual environment . python-ravestate/bin/activate # Clone the repo git clone git@github.com:roboy/ravestate && cd ravestate # Install normal requirements pip install -r requirements.txt # To run tests & build docs, install pytest, mocking, fixtures, pydoc, ... pip install -r requirements-dev.txt # Link your local ravestate clone into your virtualenv pip install -e . Launch the ravestate docker container as described above. It will serve you Neo4j, which is a backend for Scientio , Roboy's long-term memory system. In the config folder, create a file called keys.yml . It should have the following content: module : telegramio config : telegram-token : # This is where your own telegram bot token # will go later You may now conduct your first conversation with ravestate: python3 -m raveboard -f config/generic.yml -f config/keys.yml Open raveboard on localhost:42424/ravestate/index.html?rs-sio-url=http%3A//localhost%3A42424 to conduct your first conversation with ravestate. After the conversation, check the Neo4j interface under localhost:7474 . It should now contain some nodes! Reminder: Whenever you use ravestate from the command line, source the virtual environment first!","title":"Initial configuration and setup"},{"location":"#running-your-telegram-bot","text":"To test your telegram bot with a custom bot token in your keys.yml , just run telegram_test.yml instead of generic.yml . This will load the ravestate_telegramio module.","title":"Running your Telegram bot"},{"location":"#setting-up-pycharm","text":"Open your local ravestate clone as a project in pycharm. Under Project Preferences > Python interpreter , set your virtual environment. Mark the modules folder as sources root via the right-click context menu. Create a run config via the \"Edit configurations menu\": \u2022 Create a new Python configuration. \u2022 Set raveboard as the module to execute \u2022 Set the working directory to the git clone directory. \u2022 Set parameters to -f config/generic.yml -f config/keys.yml . You should now be able to run the generic ravestate config from PyCharm.","title":"Setting up PyCharm"},{"location":"#running-hello-world","text":"Ravestate applications are defined by a configuration, which specifies the ravestate modules that should be loaded. To run the basic hello world application, run ravestate with a config file or command line arguments:","title":"Running Hello World"},{"location":"#running-with-command-line-spec","text":"You can easily run a combination of ravestate modules in a shared context, by listing them as arguments to python3 -m ravestate : python3 -m ravestate \\ ravestate_wildtalk \\ ravestate_conio \\ ravestate_hibye \\ ravestate_persqa Run python3 -m ravestate -h to see more options!","title":"Running with command line spec"},{"location":"#running-with-config-files","text":"You may specify a series of config files to configure ravestate context, when specifying everything through the command line becomes too laborious: # In file hello_world.yml module : core config : import : - ravestate_wildtalk - ravestate_conio - ravestate_hibye - ravestate_persqa Then, run ravestate with this config file: python3 -m ravestate -f hello_world.yml","title":"Running with config file(s)"},{"location":"#modules","text":"Ravestate offers a landscape of fine-grained modules for different aspects of dialog application tasks, which may be seen in the following dependency diagram. Broadly, the modules are categorized into Core (Blue), I/O (Yellow), External (Red) and Skills (Green):","title":"Modules"},{"location":"#core-modules","text":"Module name Description ravestate Core ravestate library. ravestate_rawio Provides raw_in , raw_out , pic_in properties, which are served by the IO modules. ravestate_ontology Connects to scientio to serve a built-in ontology. ravestate_interloc Provides the all_interlocutors property, where present interlocutors are registered by the IO modules. ravestate_idle Provides bored and impatient signals, as specified here . ravestate_verbaliser Utilities for easy management of conversational text, documented here . ravestate_nlp Spacy-based NLP properties and signals, documented here . ravestate_emotion Generates signals for, and recognizes specific emotions ( sig_shy , sig_surprise , sig_happy , sig_affectionate ). ravestate_ros1 Provides specific Ros1PubProperty , Ros1SubProperty and Ros1CallProperty context properties, which greatly simplify working with ROS1 in ravestate. Documentation here . ravestate_ros2 Provides specific Ros2PubProperty , Ros2SubProperty and Ros2CallProperty context properties, which greatly simplify working with ROS2 in ravestate.","title":"Core Modules"},{"location":"#io-modules","text":"Module name Description ravestate_conio Simple command-line based IO for development purposes. ravestate_telegramio Single- or Multi-process Telegram server module, documented here . ravestate_roboyio PyroBoy -based STT/TTS with ROS2. ravestate_visionio See dedicated docs here . Enables face-recognition based dialog interactions.","title":"IO Modules"},{"location":"#skill-modules","text":"Module name Description ravestate_wildtalk See docs here - runs generative language models (GPT-2, ConvAi, ParlAi)! ravestate_hibye Simply voices Hi! (or the likes thereof) when an interlocutor is added, and Bye when one is removed. ravestate_persqa Conducts personalized smalltalk with interlocutors, interacts with Scientio to persist trivia. ravestate_genqa DrQA -based general question answering module. ravestate_roboyqa QA module which provides answers to questions about Roboy, such as Who is your dad? ravestate_akinator (*) Enables dialog-based play of Akinator! ravestate_sendpics (*) Uses face recognition to extract facial features and an assiciated Person with pic_in and ontology, which are then persisted in Redis and Scientio. ravestate_fillers Recognize when the dialog context is taking a long time to produce an answer, and voice a filler like \"Uhm\" or \"Let's see...\" . Note: (*) = deprecated.","title":"Skill Modules"},{"location":"#running-tests","text":"If you have built the ravestate docker image as described above, you may run the test suite as follows: docker run -t -v $( pwd ) :/ravestate -w /ravestate ravestate ./run_tests.sh","title":"Running tests"},{"location":"#buildingmaintaining-the-docs","text":"If you have installed the dependencies from requirements-dev.txt , generate the docs by running this command at project root: export PYTHONPATH = $PYTHONPATH : $( pwd ) /modules git rm -rf docs rm -rf _build docs pydocmd build mkdir -p docs/resources/docs && cp resources/docs/*.png docs/resources/docs && cp resources/docs/*.gif docs/resources/docs git add docs/* # For inspection: python3 -m http.server --directory docs The structure and content of the docs are defined in the file pydocmd.yml .","title":"Building/maintaining the docs"},{"location":"config/","text":"ravestate.argparser handle_args handle_args ( * args ) -> Tuple [ List [ str ], List [ Tuple [ str , str , Any ]], List [ str ]] Runs an argument parser for the given args. Returns modules-to-load, config value-overrides and config file-pathes. Note: If the arguments are ill-formatted, or the -h argument is passed, help will be printed to the console and the program will abort. args : Argument list which will be fed into argparse.parse_args. Returns: A Tuple with three items: 1.) A list of module names which should be imported. 2.) A list of tuples, where each tuple is a module name, a config key name, and a value. 3.) A list of yaml file paths. ravestate.config Configuration Configuration ( self , paths : List [ str ]) The Configuration class maintains a dictionary of key-value stores, which represent configuration entries for specific named modules. The key-value stores may be successively updated with consecutive yaml files, where each yaml document has the following content: module: module-name config: key-a: value-a key-b: value-b # etc add_conf Configuration . add_conf ( self , mod : ravestate . module . Module ) Register a set of allowed config entries for a specific module. Correctly typed values for allowed keys, that were previously parsed during construction from the yaml files, will be applied immediately. mod : A module object with a name and a conf dict. get_conf Configuration . get_conf ( self , module_name : str ) Retrieve updated config values for a module that was previously registered with add_conf. module_name : The module name for which configuration should be retrieved. Returns: A dictionary which contains exactly the keys that were contained in the module configuration dictionary during add_conf, or an empty dictionary if the module name is unknown. get Configuration . get ( self , module_name : str , key : str ) -> Any Get the current value of a config entry. module_name : The module that provides the config entry. key : A config key for the module that was previously added through add_conf. Returns: The current value, or None, if the entry does not exist. set Configuration . set ( self , module_name : str , key : str , value : Any ) Set the current value of a config entry. module_name : The module of the config entry. key : A config key for the module that was previously added through add_conf. value : The new value for the config entry. An error will be raised, if the type of the new value does not match the type of the old value. write Configuration . write ( self , path : str ) Write all current config entries to a yaml file. path : The file path to write. Will be overwritten! read Configuration . read ( self , path : str ) Loads all documents from a yaml file and tries to interpret them as configuration objects as described above. path : The yaml file path from which to load config documents.","title":"Configuration"},{"location":"context/","text":"ravestate.context create_and_run_context create_and_run_context ( * args , runtime_overrides = None ) Creates a new Context with the given parameters and runs it Context Context ( self , * arguments , runtime_overrides : List [ Tuple [ str , str , Any ]] = None ) emit Context . emit ( self , signal : ravestate . constraint . Signal , parents : Set [ ravestate . spike . Spike ] = None , wipe : bool = False , payload : Any = None , boring : bool = False ) -> ravestate . spike . Spike Emit a signal to the signal processing loop. Note: The spike will only be picked up by activations once run_once / run is called! signal : The signal to be emitted. parents : The signal's parents, if it is supposed to be integrated into a causal group. wipe : Boolean to control, whether wipe (signal) should be called before the new spike is created. payload : Value that should be embedded in the new spike. boring : Flag which indicates, whether the new spike is boring. Activations which acquire boring spikes will not count against the core:activity flag. Returns: The newly created spike object. wipe Context . wipe ( self , signal : ravestate . constraint . Signal ) Delete all spikes for the given signal. Partially fulfilled states that have acquired an affected spike will be forced to reject it. Wiping a parent spike will also wipe all child spikes. signal : The signal for which all existing spikes (and their children) should be invalidated and forgotten. run Context . run ( self ) -> None Creates a signal processing thread, starts it, and emits the core:startup signal. shutting_down Context . shutting_down ( self ) -> bool Retrieve the shutdown flag value, which indicates whether shutdown() has been called. shutdown Context . shutdown ( self ) -> None Sets the shutdown flag and waits for the signal processing thread to join. add_module Context . add_module ( self , module_name : str ) -> None Add a module by python module folder name, or by ravestate module name. module_name : The name of the module to be added. If it is the name of a python module that has not been imported yet, the python module will be imported, and any ravestate modules registered during the python import will also be added to this context. add_state Context . add_state ( self , * , st : ravestate . state . State ) -> None Add a state to this context. It will be indexed wrt/ the properties/signals it depends on. Error messages will be generated for unknown signals/properties. st : The state which should be added to this context. rm_state Context . rm_state ( self , * , st : ravestate . state . State ) -> None Remove a state from this context. Note, that any state which is constrained on the signal that is emitted by the deleted state will also be deleted. st : The state to remove. An error message will be generated, if the state was not previously added to this context with add_state(). add_prop Context . add_prop ( self , * , prop : ravestate . property . Property ) -> None Add a copy of a property to this context. An error message will be generated, if a property with the same name has already been added previously. Note: Context will adopt a copy of the given property, the actual property will not be changed. prop : The property object that should be added. rm_prop Context . rm_prop ( self , * , prop : ravestate . property . Property ) -> None Remove a property from this context. Generates error message, if the property was not added with add_prop() to the context previously prop : The property to remove.object conf Context . conf ( self , * , mod : str , key : Union [ str , NoneType ] = None ) -> Any Get a single config value, or all config values for a particular module. mod : The module whose configuration should be retrieved. key : A specific config key of the given module, if only a single config value should be retrieved. Returns: The value of a single config entry if key and module are both specified and valid, or a dictionary of config entries if only the module name is specified (and valid). signal_specificity Context . signal_specificity ( self , sig : ravestate . constraint . Signal ) -> float Called by state activation to determine it's constraint's specificity. sig : The signal whose specificity should be returned. Returns: The given signal's specificity. reacquire Context . reacquire ( self , act : ravestate . iactivation . IActivation , sig : ravestate . constraint . Signal ) Called by activation, to indicate, that it needs a new Spike for the specified signal, and should for this purpose be referenced by context. Note: Not thread-safe, sync must be guaranteed by caller. act : The activation that needs a new spike of the specified nature. sig : Signal type for which a new spike is needed. withdraw Context . withdraw ( self , act : ravestate . iactivation . IActivation , sig : ravestate . constraint . Signal ) Called by activation to make sure that it isn't referenced anymore as looking for the specified signal. This might be, because the activation chose to eliminate itself due to activation pressure, or because one of the activations conjunctions was fulfilled, so it is no longer looking for signals to fulfill the remaining conjunctions. Note: Not thread-safe, sync must be guaranteed by caller. act : The activation that has lost interest in the specified signal. sig : Signal type for which interest is lost. secs_to_ticks Context . secs_to_ticks ( self , seconds : float ) -> int Convert seconds to an equivalent integer number of ticks, given this context's tick rate. seconds : Seconds to convert to ticks. Returns: An integer tick count. possible_signals Context . possible_signals ( self , state : ravestate . state . State ) -> Generator [ ravestate . constraint . Signal , NoneType , NoneType ] Yields all signals, for which spikes may be created if the given state is executed. state : The state, which should be analyzed for it's possibly generated signals (declared signal + property-changed signals). run_once Context . run_once ( self , seconds_passed = 1.0 , debug = False ) -> None Run a single update for this context, which will ... (0) progress cooled down state weights. (1) reduce redundant candidate activations. (2) associate new spikes with state activations. (3) update state activations. (4) forget spikes which have no suitors in their causal groups. (5) age spikes. (6) invoke garbage collection. (7) update the core:activity and core:pressure variables. seconds_passed : Seconds, as floatiing point, since the last update. Will be used to determine the number of ticks to add/subtract to/from spike/activation age/cooldown/deathclock. test Context . test ( self ) -> bool Execute internal integrity checks. ravestate.spike Spike Spike ( self , * , sig : str , parents : Set [ ForwardRef ( 'Spike' )] = None , consumable_resources : Set [ str ] = None , payload : Any = None , boring : bool = False ) This class encapsulates a single spike, to track ... ... it's consumption for different output properties (through CausalGroup ). ... it's offspring instances (causal group -> spikes caused by this spike) boring Spike . boring ( self ) Get the boring -flag of this spike, which indicates whether state of the activation that produced this spike was boring. :return: True if the activation boring, false otherwise. causal_group Spike . causal_group ( self ) -> ravestate . causal . CausalGroup Get this spike's causal group. Returns: This instances causal group. Should never be None. adopt Spike . adopt ( self , child : 'Spike' ) -> None Called in spike constructor, for instances which claim to be caused by this spike. child : The child to add to this spike's causal group. wiped Spike . wiped ( self , child : 'ISpike' ) -> None Called by an offspring signal, to notify the spike that it was wiped, and should therefore be removed from the children set. child : The child to be forgotten. wipe Spike . wipe ( self , already_wiped_in_causal_group : bool = False ) -> None Called either in Context run loop when the spike is found to be stale (with wiped_in_causal_group=True), or in Context.wipe(spike), or by parent (recursively). After this function is called, the spike should be cleaned up by GC. already_wiped_in_causal_group : Boolean which indicates, whether wiped(spike) must still be called on the group to make sure sure that no dangling references to the spike are maintained by any state activations. has_offspring Spike . has_offspring ( self ) Called by CausalGroup.stale(spike). Returns: True if the spike has active offspring, false otherwise. tick Spike . tick ( self ) -> None Increment this spike's age by 1. age Spike . age ( self ) -> int Obtain this spike's age (in ticks). offspring Spike . offspring ( self ) -> Generator [ ForwardRef ( 'Spike' ), NoneType , NoneType ] Recursively yields this spike's offspring and it's children's offspring. Returns: All of this spike's offspring spikes. is_wiped Spike . is_wiped ( self ) Check, whether this spike has been wiped, and should therefore not be acquired anymore. payload Spike . payload ( self ) -> Any Get payload for this spike. The payload is an arbitrary value passed in Context.emit() . ravestate.activation Activation Activation ( self , st : ravestate . state . State , ctx : ravestate . icontext . IContext ) Encapsulates the potential activation of a state. Tracks the collection of Spikes to fulfill of the state-defined activation constraints. resources Activation . resources ( self ) -> Set [ str ] Return's the set of the activation's write-access property names. specificity Activation . specificity ( self ) -> float Returns the lowest specificity among the specificity values of the activation's conjunct constraints. The specificity for a single conjunction is calculated as the sum of it's component signal's specificities, which in turn is calculated as one over the signal's subscriber count. dereference Activation . dereference ( self , * , spike : Union [ ravestate . iactivation . ISpike , NoneType ] = None , reacquire : bool = False , reject : bool = False , pressured : bool = False ) -> None Notify the activation, that a single or all spike(s) are not available anymore, and should therefore not be referenced anymore by the activation. This is called by ... ... context when a state is deleted. ... causal group, when a referenced signal was consumed for a required property. ... causal group, when a referenced signal was wiped. ... this activation (with reacquire=True and pressured=True), if it gives in to activation pressure. spike : The spike that should be forgotten by the activation, or none, if all referenced spikes should be forgotten. reacquire : Flag which tells the function, whether for every rejected spike, the activation should hook into context for reacquisition of a replacement spike. reject : Flag which controls, whether de-referenced spikes should be explicitely rejected through their causal groups. pressured : Flag which controls, whether de-referencing should only occur for spikes of causal groups in the pressuring_causal_groups set. acquire Activation . acquire ( self , spike : ravestate . spike . Spike ) -> bool Let the activation acquire a signal it is registered to be interested in. spike : The signal which should fulfill at least one of this activation's signal constraints. Returns: True if the spike was acquired by at least one signal, false if acquisition failed: This may happen for multiple reasons: (1) Acquisition failed, because the spike is too old (2) Acquisition failed, because the spike's causal group does not match it's completions. (3) Acquisition failed, because the spike's causal group does not offer all of this activation's state's write-props. secs_to_ticks Activation . secs_to_ticks ( self , seconds : float ) -> int Convert seconds to an equivalent integer number of ticks, given this activation's tick rate. seconds : Seconds to convert to ticks. Returns: An integer tick count. pressure Activation . pressure ( self , give_me_up : ravestate . iactivation . ICausalGroup ) Called by CausalGroup, to pressure the activation to make a decision on whether it is going to retain a reference to the given spike, given that there is a lower- specificity activation which is ready to run. give_me_up : Causal group that wishes to be de-referenced by this activation. is_pressured Activation . is_pressured ( self ) Called by context, to figure out whether the activation is pressured, and therefore the idle:bored signal should be emitted. spiky Activation . spiky ( self , filter_boring = False ) -> bool Returns true, if the activation has acquired any spikes at all. filter_boring : Flag to indicate, whether boring spikes should should NOT be counted against a true return value. Returns: True, if any of this activation's constraint's signal is referencing a spike. boring Activation . boring ( self ) -> bool Returns True, if the activation's state is boring. Called by context, to figure out whether this activation counts towards the system not setting the idle:bored property to True. Returns: True, if the state assigned to this activation has the boring field set to true, False otherwise. spikes Activation . spikes ( self ) -> Generator [ ravestate . spike . Spike , NoneType , NoneType ] Returns iterable for all the spikes currently acquired by the activation. possible_signals Activation . possible_signals ( self ) -> Generator [ ForwardRef ( 'Signal' ), NoneType , NoneType ] Yields all signals, for which spikes may be created if this activation's state is executed. effect_not_caused Activation . effect_not_caused ( self , group : ravestate . iactivation . ICausalGroup , effect : str ) -> None Notify the activation, that a follow-up signal will not be produced by the given causal group. The activation will go through it's constraint, and reject all completion spikes for signals of name effect , if the completion spikes are from the given causal group. group : The causal group which will not contain a spike for signal effect . effect : Name of the signal for which no spike will be produced. update Activation . update ( self ) -> bool Called once per tick on this activation, to give it a chance to activate itself, or auto-eliminate, or reject spikes which have become too old. Returns: True, if the target state is activated and teh activation be forgotten, false if needs further attention in the form of updates() by context in the future. ravestate.causal CausalGroup CausalGroup ( self , resources : Set [ str ]) Class which represents a causal group graph of spike parent/offspring spikes (a \"superspike\"). These must synchronize wrt/ their (un)written properties and state activation candidates, such that they don't cause output races. Note: Always use a with ... construct to interact with a causal group. Otherwise, undefined behavior may occur due to race conditions. merge CausalGroup . merge ( self , other : 'CausalGroup' ) Merge this causal group with another. Unwritten props will become the set intersection of this group's unwritten props and other's unwritten props. consumed() will be called with all properties that are consumed by other, but not this. Afterwards, other's member objects will be set to this's. acquired CausalGroup . acquired ( self , spike : 'ISpike' , acquired_by : ravestate . iactivation . IActivation , detached : bool ) -> bool Called by Activation to notify the causal group, that it is being referenced by an activation constraint for a certain member spike. spike : State activation instance, which is now being referenced by the specified causal group. acquired_by : State activation instance, which is interested in this property. detached : Tells the causal group, whether the reference is detached, and should therefore receive special treatment. Returns: Returns True if all of the acquiring's write-props are free, and the group now refs. the activation, False otherwise. rejected CausalGroup . rejected ( self , spike : 'ISpike' , rejected_by : ravestate . iactivation . IActivation , reason : int ) -> None Called by a state activation, to notify the group that a member spike is no longer being referenced for the given state's write props. This may be because ... ... the activation's dereference function was called. (reason=0) ... the spike got too old. (reason=1) ... the activation happened and is dereferencing it's spikes. (reason=2) spike : The member spike whose ref-set should be reduced. rejected_by : State activation instance, which is no longer interested in this property. reason : See about. consent CausalGroup . consent ( self , ready_suitor : ravestate . iactivation . IActivation ) -> bool Called by constraint, to inquire whether this causal group would happily be consumed for the given state activation's properties. This will be called periodically on the group by state activations that are ready to go. Therefore, a False return value from this function is never a final judgement (more like a \"maybe later\"). ready_suitor : The state activation which would like to consume this instance for it's write props. Returns: True if this instance agrees to proceeding with the given consumer for the consumer's write props, False otherwise. activated CausalGroup . activated ( self , act : ravestate . iactivation . IActivation ) Called by activation which previously received a go-ahead from consent(), when it is truly proceeding with running (after it got the go-ahead from all it's depended=on causal groups). act : The activation that is now running. resigned CausalGroup . resigned ( self , act : ravestate . iactivation . IActivation ) -> None Called by activation, to let the causal group know that it failed, and a less specific activation may now be considered for the resigned state's write props. act : The activation that is unexpectedly not consuming it's resources, because it's state resigned/failed. consumed CausalGroup . consumed ( self , resources : Set [ str ]) -> None Called by activation to notify the group, that it has been consumed for the given set of properties. resources : The properties which have been consumed. wiped CausalGroup . wiped ( self , spike : 'ISpike' ) -> None Called by a spike, to notify the causal group that the instance was wiped and should no longer be remembered. spike : The instance that should be henceforth forgotten. stale CausalGroup . stale ( self , spike : 'ISpike' ) -> bool Determine, whether a spike is stale (has no remaining interested activations and no children). Returns: True, if no activations reference the given spike for any unwritten property. False otherwise. check_reference_sanity CausalGroup . check_reference_sanity ( self ) -> bool Make sure, that the refcount-per-act-per-spike-per-resource value sum is equal to the number of spikes from this causal group acquired per activation for each activation in the index. :return: True if the criterion is fulfilled, False otherwise.","title":"Context"},{"location":"modules/","text":"ravestate.module Module Module ( self , * , name : str , config : Dict [ str , Any ] = None , depends : Iterable [ ForwardRef ( 'Module' )] = None ) Atomic class, which encapsulates a named set of states, properties and config entries, which form a coherent bundle. Example: with Module ( name = \"my_module\" , config = { \"paramA\" : 42 }): # define properties # define states modules_per_python_module defaultdict(default_factory[, ...]) --> dict with default factory The default factory is called without arguments to produce a new value when a key is not present, in getitem only. A defaultdict compares equal to a dict with the same items. All remaining arguments are treated the same as if they were passed to the dict constructor, including keyword arguments. registered_modules dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2) has_module has_module ( module_name : str ) -> bool Check whether a module with a particular name has been registered. module_name : The name which should be checked for beign registered. Returns: True if a module with the given name has been registered, false otherwise. get_module get_module ( module_name : str ) -> Set [ ravestate . module . Module ] Get modules registered with a particular name. Note: If the module_name refers to a Python module, multiple Ravestate modules might be returned. module_name : Python or Ravestate module name. Returns: The modules registered under the given name, empty set otherwise.","title":"Modules"},{"location":"properties/","text":"ravestate.property Property Property ( self , * , name = '' , allow_read = True , allow_write = True , allow_push = True , allow_pop = True , default_value = None , always_signal_changed = False , is_flag_property = False , wipe_on_changed = True , boring = False ) Base class for context properties. Controls read/write/push/pop/delete permissions, property name basic impls. for the property value, parent/child mechanism. Example (Creating a module containing a property named my_property): with Module ( name = \"my_module\" ): my_property = Property ( name = \"my_property\" ) set_parent_path Property . set_parent_path ( self , path ) Set the ancestors (including modulename) for a property path : ancestry in the form of modulename:parent_prop_name (or simply modulename) gather_children Property . gather_children ( self ) -> List [ ForwardRef ( 'Property' )] Collect this property, and all of it's children. read Property . read ( self ) Read the current property value write Property . write ( self , value ) Write a new value to the property value : The new value. Returns: True if the value has changed and :changed should be signaled, false otherwise. push Property . push ( self , child : 'Property' ) Add a child to the property child : The child object Returns: True if the child was added successfully, false otherwise. pop Property . pop ( self , child_name : str ) Remove a child from the property by it's name. child_name : Name of the child to be removed. Returns: True if the pop was successful, False otherwise changed Property . changed ( self ) -> ravestate . constraint . Signal Signal that is emitted by PropertyWrapper when write() returns True. pushed Property . pushed ( self ) -> ravestate . constraint . Signal Signal that is emitted by PropertyWrapper when push() returns True. popped Property . popped ( self ) -> ravestate . constraint . Signal Signal that is emitted by PropertyWrapper when pop() returns True. true Property . true ( self ) -> ravestate . constraint . Signal Signal that is emitted by PropertyWrapper when it is a flag-property and self.value is set to True. false Property . false ( self ) -> ravestate . constraint . Signal Signal that is emitted by PropertyWrapper when it is a flag-property and self.value is set to False. signals Property . signals ( self ) -> Generator [ ravestate . constraint . Signal , NoneType , NoneType ] Yields all signals that may be emitted because of this property, given it's write/push/pop permissions.","title":"Properties"},{"location":"states/","text":"state state ( * , signal : Union [ ravestate . constraint . Signal , NoneType ] = '' , write : Tuple [ ravestate . property . Property ] = (), read : Tuple [ ravestate . property . Property ] = (), cond : ravestate . constraint . Constraint = None , emit_detached : bool = False , weight : float = 1.0 , cooldown : float = 0.0 , boring : bool = False ) Decorator to declare a new state, which may emit a certain signal, write to a certain set of properties (calling write, push, pop), and read from certain properties (calling read). Example (Module that outputs \"Don't Panic\" after startup): with Module ( name = \"my_module\" ): @state ( cond = startup ()) def after_startup ( context , write = OUTPUT_PROPERTY ): context [ OUTPUT_PROPERTY ] = \"Don't Panic\" ravestate.constraint ConfigurableAge ConfigurableAge ( self , key : str ) Class for having min/max_age parameters for Constraints configurable with a config key key str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object. str () (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'. Constraint Constraint ( self , / , * args , ** kwargs ) Superclass for Signal, Conjunct and Disjunct Signal Signal ( self , name : str , * , min_age = 0.0 , max_age = 5.0 , detached = False , _skip_module_context = False ) Class that represents a Signal. Should be constructed in a with Module(..) context, such that it's module scope is set automatically. SignalRef SignalRef ( self , name : str , * , min_age = 0.0 , max_age = 5.0 , detached = False ) Signal reference. Almost the same as a signal, except that it will not try to auto-discover it's module out of thread-local context (module_name will stay unfilled). Needed, because sometimes you need to reference a singal within a module scope without assigning that signal to the contextual module. Conjunct Conjunct ( self , * args ) Class that represents a Conjunction of Signals. Can be constructed using an overloaded & operator. Example: signal_A & signal_B Disjunct Disjunct ( self , * args ) Class that represents a Disjunction of Conjunctions Can be constructed using an overloaded | operator. Examples: conjunction_A | conjunction_B ( signal_A & signal_B ) | ( signal_C & signal_D ) receptor receptor ( * , ctx_wrap : Union [ ravestate . wrappers . ContextWrapper , ravestate . context . Context ], write : Union [ str , ravestate . property . Property , Tuple [ Union [ str , ravestate . property . Property ]]]) A receptor is a special state which can be invoked from outside, to push values into the context. ctx_wrap : A context wrapper as is always given into the state functions as their first argument. write : The property, or tuple of properties, which are going to be written. Example: # Module that outputs \"Don't Panic\" one minute after startup # Because a receptor is used, the OUTPUT_PROPERTY is not blocked the whole time with Module ( name = \"my_module\" ): @state ( cond = startup ()) def after_startup ( context ): @receptor ( ctx_wrap = context , write = OUTPUT_PROPERTY ) def dont_panic ( ctx_write ): ctx_write [ OUTPUT_PROPERTY ] = \"Don't Panic\" sleep ( 60 ) dont_panic ()","title":"States"},{"location":"modules/raveboard/","text":"Raveboard Roboy ravestate UI module. Usage Substitute ravestate with raveboard when launching your module config. E.g. python3 -m raveboard ravestate_wildtalk ravestate_hibye If you did not override the raveboard_port config for the core module, you may access the UI in any browser under localhost:42424/ravestate/index.html Otherwise, just substitute 42424 with your configured port. Setup, Development and Building This project is built with Angular 8 and TypeScript. Initial setup: - install Angular CLI globally by running npm install -g @angular/cli (don't forget to add /.npm-global/bin to your path) - Install project dependencies by running npm install in the root folder (= folder with package.json ) To start development: - run ng serve in the root folder to start a dev server - Navigate to http://localhost:4200/ in the browser to open the UI - The app will automatically reload if you change any of the source files To build a production version: - run ng build --prod in the root folder to build a bundle for the browser - the bundle is saved in the dist folder","title":"Raveboard"},{"location":"modules/raveboard/#raveboard","text":"Roboy ravestate UI module.","title":"Raveboard"},{"location":"modules/raveboard/#usage","text":"Substitute ravestate with raveboard when launching your module config. E.g. python3 -m raveboard ravestate_wildtalk ravestate_hibye If you did not override the raveboard_port config for the core module, you may access the UI in any browser under localhost:42424/ravestate/index.html Otherwise, just substitute 42424 with your configured port.","title":"Usage"},{"location":"modules/raveboard/#setup-development-and-building","text":"This project is built with Angular 8 and TypeScript. Initial setup: - install Angular CLI globally by running npm install -g @angular/cli (don't forget to add /.npm-global/bin to your path) - Install project dependencies by running npm install in the root folder (= folder with package.json ) To start development: - run ng serve in the root folder to start a dev server - Navigate to http://localhost:4200/ in the browser to open the UI - The app will automatically reload if you change any of the source files To build a production version: - run ng build --prod in the root folder to build a bundle for the browser - the bundle is saved in the dist folder","title":"Setup, Development and Building"},{"location":"modules/ravestate_akinator/","text":"_ _ | | ( _ ) _ _____ | | _ _ ____ _____ _ | | _ ___ ____ ( ____ | | _ / ) | _ \\ ( ____ ( _ _ ) _ \\ / ___ ) / ___ | _ ( | | | | / ___ | | || | _ | | | \\ _____ | _ | \\ _ ) _ | _ | | _ \\ _____ | \\ __ ) ___ /| _ | 20 Questions with Akinator The Ravestate Akinator module offers an implementation of the 20 questions game. It consists of a wrapper for the API calls to the Akinator web game and several states to handle the game flow with the interlocutor. Architecture This is an overview of the dialog flow for the 20 questions game. Starting the Game There are two possibilities to trigger the game: 1. Interlocutor input: \"I want to play\", \"I like games\" or something similar 2. Automatically through Ravestate: When the system gets bored (no dialog states are active but an interlocutor is present) then Akinator is one of the possible modules that will be triggered. The API Wrapper api.py handles the requests to the online Akinator game. There are three different types of get requests to handle the game: 1. Question asking phase: post the answer to the previous question and retrieve the next question 2. Guessing phase: retrieve the guess 3. Game finishing phase: give Akinator feedback on his guess","title":"Akinator"},{"location":"modules/ravestate_akinator/#20-questions-with-akinator","text":"The Ravestate Akinator module offers an implementation of the 20 questions game. It consists of a wrapper for the API calls to the Akinator web game and several states to handle the game flow with the interlocutor.","title":"20 Questions with Akinator"},{"location":"modules/ravestate_akinator/#architecture","text":"This is an overview of the dialog flow for the 20 questions game.","title":"Architecture"},{"location":"modules/ravestate_akinator/#starting-the-game","text":"There are two possibilities to trigger the game: 1. Interlocutor input: \"I want to play\", \"I like games\" or something similar 2. Automatically through Ravestate: When the system gets bored (no dialog states are active but an interlocutor is present) then Akinator is one of the possible modules that will be triggered.","title":"Starting the Game"},{"location":"modules/ravestate_akinator/#the-api-wrapper","text":"api.py handles the requests to the online Akinator game. There are three different types of get requests to handle the game: 1. Question asking phase: post the answer to the previous question and retrieve the next question 2. Guessing phase: retrieve the guess 3. Game finishing phase: give Akinator feedback on his guess","title":"The API Wrapper"},{"location":"modules/ravestate_nlp/","text":"_ | | ____ | | ____ | _ \\ | || _ \\ | | | | || | _ | | | _ | | _ | \\ _ ) __ / | _ | NLP The natural language processing (NLP) module enables Roboy to process and understand the human language. That way he can interpret the meaning of the detected sentences. We use a free, open-source NLP library for advanced NLP in Python: spaCy You can find some hands-on examples here . Extracted Features Feature Ravestate Properties/Signals Description Example Sentence: 'Revolutions need six fancy chickens!' Tokenization nlp.prop_tokens Segmenting text into words, punctuation marks etc. 'Revolutions', 'need', 'six', 'fancy', 'chickens', '!' Part-of-Speech (POS) Tagging nlp.prop_postags Assigning word types to tokens 'NOUN', 'VERB', 'NUM', 'ADJ', 'NOUN', 'PUNCT' Detailed POS Tag nlp.prop_tags Fine-grained part-of-speech 'Revolutions' has the tag: 'NNS', which stand for: noun, plural 'need' has the tag: 'VBP', which stands for: verb, non-3rd person singular present List of POS tags Lemmatization nlp.prop_lemmas Assigning the base forms of words 'Revolutions' has the lemma: 'revolution' 'was' would have the lemma: 'be' Named Entity Recognition (NER) nlp.prop_ner Labelling \"real-world\" objects (\"NE\"=Named Entity) 'six' has the NE: 'CARDINAL', which are numerals that do not fall under another type List of NEs Triple Extraction nlp.prop_triples A triple consists of subject, predicate, object of sentence Triple: subject: 'Revolutions', predicate: 'need', object: 'chickens' About Roboy nlp.prop_roboy Detecting whether sentence is about Roboy 'you', 'roboy', 'robot', 'roboboy', ... Yes-No nlp.prop_yesno Detecting answers to yes-no questions Checking for 'yes', 'no', 'i don't know', 'probably', 'probably not' and synonyms of these Sentence Type: Question nlp.sig_is_question Emitted if the input sentence is a question Play Game nlp.sig_intent_play Emitted when the interlocutor wants to play a game input: \"I want to play\", \"I like games\" or something similar Additional features can be added and published to the system. All existing features can be found here . Using the Features React to Property Change Each feature is stored in a ravestate property. A state which wants to access a property needs read permissions for that property. Example: State that reads the \"yesno\" property import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio @rs.state ( cond = nlp . prop_yesno . changed (), # state reacts to a change in the 'yesno' property read = nlp . prop_yesno , # state is allowed to read the 'yesno' property write = rawio . prop_out ) # state is allowed to write to the output property def postive_chicken ( ctx : ContextWrapper ): if ctx [ nlp . prop_yesno ] . yes (): ctx [ rawio . prop_out ] = \"You seem to be a positive chicken!\" React to Signals For 'Sentence Type: Question' a signal is emitted. Example: State that reacts to the \"is-question\" signal import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio @rs.state ( cond = nlp . sig_is_question , # state reacts to the is-question signal write = rawio . prop_out ) # state is allowed to write to the output property def curious_chicken ( ctx : ContextWrapper ): ctx [ nlp . prop_out ] = \"You seem to ask a lot of questions, chicken!\" Using the Triples for Sentence Analysis The triple extraction is done in extract_triples.py by using the dependency tree of the sentence. A dependency tree shows the relation between the words of a sentence. The finite verb (predicate) is the structural center of the sentence and therefor of the tree. So starting with the predicate the algorithm searches through the dependency tree to find subject and object. Analyzing a Sentence Example: 'Chickens like revolutions' reaction state Triple: subject: 'Chickens', predicate: 'like', object: 'revolutions' import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio @rs.state ( cond = nlp . prop_triples . changed (), # state reacts to a change in the 'triples' property read = nlp . prop_triples , # state is allowed to read the 'triples' property write = rawio . prop_out ) # state is allowed to write to the output chanel def postive_chicken ( ctx : ContextWrapper ): triple = ctx [ nlp . prop_triples ][ 0 ] # gives you the first Triple object # check predicate and object correspondingly # match_either_lemma() is a method in the Triple class if triple . match_either_lemma ( subj = { \"chicken\" , \"dinosaur\" }): # returns true when subj, pred or obj have the desired value ctx [ rawio . prop_out ] = \"You said something about a chicken, i like chickens!\" Using User-defined Signals and Implementing Q/A-States A user can define signals as needed and implement states reacting on them. If both a signal is emitted and something is written to rawio in one state, the state needs to be defined with emit_detached=True . React to User-defined States and Implement Q/A Example: In one state a question is asks and then a user-defined signal is emitted that shows that the question has been stated. The other states reacts on the answer to this question. import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio chicken_signal = rs . Signal ( \"chicken signal\" ) @rs.state ( cond = nlp . prop_tokens . changed (), write = rawio . prop_out , signal = chicken_signal , emit_detached = True ) def ask_chicken_question ( ctx : rs . ContextWrapper ): ctx [ rawio . prop_out ] = \"Wanna know something awesome about chickens?\" return rs . Emit () @rs.state ( cond = chicken_signal & nlp . prop_yesno . changed (), read = nlp . prop_yesno , write = rawio . prop_out ) def answer_chicken_question ( ctx : rs . ContextWrapper ): if ctx [ nlp . prop_yesno ] == \"yes\" : ctx [ rawio . prop_out ] = \"Well, chicken's actually aren't that awesome.\" elif ctx [ nlp . prop_yesno ] == \"no\" : ctx [ rawio . prop_out ] = \"You're missing out on awesome chicken stories!\" Happy language processing to all chickens out there!","title":"NLP"},{"location":"modules/ravestate_nlp/#nlp","text":"The natural language processing (NLP) module enables Roboy to process and understand the human language. That way he can interpret the meaning of the detected sentences. We use a free, open-source NLP library for advanced NLP in Python: spaCy You can find some hands-on examples here .","title":"NLP"},{"location":"modules/ravestate_nlp/#extracted-features","text":"Feature Ravestate Properties/Signals Description Example Sentence: 'Revolutions need six fancy chickens!' Tokenization nlp.prop_tokens Segmenting text into words, punctuation marks etc. 'Revolutions', 'need', 'six', 'fancy', 'chickens', '!' Part-of-Speech (POS) Tagging nlp.prop_postags Assigning word types to tokens 'NOUN', 'VERB', 'NUM', 'ADJ', 'NOUN', 'PUNCT' Detailed POS Tag nlp.prop_tags Fine-grained part-of-speech 'Revolutions' has the tag: 'NNS', which stand for: noun, plural 'need' has the tag: 'VBP', which stands for: verb, non-3rd person singular present List of POS tags Lemmatization nlp.prop_lemmas Assigning the base forms of words 'Revolutions' has the lemma: 'revolution' 'was' would have the lemma: 'be' Named Entity Recognition (NER) nlp.prop_ner Labelling \"real-world\" objects (\"NE\"=Named Entity) 'six' has the NE: 'CARDINAL', which are numerals that do not fall under another type List of NEs Triple Extraction nlp.prop_triples A triple consists of subject, predicate, object of sentence Triple: subject: 'Revolutions', predicate: 'need', object: 'chickens' About Roboy nlp.prop_roboy Detecting whether sentence is about Roboy 'you', 'roboy', 'robot', 'roboboy', ... Yes-No nlp.prop_yesno Detecting answers to yes-no questions Checking for 'yes', 'no', 'i don't know', 'probably', 'probably not' and synonyms of these Sentence Type: Question nlp.sig_is_question Emitted if the input sentence is a question Play Game nlp.sig_intent_play Emitted when the interlocutor wants to play a game input: \"I want to play\", \"I like games\" or something similar Additional features can be added and published to the system. All existing features can be found here .","title":"Extracted Features"},{"location":"modules/ravestate_nlp/#using-the-features","text":"","title":"Using the Features"},{"location":"modules/ravestate_nlp/#react-to-property-change","text":"Each feature is stored in a ravestate property. A state which wants to access a property needs read permissions for that property. Example: State that reads the \"yesno\" property import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio @rs.state ( cond = nlp . prop_yesno . changed (), # state reacts to a change in the 'yesno' property read = nlp . prop_yesno , # state is allowed to read the 'yesno' property write = rawio . prop_out ) # state is allowed to write to the output property def postive_chicken ( ctx : ContextWrapper ): if ctx [ nlp . prop_yesno ] . yes (): ctx [ rawio . prop_out ] = \"You seem to be a positive chicken!\"","title":"React to Property Change"},{"location":"modules/ravestate_nlp/#react-to-signals","text":"For 'Sentence Type: Question' a signal is emitted. Example: State that reacts to the \"is-question\" signal import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio @rs.state ( cond = nlp . sig_is_question , # state reacts to the is-question signal write = rawio . prop_out ) # state is allowed to write to the output property def curious_chicken ( ctx : ContextWrapper ): ctx [ nlp . prop_out ] = \"You seem to ask a lot of questions, chicken!\"","title":"React to Signals"},{"location":"modules/ravestate_nlp/#using-the-triples-for-sentence-analysis","text":"The triple extraction is done in extract_triples.py by using the dependency tree of the sentence. A dependency tree shows the relation between the words of a sentence. The finite verb (predicate) is the structural center of the sentence and therefor of the tree. So starting with the predicate the algorithm searches through the dependency tree to find subject and object.","title":"Using the Triples for Sentence Analysis"},{"location":"modules/ravestate_nlp/#analyzing-a-sentence","text":"Example: 'Chickens like revolutions' reaction state Triple: subject: 'Chickens', predicate: 'like', object: 'revolutions' import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio @rs.state ( cond = nlp . prop_triples . changed (), # state reacts to a change in the 'triples' property read = nlp . prop_triples , # state is allowed to read the 'triples' property write = rawio . prop_out ) # state is allowed to write to the output chanel def postive_chicken ( ctx : ContextWrapper ): triple = ctx [ nlp . prop_triples ][ 0 ] # gives you the first Triple object # check predicate and object correspondingly # match_either_lemma() is a method in the Triple class if triple . match_either_lemma ( subj = { \"chicken\" , \"dinosaur\" }): # returns true when subj, pred or obj have the desired value ctx [ rawio . prop_out ] = \"You said something about a chicken, i like chickens!\"","title":"Analyzing a Sentence"},{"location":"modules/ravestate_nlp/#using-user-defined-signals-and-implementing-qa-states","text":"A user can define signals as needed and implement states reacting on them. If both a signal is emitted and something is written to rawio in one state, the state needs to be defined with emit_detached=True .","title":"Using User-defined Signals and Implementing Q/A-States"},{"location":"modules/ravestate_nlp/#react-to-user-defined-states-and-implement-qa","text":"Example: In one state a question is asks and then a user-defined signal is emitted that shows that the question has been stated. The other states reacts on the answer to this question. import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio chicken_signal = rs . Signal ( \"chicken signal\" ) @rs.state ( cond = nlp . prop_tokens . changed (), write = rawio . prop_out , signal = chicken_signal , emit_detached = True ) def ask_chicken_question ( ctx : rs . ContextWrapper ): ctx [ rawio . prop_out ] = \"Wanna know something awesome about chickens?\" return rs . Emit () @rs.state ( cond = chicken_signal & nlp . prop_yesno . changed (), read = nlp . prop_yesno , write = rawio . prop_out ) def answer_chicken_question ( ctx : rs . ContextWrapper ): if ctx [ nlp . prop_yesno ] == \"yes\" : ctx [ rawio . prop_out ] = \"Well, chicken's actually aren't that awesome.\" elif ctx [ nlp . prop_yesno ] == \"no\" : ctx [ rawio . prop_out ] = \"You're missing out on awesome chicken stories!\"","title":"React to User-defined States and Implement Q/A"},{"location":"modules/ravestate_nlp/#happy-language-processing-to-all-chickens-out-there","text":"","title":"Happy language processing to all chickens out there!"},{"location":"modules/ravestate_nlp/luigi/","text":"Sentence Processing and Feature Extraction Techniques for Ice Cream Selling This document discusses the possible sentence processing and feature extraction techniques applied with NLP module of ravestate library for ice cream selling. Yes-No Property This feature can be used to understand a given question's positive/negative answer to proceed such as: Do you want the usual? Do you want ice cream? Example: State that reads the \"yesno\" property import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio @rs.state ( cond = nlp . prop_yesno . changed (), # state reacts to a change in the 'yesno' property read = nlp . prop_yesno , # state is allowed to read the 'yesno' property write = rawio . prop_out ) # state is allowed to write to the output property def customer_wants_ice_cream ( ctx : ContextWrapper ): if ctx [ nlp . prop_yesno ] == \"yes\" : ctx [ rawio . prop_out ] = \"Your ice cream is coming right up!\" Lemmatization After tokenizing the sentences, roboy needs to match the strings in order to understand the context. Lemmatization allows to get the base form of words which is the required form for matching. Named Entity Recognition (NER) NER can be used in order to understand the quantity of ice cream, payment or any matter that requires specifics. The following figure includes the most possible types of use, please check https://spacy.io/api/annotation#named-entities for further types and for Spacy's interactive NER detector https://explosion.ai/demos/displacy-ent Type DESCRIPTION DATE Absolute or relative dates or periods. TIME Times smaller than a day. PERCENT Percentage, including \u201d%\u201c. MONEY Monetary values, including unit. QUANTITY Measurements, as of weight or distance. ORDINAL \u201cfirst\u201d, \u201csecond\u201d, etc. CARDINAL Numerals that do not fall under another type. Triple Extraction Triple Extraction provides object and subject applied with the verb. This allows us to have a wider perspective over the request because we can specifically understand which flavor is additionally or less requested. Consider the following scenarios: I need more vanilla ice cream Drop the chocolate please. This feature can be used to understand the requested flavors or payment options. Roboy's flavor and payment options are predefined so any match between our database and incoming token can help us to identify the requested options. Such as: \"vanilla\" \"chocolate\" \"lemon\" \"strawberry\" import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio prop_flavor = rs . Property ( name = \"flavor\" , allow_read = True , allow_write = True , always_signal_changed = True ) @rs.state ( cond = nlp . prop_triples . changed (), # state reacts to a change in the 'prop_triples' property read = nlp . prop_triples , # state is allowed to read the 'prop_triples' property write = prop_flavor , # state is allowed to write to the 'prop_flavor' property signal = prop_flavor . changed_signal ) # state signals 'prop_flavor' change signal def flavor_recognition_state ( ctx : ContextWrapper ): triple_match_result = ctx [ nlp . prop_triples ][ 0 ] . match_either_lemma ( obj = { \"vanilla\" , \"strawberry\" , \"lemon\" , \"chocolate\" }): objects = triple_match_result . objs if objects : ctx [ prop_flavor ] = objects return rs . Emit () or ... \"credit card\" \"cash\" \"bitcoin\" import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio prop_payment = rs . Property ( name = \"payment\" , allow_read = True , allow_write = True , always_signal_changed = True ) @rs.state ( cond = nlp . prop_triples . changed (), # state reacts to a change in the 'prop_triples' property read = nlp . prop_triples , # state is allowed to read the 'prop_triples' property write = prop_payment , # state is allowed to write to the 'prop_payment' property signal = prop_payment . changed_signal ) # state signals 'prop_payment' change signal def payment_recognition_state ( ctx : ContextWrapper ): triple_match_result = ctx [ nlp . prop_triples ][ 0 ] . match_either_lemma ( obj = { \"bitcoin\" , \"cash\" , \"card\" }): objects = triple_match_result . objs if objects and len ( objects ) == 1 : ctx [ prop_payment ] = objects [ 0 ] return rs . Emit () Alternatively by using triples, Roboy can understand whether the sentence is a request or not by only parsing the verb. For example if the customer uses one of the following phrases, it is easy to detect that customer has a request: \"would like\" \"can have\" \"can get\" \"will have\" \"choose\" \"wish\" \"desire\" \"prefer\"","title":"NLP: The Ice Cream Salesman"},{"location":"modules/ravestate_nlp/luigi/#sentence-processing-and-feature-extraction-techniques-for-ice-cream-selling","text":"This document discusses the possible sentence processing and feature extraction techniques applied with NLP module of ravestate library for ice cream selling.","title":"Sentence Processing and Feature Extraction Techniques for Ice Cream Selling"},{"location":"modules/ravestate_nlp/luigi/#yes-no-property","text":"This feature can be used to understand a given question's positive/negative answer to proceed such as: Do you want the usual? Do you want ice cream? Example: State that reads the \"yesno\" property import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio @rs.state ( cond = nlp . prop_yesno . changed (), # state reacts to a change in the 'yesno' property read = nlp . prop_yesno , # state is allowed to read the 'yesno' property write = rawio . prop_out ) # state is allowed to write to the output property def customer_wants_ice_cream ( ctx : ContextWrapper ): if ctx [ nlp . prop_yesno ] == \"yes\" : ctx [ rawio . prop_out ] = \"Your ice cream is coming right up!\"","title":"Yes-No Property"},{"location":"modules/ravestate_nlp/luigi/#lemmatization","text":"After tokenizing the sentences, roboy needs to match the strings in order to understand the context. Lemmatization allows to get the base form of words which is the required form for matching.","title":"Lemmatization"},{"location":"modules/ravestate_nlp/luigi/#named-entity-recognition-ner","text":"NER can be used in order to understand the quantity of ice cream, payment or any matter that requires specifics. The following figure includes the most possible types of use, please check https://spacy.io/api/annotation#named-entities for further types and for Spacy's interactive NER detector https://explosion.ai/demos/displacy-ent Type DESCRIPTION DATE Absolute or relative dates or periods. TIME Times smaller than a day. PERCENT Percentage, including \u201d%\u201c. MONEY Monetary values, including unit. QUANTITY Measurements, as of weight or distance. ORDINAL \u201cfirst\u201d, \u201csecond\u201d, etc. CARDINAL Numerals that do not fall under another type.","title":"Named Entity Recognition (NER)"},{"location":"modules/ravestate_nlp/luigi/#triple-extraction","text":"Triple Extraction provides object and subject applied with the verb. This allows us to have a wider perspective over the request because we can specifically understand which flavor is additionally or less requested. Consider the following scenarios: I need more vanilla ice cream Drop the chocolate please. This feature can be used to understand the requested flavors or payment options. Roboy's flavor and payment options are predefined so any match between our database and incoming token can help us to identify the requested options. Such as: \"vanilla\" \"chocolate\" \"lemon\" \"strawberry\" import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio prop_flavor = rs . Property ( name = \"flavor\" , allow_read = True , allow_write = True , always_signal_changed = True ) @rs.state ( cond = nlp . prop_triples . changed (), # state reacts to a change in the 'prop_triples' property read = nlp . prop_triples , # state is allowed to read the 'prop_triples' property write = prop_flavor , # state is allowed to write to the 'prop_flavor' property signal = prop_flavor . changed_signal ) # state signals 'prop_flavor' change signal def flavor_recognition_state ( ctx : ContextWrapper ): triple_match_result = ctx [ nlp . prop_triples ][ 0 ] . match_either_lemma ( obj = { \"vanilla\" , \"strawberry\" , \"lemon\" , \"chocolate\" }): objects = triple_match_result . objs if objects : ctx [ prop_flavor ] = objects return rs . Emit () or ... \"credit card\" \"cash\" \"bitcoin\" import ravestate as rs import ravestate_nlp as nlp import ravestate_rawio as rawio prop_payment = rs . Property ( name = \"payment\" , allow_read = True , allow_write = True , always_signal_changed = True ) @rs.state ( cond = nlp . prop_triples . changed (), # state reacts to a change in the 'prop_triples' property read = nlp . prop_triples , # state is allowed to read the 'prop_triples' property write = prop_payment , # state is allowed to write to the 'prop_payment' property signal = prop_payment . changed_signal ) # state signals 'prop_payment' change signal def payment_recognition_state ( ctx : ContextWrapper ): triple_match_result = ctx [ nlp . prop_triples ][ 0 ] . match_either_lemma ( obj = { \"bitcoin\" , \"cash\" , \"card\" }): objects = triple_match_result . objs if objects and len ( objects ) == 1 : ctx [ prop_payment ] = objects [ 0 ] return rs . Emit () Alternatively by using triples, Roboy can understand whether the sentence is a request or not by only parsing the verb. For example if the customer uses one of the following phrases, it is easy to detect that customer has a request: \"would like\" \"can have\" \"can get\" \"will have\" \"choose\" \"wish\" \"desire\" \"prefer\"","title":"Triple Extraction"},{"location":"modules/ravestate_ros1/","text":"_____ ____ _____ __ | __ \\ / __ \\ / ____ | / _ | | | __ ) | | | | | | ( ___ | | | _ / | | | | \\ ___ \\ | | | | \\ \\ | | __ | | ____ ) | | | | _ | \\ _ \\ \\ ____ / | _____ / | _ | ROS1 The ROS1 module enables easy integration of Publishers, Subscribers and Service Calls into Ravestate. It provides 3 Subclasses of property: Ros1SubProperty, Ros1PubProperty and Ros1CallProperty Using the Properties Ros1SubProperty Subclass of property that is synchronized with a given ROS1-Topic. Whenever it receives a new message from the topic, the value is written to the property and a changed-Signal is emitted. Example: Property that subscribes to the ROS topic chatter of type std_msgs/String and state that reacts upon new messages import ravestate as rs import ravestate_rawio as rawio from ravestate_ros1 import Ros1SubProperty from std_msgs.msg import String # Import ROS message type with rs . Module ( name = \"ros_chatter\" ): prop_subscriber = Ros1SubProperty ( name = \"subscriber\" , topic = 'chatter' , msg_type = String ) @rs.state ( read = prop_subscriber , write = rawio . prop_out ) def react_to_message ( ctx : rs . ContextWrapper ): message = ctx [ prop_subscriber . changed ()] # message is of type std_msgs.msg.String ctx [ rawio . prop_out ] = f \"Received on ROS-topic chatter: {message.data}\" Ros1PubProperty Subclass of property that publishes all values written to it to a given ROS1-Topic. Example: Property that publishes to the ROS topic chatter of type std_msgs/String and state that writes all outputs of the dialog system to the property import ravestate as rs import ravestate_rawio as rawio from ravestate_ros1 import Ros1PubProperty from std_msgs.msg import String # Import ROS message type with rs . Module ( name = \"ros_chatter\" ): prop_publisher = Ros1PubProperty ( name = \"publisher\" , topic = 'chatter' , msg_type = String ) @rs.state ( read = rawio . prop_out , write = prop_publisher ) def ros_output ( ctx : rs . ContextWrapper ): # create message of type std_msgs.msg.String message = String ( data = ctx [ rawio . prop_out . changed ()]) ctx [ prop_publisher ] = message Ros1CallProperty Subclass of property that calls a ROS-Service when a value is written to it, blocks until it gets the response, writes back the response into the property and returns. Build Request, can be of different form (see example): Parameters as a dict Parameters as ordered sequence Matching Request-Type Write Request to property, this blocks until a response is received and written back into the property. If no response is received (timeout is 10 seconds per default, can be set with call_timeout), None is written back into the property. Now the response can be read from the property. Check if it is None because service was unavailable! Example: Property connected to the ROS service /add_two_ints of type rospy_tutorials/AddTwoInts and state that calls the Service through the property when the input is \"add\" import ravestate as rs import ravestate_rawio as rawio from ravestate_ros1 import Ros1CallProperty # Import ROS service type from rospy_tutorials.srv import AddTwoInts , AddTwoIntsRequest with rs . Module ( name = \"add_two_ints\" ): prop_addtwoints = Ros1CallProperty ( name = \"addtwoints\" , service_name = \"/add_two_ints\" , service_type = AddTwoInts , call_timeout = 5.0 ) # prop_addtwoints has to be in read and write @rs.state ( cond = rawio . prop_in . changed (), read = ( rawio . prop_in , prop_addtwoints ), write = ( rawio . prop_out , prop_addtwoints )) def add_two_ints ( ctx ): if ctx [ rawio . prop_in . changed ()] == \"add\" : # 1. Build Request # Option 1: Parameters as dict request = { 'a' : 11 , 'b' : 232 } # Option 2: Parameters as ordered sequence request = ( 1 , 2 ) # Option 3: Matching Request-Type request = AddTwoIntsRequest ( 1 , 2 ) # 2. Write Request to property. # This blocks until Response is written into property or service call timed out. ctx [ prop_addtwoints ] = request # 3. Read Response (of type rospy_tutorials.srv.AddTwoIntsResponse) from property response = ctx [ prop_addtwoints ] # Check if response is None if response : ctx [ rawio . prop_out ] = response . sum Happy ROS-Ravestate Integration","title":"ROS"},{"location":"modules/ravestate_ros1/#ros1","text":"The ROS1 module enables easy integration of Publishers, Subscribers and Service Calls into Ravestate. It provides 3 Subclasses of property: Ros1SubProperty, Ros1PubProperty and Ros1CallProperty","title":"ROS1"},{"location":"modules/ravestate_ros1/#using-the-properties","text":"","title":"Using the Properties"},{"location":"modules/ravestate_ros1/#ros1subproperty","text":"Subclass of property that is synchronized with a given ROS1-Topic. Whenever it receives a new message from the topic, the value is written to the property and a changed-Signal is emitted. Example: Property that subscribes to the ROS topic chatter of type std_msgs/String and state that reacts upon new messages import ravestate as rs import ravestate_rawio as rawio from ravestate_ros1 import Ros1SubProperty from std_msgs.msg import String # Import ROS message type with rs . Module ( name = \"ros_chatter\" ): prop_subscriber = Ros1SubProperty ( name = \"subscriber\" , topic = 'chatter' , msg_type = String ) @rs.state ( read = prop_subscriber , write = rawio . prop_out ) def react_to_message ( ctx : rs . ContextWrapper ): message = ctx [ prop_subscriber . changed ()] # message is of type std_msgs.msg.String ctx [ rawio . prop_out ] = f \"Received on ROS-topic chatter: {message.data}\"","title":"Ros1SubProperty"},{"location":"modules/ravestate_ros1/#ros1pubproperty","text":"Subclass of property that publishes all values written to it to a given ROS1-Topic. Example: Property that publishes to the ROS topic chatter of type std_msgs/String and state that writes all outputs of the dialog system to the property import ravestate as rs import ravestate_rawio as rawio from ravestate_ros1 import Ros1PubProperty from std_msgs.msg import String # Import ROS message type with rs . Module ( name = \"ros_chatter\" ): prop_publisher = Ros1PubProperty ( name = \"publisher\" , topic = 'chatter' , msg_type = String ) @rs.state ( read = rawio . prop_out , write = prop_publisher ) def ros_output ( ctx : rs . ContextWrapper ): # create message of type std_msgs.msg.String message = String ( data = ctx [ rawio . prop_out . changed ()]) ctx [ prop_publisher ] = message","title":"Ros1PubProperty"},{"location":"modules/ravestate_ros1/#ros1callproperty","text":"Subclass of property that calls a ROS-Service when a value is written to it, blocks until it gets the response, writes back the response into the property and returns. Build Request, can be of different form (see example): Parameters as a dict Parameters as ordered sequence Matching Request-Type Write Request to property, this blocks until a response is received and written back into the property. If no response is received (timeout is 10 seconds per default, can be set with call_timeout), None is written back into the property. Now the response can be read from the property. Check if it is None because service was unavailable! Example: Property connected to the ROS service /add_two_ints of type rospy_tutorials/AddTwoInts and state that calls the Service through the property when the input is \"add\" import ravestate as rs import ravestate_rawio as rawio from ravestate_ros1 import Ros1CallProperty # Import ROS service type from rospy_tutorials.srv import AddTwoInts , AddTwoIntsRequest with rs . Module ( name = \"add_two_ints\" ): prop_addtwoints = Ros1CallProperty ( name = \"addtwoints\" , service_name = \"/add_two_ints\" , service_type = AddTwoInts , call_timeout = 5.0 ) # prop_addtwoints has to be in read and write @rs.state ( cond = rawio . prop_in . changed (), read = ( rawio . prop_in , prop_addtwoints ), write = ( rawio . prop_out , prop_addtwoints )) def add_two_ints ( ctx ): if ctx [ rawio . prop_in . changed ()] == \"add\" : # 1. Build Request # Option 1: Parameters as dict request = { 'a' : 11 , 'b' : 232 } # Option 2: Parameters as ordered sequence request = ( 1 , 2 ) # Option 3: Matching Request-Type request = AddTwoIntsRequest ( 1 , 2 ) # 2. Write Request to property. # This blocks until Response is written into property or service call timed out. ctx [ prop_addtwoints ] = request # 3. Read Response (of type rospy_tutorials.srv.AddTwoIntsResponse) from property response = ctx [ prop_addtwoints ] # Check if response is None if response : ctx [ rawio . prop_out ] = response . sum","title":"Ros1CallProperty"},{"location":"modules/ravestate_ros1/#happy-ros-ravestate-integration","text":"","title":"Happy ROS-Ravestate Integration"},{"location":"modules/ravestate_telegramio/","text":"_______ _ _____ ____ | __ __ | | | | _ _ / __ \\ | | ___ | | ___ __ _ _ __ __ _ _ __ ___ | || | | | | |/ _ \\ |/ _ \\ / _ ` | '__/ _` | ' _ ` _ \\ | || | | | | | __ / | __ / ( _ | | | | ( _ | | | | | | | _ | || | __ | | | _ | \\ ___ | _ | \\ ___ | \\ __ , | _ | \\ __ , _ | _ | | _ | | _ | _____ \\ ____ / __ / | | ___ / TelegramIO The TelegramIO module enables ravestate to connect to a Telegram-Bot and chat to people there. The connection to Telegram is managed with python-telegram-bot Architecture There are two main modes for this module: * Single-Process-Mode: All chats share the same context * Multiprocess-Mode: Every chat creates its own context in a separate process Single-Process-Mode In this mode the module handles incoming text messages and pictures from all chats. Outgoing messages are sent to every currently active chat. Multiprocess-Mode In this mode the \"Master\" part of the module is running in the main process of ravestate. Whenever there is a new chat, a new instance of ravestate is started in a new process and a bidirectional pipe is set up to enable communication between the main process and the new child process. Only the main process is connected to the Telegram-Bot and therefore any incoming messages get forwarded to the corresponding child process via the pipe. The main process also forwards any incoming messages it receives through the pipe to the corresponding telegram chat. In order to clean up unused child processes, the main process kills child processes after a configurable amount of inactivity. Child processes running the TelegramIO-Module listen for incoming text messages or pictures on the pipe and write output to the pipe. They only exchange messages with a single chat (indirectly via the pipe). Abilities The TelegramIO module is able to handle incoming text messages as well as incoming pictures. The module creates an interlocutor node for every user it is chatting with, containing the user's telegram-id, username and full name if it is set by the user. Incoming text messages are simply written into the RawIO Input property. For incoming pictures, the picture is saved locally as a file and the filepath is written to the RawIO Pic_In Context property. Messages in the RawIO Output are sent to the Telegram Chat(s). Configuration There are 5 configurable parameters (see init .py ): * telegram-token : Put the Token of your Telegram-Bot here * all_in_one_context : True if Single-Process-Mode should be used, False if Multiprocess-Mode should be used. * child_conn : Not to be set in a config file or via the command line. Will be set by master process as a runtime_override. * child_config_files : If in Multiprocess-Mode, the config-paths listed here will be used when creating a new context for a new chat. * chat_lifetime : The timespan in minutes in which a chat will be kept active after the last message","title":"TelegramIO"},{"location":"modules/ravestate_telegramio/#telegramio","text":"The TelegramIO module enables ravestate to connect to a Telegram-Bot and chat to people there. The connection to Telegram is managed with python-telegram-bot","title":"TelegramIO"},{"location":"modules/ravestate_telegramio/#architecture","text":"There are two main modes for this module: * Single-Process-Mode: All chats share the same context * Multiprocess-Mode: Every chat creates its own context in a separate process","title":"Architecture"},{"location":"modules/ravestate_telegramio/#single-process-mode","text":"In this mode the module handles incoming text messages and pictures from all chats. Outgoing messages are sent to every currently active chat.","title":"Single-Process-Mode"},{"location":"modules/ravestate_telegramio/#multiprocess-mode","text":"In this mode the \"Master\" part of the module is running in the main process of ravestate. Whenever there is a new chat, a new instance of ravestate is started in a new process and a bidirectional pipe is set up to enable communication between the main process and the new child process. Only the main process is connected to the Telegram-Bot and therefore any incoming messages get forwarded to the corresponding child process via the pipe. The main process also forwards any incoming messages it receives through the pipe to the corresponding telegram chat. In order to clean up unused child processes, the main process kills child processes after a configurable amount of inactivity. Child processes running the TelegramIO-Module listen for incoming text messages or pictures on the pipe and write output to the pipe. They only exchange messages with a single chat (indirectly via the pipe).","title":"Multiprocess-Mode"},{"location":"modules/ravestate_telegramio/#abilities","text":"The TelegramIO module is able to handle incoming text messages as well as incoming pictures. The module creates an interlocutor node for every user it is chatting with, containing the user's telegram-id, username and full name if it is set by the user. Incoming text messages are simply written into the RawIO Input property. For incoming pictures, the picture is saved locally as a file and the filepath is written to the RawIO Pic_In Context property. Messages in the RawIO Output are sent to the Telegram Chat(s).","title":"Abilities"},{"location":"modules/ravestate_telegramio/#configuration","text":"There are 5 configurable parameters (see init .py ): * telegram-token : Put the Token of your Telegram-Bot here * all_in_one_context : True if Single-Process-Mode should be used, False if Multiprocess-Mode should be used. * child_conn : Not to be set in a config file or via the command line. Will be set by master process as a runtime_override. * child_config_files : If in Multiprocess-Mode, the config-paths listed here will be used when creating a new context for a new chat. * chat_lifetime : The timespan in minutes in which a chat will be kept active after the last message","title":"Configuration"},{"location":"modules/ravestate_verbaliser/","text":"_ _ _ | | | | ( _ ) _ _ _____ ____ | | __ _____ | | _ ___ _____ ____ | | | | ___ |/ ___ ) _ \\ ( ____ | || |/ ___ ) ___ |/ ___ ) \\ V /| ____ | | | | _ ) ) ___ | || | ___ | ____ | | \\ _ / | _____ ) _ | | ____ / \\ _____ | \\ _ ) _ ( ___ /| _____ ) _ | Verbaliser The Verbaliser produces Roboy's utterances. It diversifies the interactions with Roboy by randomizing the output given a specific intent. Using the Verbaliser Question-Answer Lists YAML files are used to define the actual utterances. In other words: they store everything Roboy can vocalise. To diversify his remarks the Verbaliser randomises similar outputs. The class QAPhrases retrieves the values from a YAML file and parses the containing phrases. Here is a template for such a YAML file: type : qa # remark types : question answering ( qa ) , phrases name : \" INTENT \" # intent or topic Q : # possible questions - \" Question phrasing 1 \" - \" Question phrasing 2 \" - \" Question phrasing 3 \" A : # answers SUCCESS : - \" Possible answer on success 1 \" - \" Possible answer on success 2 \" FAILURE : - \" Possible answer on failure \" FUP : # follow up questions ( for interlocutors that are already known to Roboy ) Q : - \" Possible follow up question \" A : - \" Possible follow up answer \" See more examples here . Example for Answering the Question: What happened to the Dinosaurs? Creating the YAML file: Fill in all the possible answers. type : qa name : \"DINO\" A : SUCCESS : - \"I am sure it was a mind-boggingly huge meteorite!\" - \"They smoked too much ash!\" - \"A vulcano had flatulences.\" - \"The chicken were stronger.\" FAILURE : - \"I have no idea what you just said.\" - \"Sorry, I am only interested in dinosaurs.\" Adding this file to the Verbaliser: In this case the file is going to be located in a folder of important facts. However, the single file can similarly be added by itself. The folder is in the same path as the python file adding it. from ravestate_verbaliser import verbaliser from os.path import realpath , dirname , join verbaliser . add_folder ( join ( dirname ( realpath ( __file__ )), \"important_facts_folder\" )) Using the Verbaliser for fancy outputs: This outputs an answer to the question. To understand how to analyse the context of the question have a look at the Natural Language Processing README if input_had_something_to_do_with_dinos and was_a_question : ctx [ \"rawio:out\" ] = verbaliser . get_random_successful_answer ( \"DINO\" ) else : ctx [ \"rawio:out\" ] = verbaliser . get_random_failure_answer ( \"DINO\" ) Possible conversation flow: Interlocutor : \"What happend to the Dinosaurs?\" Roboy : \"The chicken were stronger.\" Example for Extracting Phrase Lists The Verbaliser can also be used to get all the imported phrases for a specific intent as a list. Creating the phrases.yml: type : phrases name : \" dino \" opts : - \" Dinos can not scratch their backs. \" - \" Once upon a time these mind-bogglingly huge creatures wandered the earth. \" - \" The longest Dinosaur was the Argentiosaurus. \" --- type : phrases name : \" chicken \" opts : - \" Chickens are not completely flightless. \" - \" There are more chickens out there than programmers. \" - \" If I were a chicken for one day I would say: 'Puk Puk Pukaaak'. Adding the file to the Verbaliser: The YAML file is assumed to be located in the important_phrases folder. The folder is again in the same path as this python script: from ravestate_verbaliser import verbaliser from os.path import realpath , dirname , join verbaliser . add_file ( join ( dirname ( realpath ( __file__ )), \"important_phrases\" , \"phrases.yml\" )) Using the Verbaliser to get a list of phrases: Given a specific intent the Verbaliser can be used to return a list of phrases. import ravestate_verbaliser dino_list = ravestate_verbaliser . verbaliser . get_phrase_list ( 'dino' ) The verbaliser:intent Property The verbaliser:react_to_intent state produces a random phrase output for a given intent. All the possible intents are specified in YAML files that are located in the ravestate_phrases_basic_en folder . The state reads the verbaliser:intent property and outputs one random phrase in the list with that specific intent. It can therefor be triggered as follows: Let's assume that phrases.yml is now located in avestate_phrases_basic_en. @state ( cond = s ( \"triggered_by_some_signal\" ), write = \"verbaliser:intent\" ) def say_some_nice_chicken_suff ( ctx : ContextWrapper ): ctx [ \"verbaliser:intent\" ] = \"chicken\"","title":"Verbaliser"},{"location":"modules/ravestate_verbaliser/#verbaliser","text":"The Verbaliser produces Roboy's utterances. It diversifies the interactions with Roboy by randomizing the output given a specific intent.","title":"Verbaliser"},{"location":"modules/ravestate_verbaliser/#using-the-verbaliser","text":"","title":"Using the Verbaliser"},{"location":"modules/ravestate_verbaliser/#question-answer-lists","text":"YAML files are used to define the actual utterances. In other words: they store everything Roboy can vocalise. To diversify his remarks the Verbaliser randomises similar outputs. The class QAPhrases retrieves the values from a YAML file and parses the containing phrases. Here is a template for such a YAML file: type : qa # remark types : question answering ( qa ) , phrases name : \" INTENT \" # intent or topic Q : # possible questions - \" Question phrasing 1 \" - \" Question phrasing 2 \" - \" Question phrasing 3 \" A : # answers SUCCESS : - \" Possible answer on success 1 \" - \" Possible answer on success 2 \" FAILURE : - \" Possible answer on failure \" FUP : # follow up questions ( for interlocutors that are already known to Roboy ) Q : - \" Possible follow up question \" A : - \" Possible follow up answer \" See more examples here .","title":"Question-Answer Lists"},{"location":"modules/ravestate_verbaliser/#example-for-answering-the-question-what-happened-to-the-dinosaurs","text":"Creating the YAML file: Fill in all the possible answers. type : qa name : \"DINO\" A : SUCCESS : - \"I am sure it was a mind-boggingly huge meteorite!\" - \"They smoked too much ash!\" - \"A vulcano had flatulences.\" - \"The chicken were stronger.\" FAILURE : - \"I have no idea what you just said.\" - \"Sorry, I am only interested in dinosaurs.\" Adding this file to the Verbaliser: In this case the file is going to be located in a folder of important facts. However, the single file can similarly be added by itself. The folder is in the same path as the python file adding it. from ravestate_verbaliser import verbaliser from os.path import realpath , dirname , join verbaliser . add_folder ( join ( dirname ( realpath ( __file__ )), \"important_facts_folder\" )) Using the Verbaliser for fancy outputs: This outputs an answer to the question. To understand how to analyse the context of the question have a look at the Natural Language Processing README if input_had_something_to_do_with_dinos and was_a_question : ctx [ \"rawio:out\" ] = verbaliser . get_random_successful_answer ( \"DINO\" ) else : ctx [ \"rawio:out\" ] = verbaliser . get_random_failure_answer ( \"DINO\" ) Possible conversation flow: Interlocutor : \"What happend to the Dinosaurs?\" Roboy : \"The chicken were stronger.\"","title":"Example for Answering the Question: What happened to the Dinosaurs?"},{"location":"modules/ravestate_verbaliser/#example-for-extracting-phrase-lists","text":"The Verbaliser can also be used to get all the imported phrases for a specific intent as a list. Creating the phrases.yml: type : phrases name : \" dino \" opts : - \" Dinos can not scratch their backs. \" - \" Once upon a time these mind-bogglingly huge creatures wandered the earth. \" - \" The longest Dinosaur was the Argentiosaurus. \" --- type : phrases name : \" chicken \" opts : - \" Chickens are not completely flightless. \" - \" There are more chickens out there than programmers. \" - \" If I were a chicken for one day I would say: 'Puk Puk Pukaaak'. Adding the file to the Verbaliser: The YAML file is assumed to be located in the important_phrases folder. The folder is again in the same path as this python script: from ravestate_verbaliser import verbaliser from os.path import realpath , dirname , join verbaliser . add_file ( join ( dirname ( realpath ( __file__ )), \"important_phrases\" , \"phrases.yml\" )) Using the Verbaliser to get a list of phrases: Given a specific intent the Verbaliser can be used to return a list of phrases. import ravestate_verbaliser dino_list = ravestate_verbaliser . verbaliser . get_phrase_list ( 'dino' )","title":"Example for Extracting Phrase Lists"},{"location":"modules/ravestate_verbaliser/#the-verbaliserintent-property","text":"The verbaliser:react_to_intent state produces a random phrase output for a given intent. All the possible intents are specified in YAML files that are located in the ravestate_phrases_basic_en folder . The state reads the verbaliser:intent property and outputs one random phrase in the list with that specific intent. It can therefor be triggered as follows: Let's assume that phrases.yml is now located in avestate_phrases_basic_en. @state ( cond = s ( \"triggered_by_some_signal\" ), write = \"verbaliser:intent\" ) def say_some_nice_chicken_suff ( ctx : ContextWrapper ): ctx [ \"verbaliser:intent\" ] = \"chicken\"","title":"The verbaliser:intent Property"},{"location":"modules/ravestate_visionio/","text":"_ _ _____ ____ ( _ ) ( _ ) | _ _ / __ \\ _ _ _ ___ _ ___ _____ | || | | | | | | | |/ ___ ) |/ _ \\ | _ \\ | || | | | \\ V /| | ___ | ( ( _ ) ) | | | _ | || | __ | | \\ _ / | _ ( ___ /| _ | \\ ___ /| _ | | _ ) _____ \\ ____ / VisionIO The VisionIO module enables ravestate to connect to a Face Recognition ROS topic provided by face_oracle , based on which conversations can be initiated. Architecture OpenCV Video Stream ----> ( FaceOracle Client ) ( webcam_video_processor . py ) ---> ravestate_visionio | A / roboy / cognition / vision / visible_face_names | | ( ROS1 ) V | face_recognition | | name - confidence pairs V | facial feature vector | | | V | ( FaceOracle Websocket Server ) | A V | Load FaceVector - PrimKey | pairs from Redis | | | V | Match with Request | Vector via Pyroboy | FaceRec . match_face | | | V | [ Name for PrimKey from Scientio ] Dependencies VisionIO requires the following components to be running: ravestate or raveboard with ravestate_visionio module Neo4j backend for Scientio redis for persisting facial feature vectors face_oracle client and server Configuration VisionIO provides the following config keys: Key Default Description redis_host Host for Redis database. localhost redis_port Port for Redis database. 6379 redis_pass Password for Redis database Empty ros1-node Topic for Faces messages. /roboy/cognition/vision/visible_face_names min-confidence Minimum confidence below which someone will be a stranger. 0.85 How to run We recommend running VisionIO through one of the ravestate docker-compose profiles, which will start Neo4j , redis , and the face_oracle client and server automatically. Start the profile and visionio in docker as follows: > docker-compose up -d { profile } > docker exec -it rs bash > python3 -m ravestate ... To start face recognition, open localhost:8088/index.html in a browser (Chrome works best). This will give a visualisation of recognised faces, and simultaneously keep recognition running. Face recognition will only work as long as you can see it doing so! The docker-compose profiles differ per operating system: Profile rs-linux On Linux, a Webcam for VisionIO can simply be mapped into docker as a device. Per default, this will be video0 . If you want to change the device, map a new device in docker-compose.yml , and don't forget to change the FACEORACLE_VIDEO_DEVICE variable. Profile rs-macos On Mac, Docker can not natively access USB devices. Instead, live video can be streamed into the container via RTMP: Install/start Local RTMP Server Install ffmpeg via brew install ffmpeg Stream webcam via RTMP by starting ravestate/run_ffmpeg_stream.sh You can now start docker-compose up -d rs-macos . Using a video instead of a webcam feed If you don't have a webcam, you can use a video instead for debugging. Just set FACEORACLE_VIDEO_DEVICE for your particular platform profile to /ravestate/resources/obama.mp4 . Note, that after changing docker-compose.yml , you have to run docker-compose up -d with the --force-recreate flag.","title":"VisionIO"},{"location":"modules/ravestate_visionio/#visionio","text":"The VisionIO module enables ravestate to connect to a Face Recognition ROS topic provided by face_oracle , based on which conversations can be initiated.","title":"VisionIO"},{"location":"modules/ravestate_visionio/#architecture","text":"OpenCV Video Stream ----> ( FaceOracle Client ) ( webcam_video_processor . py ) ---> ravestate_visionio | A / roboy / cognition / vision / visible_face_names | | ( ROS1 ) V | face_recognition | | name - confidence pairs V | facial feature vector | | | V | ( FaceOracle Websocket Server ) | A V | Load FaceVector - PrimKey | pairs from Redis | | | V | Match with Request | Vector via Pyroboy | FaceRec . match_face | | | V | [ Name for PrimKey from Scientio ]","title":"Architecture"},{"location":"modules/ravestate_visionio/#dependencies","text":"VisionIO requires the following components to be running: ravestate or raveboard with ravestate_visionio module Neo4j backend for Scientio redis for persisting facial feature vectors face_oracle client and server","title":"Dependencies"},{"location":"modules/ravestate_visionio/#configuration","text":"VisionIO provides the following config keys: Key Default Description redis_host Host for Redis database. localhost redis_port Port for Redis database. 6379 redis_pass Password for Redis database Empty ros1-node Topic for Faces messages. /roboy/cognition/vision/visible_face_names min-confidence Minimum confidence below which someone will be a stranger. 0.85","title":"Configuration"},{"location":"modules/ravestate_visionio/#how-to-run","text":"We recommend running VisionIO through one of the ravestate docker-compose profiles, which will start Neo4j , redis , and the face_oracle client and server automatically. Start the profile and visionio in docker as follows: > docker-compose up -d { profile } > docker exec -it rs bash > python3 -m ravestate ... To start face recognition, open localhost:8088/index.html in a browser (Chrome works best). This will give a visualisation of recognised faces, and simultaneously keep recognition running. Face recognition will only work as long as you can see it doing so! The docker-compose profiles differ per operating system:","title":"How to run"},{"location":"modules/ravestate_visionio/#profile-rs-linux","text":"On Linux, a Webcam for VisionIO can simply be mapped into docker as a device. Per default, this will be video0 . If you want to change the device, map a new device in docker-compose.yml , and don't forget to change the FACEORACLE_VIDEO_DEVICE variable.","title":"Profile rs-linux"},{"location":"modules/ravestate_visionio/#profile-rs-macos","text":"On Mac, Docker can not natively access USB devices. Instead, live video can be streamed into the container via RTMP: Install/start Local RTMP Server Install ffmpeg via brew install ffmpeg Stream webcam via RTMP by starting ravestate/run_ffmpeg_stream.sh You can now start docker-compose up -d rs-macos .","title":"Profile rs-macos"},{"location":"modules/ravestate_visionio/#using-a-video-instead-of-a-webcam-feed","text":"If you don't have a webcam, you can use a video instead for debugging. Just set FACEORACLE_VIDEO_DEVICE for your particular platform profile to /ravestate/resources/obama.mp4 . Note, that after changing docker-compose.yml , you have to run docker-compose up -d with the --force-recreate flag.","title":"Using a video instead of a webcam feed"},{"location":"modules/ravestate_wildtalk/","text":"_ _ _____ _ ______ _____ ___ _ _ __ | | | || _ _ || | | _ \\ | _ _ | / _ \\ | | | | / / | | | | | | | | | | | | | | / / _ \\ \\ | | | |/ / | |/ \\ | | | | | | | | | | | | | _ || | | \\ \\ / \\ / _ | | _ | | ____ | |/ / | | | | | || | ____ | | \\ \\ \\ / \\ / \\ ___ / \\ _____ /| ___ / \\ _ / \\ _ | | _ / \\ _____ / \\ _ | \\ _ / WILDTALK The wildtalk module module generates responses for given input using the given model. In order to change the model used for generation, change this option: Option Description model one of \"convai_gpt\", \"gpt2\", \"parlai\" If no separate wildtalk server is running, this module starts a wildtalk server which can be used by multiple ravestate instances. Available Models ConvAI GPT This model is based on transfer-learning-conv-ai It can be used by setting model to \"convai_gpt\" Option Description temperature higher value -> more variation in output max_length maximal length of generated output top_k <=0: no filtering, >0: keep only top k tokens with highest probability. top_p <=0.0 no filtering, >0.0: keep smallest subset whose total probability mass >= top_p max_history maximal number of previous dialog turns to be used for output generation GPT2 This model uses the gpt2 transformer and model from pytorch_transformers . It uses the gpt2-medium dataset. It can be used by setting model to \"gpt2\" Option Description temperature higher value -> more variation in output max_length maximal length of generated output top_k <=0: no filtering, >0: keep only top k tokens with highest probability. Parlai This model uses roboy-parlai . It can be used by setting model to \"parlai\" Option Description temperature higher value -> more variation in output max_length maximal length of generated output top_k <=0: no filtering, >0: keep only top k tokens with highest probability. Separate Server A separate server can be used for running the wildtalk generation. This can be configured with the following options: Option Description server_address Address under which the server is accessible server_port Port of the server To start a standalone server for wildtalk generation, execute this in the /modules folder of ravestate: python -c \"from ravestate_wildtalk import server; server.run(port=, model=) Note that the model used on server startup will be the model used when accessed from ravestate.","title":"Wildtalk"},{"location":"modules/ravestate_wildtalk/#wildtalk","text":"The wildtalk module module generates responses for given input using the given model. In order to change the model used for generation, change this option: Option Description model one of \"convai_gpt\", \"gpt2\", \"parlai\" If no separate wildtalk server is running, this module starts a wildtalk server which can be used by multiple ravestate instances.","title":"WILDTALK"},{"location":"modules/ravestate_wildtalk/#available-models","text":"","title":"Available Models"},{"location":"modules/ravestate_wildtalk/#convai-gpt","text":"This model is based on transfer-learning-conv-ai It can be used by setting model to \"convai_gpt\" Option Description temperature higher value -> more variation in output max_length maximal length of generated output top_k <=0: no filtering, >0: keep only top k tokens with highest probability. top_p <=0.0 no filtering, >0.0: keep smallest subset whose total probability mass >= top_p max_history maximal number of previous dialog turns to be used for output generation","title":"ConvAI GPT"},{"location":"modules/ravestate_wildtalk/#gpt2","text":"This model uses the gpt2 transformer and model from pytorch_transformers . It uses the gpt2-medium dataset. It can be used by setting model to \"gpt2\" Option Description temperature higher value -> more variation in output max_length maximal length of generated output top_k <=0: no filtering, >0: keep only top k tokens with highest probability.","title":"GPT2"},{"location":"modules/ravestate_wildtalk/#parlai","text":"This model uses roboy-parlai . It can be used by setting model to \"parlai\" Option Description temperature higher value -> more variation in output max_length maximal length of generated output top_k <=0: no filtering, >0: keep only top k tokens with highest probability.","title":"Parlai"},{"location":"modules/ravestate_wildtalk/#separate-server","text":"A separate server can be used for running the wildtalk generation. This can be configured with the following options: Option Description server_address Address under which the server is accessible server_port Port of the server To start a standalone server for wildtalk generation, execute this in the /modules folder of ravestate: python -c \"from ravestate_wildtalk import server; server.run(port=, model=) Note that the model used on server startup will be the model used when accessed from ravestate.","title":"Separate Server"}]}
\ No newline at end of file
diff --git a/docs/sitemap.xml b/docs/sitemap.xml
index 6e5179a..65ad4eb 100644
--- a/docs/sitemap.xml
+++ b/docs/sitemap.xml
@@ -2,52 +2,77 @@
None
- 2019-05-15
+ 2019-10-21dailyNone
- 2019-05-15
+ 2019-10-21dailyNone
- 2019-05-15
+ 2019-10-21dailyNone
- 2019-05-15
+ 2019-10-21dailyNone
- 2019-05-15
+ 2019-10-21dailyNone
- 2019-05-15
+ 2019-10-21dailyNone
- 2019-05-15
+ 2019-10-21dailyNone
- 2019-05-15
+ 2019-10-21dailyNone
- 2019-05-15
+ 2019-10-21dailyNone
- 2019-05-15
+ 2019-10-21
+ daily
+
+
+ None
+ 2019-10-21
+ daily
+
+
+ None
+ 2019-10-21
+ daily
+
+
+ None
+ 2019-10-21
+ daily
+
+
+ None
+ 2019-10-21
+ daily
+
+
+ None
+ 2019-10-21daily
\ No newline at end of file
diff --git a/docs/sitemap.xml.gz b/docs/sitemap.xml.gz
index 86cccab..207fd53 100644
Binary files a/docs/sitemap.xml.gz and b/docs/sitemap.xml.gz differ
diff --git a/docs/states/index.html b/docs/states/index.html
index 0dc6869..b8b1989 100644
--- a/docs/states/index.html
+++ b/docs/states/index.html
@@ -194,6 +194,18 @@
+
Decorator to declare a new state, which may emit a certain signal,
write to a certain set of properties (calling write, push, pop),
and read from certain properties (calling read).
Example (Module that outputs "Don't Panic" after startup):
Signal reference. Almost the same as a signal, except that
it will not try to auto-discover it's module out of thread-local context
@@ -406,32 +496,37 @@
SignalRef
without assigning that signal to the contextual module.
Conjunct
-
Conjunct(self, *args)
-
+
Conjunct(self,*args)
+
+
Class that represents a Conjunction of Signals.
Can be constructed using an overloaded & operator.
Example:
-
signal_A & signal_B
-
+
signal_A&signal_B
+
+
Disjunct
-
Disjunct(self, *args)
-
+
Disjunct(self,*args)
+
+
Class that represents a Disjunction of Conjunctions
Can be constructed using an overloaded | operator.
A receptor is a special state which can be invoked from outside,
to push values into the context.
@@ -445,18 +540,18 @@
receptor
Example:
-
# Module that outputs "Don't Panic" one minute after startup
-# Because a receptor is used, the OUTPUT_PROPERTY is not blocked the whole time
-with Module(name="my_module"):
- @state(cond=startup())
- def after_startup(context):
- @receptor(ctx_wrap=context, write=OUTPUT_PROPERTY)
- def dont_panic(ctx_write):
- ctx_write[OUTPUT_PROPERTY] = "Don't Panic"
-
- sleep(60)
- dont_panic()
-
+
# Module that outputs "Don't Panic" one minute after startup
+# Because a receptor is used, the OUTPUT_PROPERTY is not blocked the whole time
+withModule(name="my_module"):
+ @state(cond=startup())
+ defafter_startup(context):
+ @receptor(ctx_wrap=context,write=OUTPUT_PROPERTY)
+ defdont_panic(ctx_write):
+ ctx_write[OUTPUT_PROPERTY]="Don't Panic"
+
+ sleep(60)
+ dont_panic()
+
diff --git a/modules/raveboard/.gitignore b/modules/raveboard/.gitignore
new file mode 100644
index 0000000..3c5606f
--- /dev/null
+++ b/modules/raveboard/.gitignore
@@ -0,0 +1,46 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# compiled output
+/tmp
+/out-tsc
+# Only exists if Bazel was run
+/bazel-out
+
+# dependencies
+/node_modules
+
+# profiling files
+chrome-profiler-events.json
+speed-measure-plugin.json
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+.history/*
+
+# misc
+/.sass-cache
+/connect.lock
+/coverage
+/libpeerconnection.log
+npm-debug.log
+yarn-error.log
+testem.log
+/typings
+package-lock.json
+
+# System Files
+.DS_Store
+Thumbs.db
diff --git a/modules/raveboard/README.md b/modules/raveboard/README.md
new file mode 100644
index 0000000..a9b7aba
--- /dev/null
+++ b/modules/raveboard/README.md
@@ -0,0 +1,34 @@
+# Raveboard
+
+Roboy ravestate UI module.
+
+## Usage
+
+Substitute `ravestate` with `raveboard` when launching your module config. E.g.
+
+```bash
+python3 -m raveboard ravestate_wildtalk ravestate_hibye
+```
+
+If you did not override the `raveboard_port` config for the `core` module, you may
+access the UI in any browser under [localhost:42424/ravestate/index.html](http://localhost:4242/ravestate/index.html)
+Otherwise, just substitute 42424 with your configured port.
+
+## Setup, Development and Building
+
+This project is built with Angular 8 and TypeScript.
+
+Initial setup:
+- install Angular CLI globally by running `npm install -g @angular/cli` (don't forget to add `/.npm-global/bin` to your path)
+- Install project dependencies by running `npm install` in the root folder (= folder with `package.json`)
+
+To start development:
+- run `ng serve` in the root folder to start a dev server
+- Navigate to `http://localhost:4200/` in the browser to open the UI
+- The app will automatically reload if you change any of the source files
+
+To build a production version:
+- run `ng build --prod` in the root folder to build a bundle for the browser
+- the bundle is saved in the `dist` folder
+
+
diff --git a/test/modules/ravestate_hibye/__init__.py b/modules/raveboard/__init__.py
similarity index 100%
rename from test/modules/ravestate_hibye/__init__.py
rename to modules/raveboard/__init__.py
diff --git a/modules/raveboard/__main__.py b/modules/raveboard/__main__.py
new file mode 100644
index 0000000..edf228b
--- /dev/null
+++ b/modules/raveboard/__main__.py
@@ -0,0 +1,5 @@
+import sys
+from raveboard.ui_context import UIContext
+
+ctx = UIContext(*sys.argv[1:])
+ctx.run()
diff --git a/modules/raveboard/angular.json b/modules/raveboard/angular.json
new file mode 100644
index 0000000..077476c
--- /dev/null
+++ b/modules/raveboard/angular.json
@@ -0,0 +1,93 @@
+{
+ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+ "version": 1,
+ "newProjectRoot": "projects",
+ "projects": {
+ "ravestate": {
+ "projectType": "application",
+ "schematics": {
+ "@schematics/angular:component": {
+ "style": "scss"
+ }
+ },
+ "root": "",
+ "sourceRoot": "src",
+ "prefix": "app",
+ "architect": {
+ "build": {
+ "builder": "@angular-devkit/build-angular:browser",
+ "options": {
+ "outputPath": "dist/ravestate",
+ "index": "src/index.html",
+ "main": "src/main.ts",
+ "polyfills": "src/polyfills.ts",
+ "tsConfig": "tsconfig.json",
+ "assets": [
+ "src/favicon.ico",
+ "src/assets"
+ ],
+ "styles": [
+ "src/styles.scss"
+ ],
+ "scripts": []
+ },
+ "configurations": {
+ "production": {
+ "fileReplacements": [
+ {
+ "replace": "src/environments/environment.ts",
+ "with": "src/environments/environment.prod.ts"
+ }
+ ],
+ "optimization": true,
+ "outputHashing": "all",
+ "sourceMap": false,
+ "extractCss": true,
+ "namedChunks": false,
+ "aot": true,
+ "extractLicenses": true,
+ "vendorChunk": false,
+ "buildOptimizer": true,
+ "budgets": [
+ {
+ "type": "initial",
+ "maximumWarning": "2mb",
+ "maximumError": "5mb"
+ }
+ ]
+ }
+ }
+ },
+ "serve": {
+ "builder": "@angular-devkit/build-angular:dev-server",
+ "options": {
+ "browserTarget": "ravestate:build"
+ },
+ "configurations": {
+ "production": {
+ "browserTarget": "ravestate:build:production"
+ }
+ }
+ },
+ "extract-i18n": {
+ "builder": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "browserTarget": "ravestate:build"
+ }
+ },
+ "lint": {
+ "builder": "@angular-devkit/build-angular:tslint",
+ "options": {
+ "tsConfig": [
+ "tsconfig.json",
+ "tsconfig.spec.json"
+ ],
+ "exclude": [
+ "**/node_modules/**"
+ ]
+ }
+ }
+ }
+ }},
+ "defaultProject": "ravestate"
+}
\ No newline at end of file
diff --git a/modules/raveboard/dist/ravestate/3rdpartylicenses.txt b/modules/raveboard/dist/ravestate/3rdpartylicenses.txt
new file mode 100644
index 0000000..0cdbc25
--- /dev/null
+++ b/modules/raveboard/dist/ravestate/3rdpartylicenses.txt
@@ -0,0 +1,649 @@
+@angular/common
+MIT
+
+@angular/core
+MIT
+
+@angular/forms
+MIT
+
+@angular/platform-browser
+MIT
+
+after
+MIT
+Copyright (c) 2011 Raynos.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+arraybuffer.slice
+MIT
+Copyright (C) 2013 Rase-
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+backo2
+MIT
+
+base64-arraybuffer
+MIT
+Copyright (c) 2012 Niklas von Hertzen
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+
+blob
+MIT
+MIT License
+
+Copyright (C) 2014 Rase-
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+component-bind
+
+component-emitter
+MIT
+(The MIT License)
+
+Copyright (c) 2014 Component contributors
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+
+component-inherit
+
+debug
+MIT
+(The MIT License)
+
+Copyright (c) 2014 TJ Holowaychuk
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software
+and associated documentation files (the 'Software'), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial
+portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
+LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+
+engine.io-client
+MIT
+(The MIT License)
+
+Copyright (c) 2014-2015 Automattic
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+engine.io-parser
+MIT
+(The MIT License)
+
+Copyright (c) 2016 Guillermo Rauch (@rauchg)
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+has-binary2
+MIT
+The MIT License (MIT)
+
+Copyright (c) 2014 Kevin Roark
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+has-cors
+MIT
+
+indexof
+
+isarray
+MIT
+
+ms
+MIT
+The MIT License (MIT)
+
+Copyright (c) 2016 Zeit, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+parseqs
+MIT
+The MIT License (MIT)
+
+Copyright (c) 2015 Gal Koren
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+
+parseuri
+MIT
+The MIT License (MIT)
+
+Copyright (c) 2014 Gal Koren
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+
+rxjs
+Apache-2.0
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+
+
+socket.io-client
+MIT
+The MIT License (MIT)
+
+Copyright (c) 2014 Guillermo Rauch
+
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+socket.io-parser
+MIT
+(The MIT License)
+
+Copyright (c) 2014 Guillermo Rauch
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the 'Software'), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+to-array
+MIT
+Copyright (c) 2012 Raynos.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+yeast
+MIT
+The MIT License (MIT)
+
+Copyright (c) 2015 Unshift.io, Arnout Kazemier, the Contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+
+zone.js
+MIT
+The MIT License
+
+Copyright (c) 2016-2018 Google, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/modules/raveboard/dist/ravestate/favicon.ico b/modules/raveboard/dist/ravestate/favicon.ico
new file mode 100644
index 0000000..ee4ce99
Binary files /dev/null and b/modules/raveboard/dist/ravestate/favicon.ico differ
diff --git a/modules/raveboard/dist/ravestate/index.html b/modules/raveboard/dist/ravestate/index.html
new file mode 100644
index 0000000..e277c9c
--- /dev/null
+++ b/modules/raveboard/dist/ravestate/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+ Raveboard
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/raveboard/dist/ravestate/main.e5cf80332fe66f7c26b7.js b/modules/raveboard/dist/ravestate/main.e5cf80332fe66f7c26b7.js
new file mode 100644
index 0000000..dc8b0a1
--- /dev/null
+++ b/modules/raveboard/dist/ravestate/main.e5cf80332fe66f7c26b7.js
@@ -0,0 +1 @@
+(window.webpackJsonp=window.webpackJsonp||[]).push([[1],{"+SKG":function(e,t){e.exports=function(e){return n&&Buffer.isBuffer(e)||r&&(e instanceof ArrayBuffer||s(e))};var n="function"==typeof Buffer&&"function"==typeof Buffer.isBuffer,r="function"==typeof ArrayBuffer,s=function(e){return"function"==typeof ArrayBuffer.isView?ArrayBuffer.isView(e):e.buffer instanceof ArrayBuffer}},0:function(e,t,n){e.exports=n("zUnb")},"0z79":function(e,t,n){var r=n("AdPF"),s=n("CUme"),o=n("cpc2"),i=n("Yvos"),a=n("NOtv")("engine.io-client:polling-xhr");function l(){}function c(e){if(s.call(this,e),this.requestTimeout=e.requestTimeout,this.extraHeaders=e.extraHeaders,"undefined"!=typeof location){var t="https:"===location.protocol,n=location.port;n||(n=t?443:80),this.xd="undefined"!=typeof location&&e.hostname!==location.hostname||n!==e.port,this.xs=e.secure!==t}}function u(e){this.method=e.method||"GET",this.uri=e.uri,this.xd=!!e.xd,this.xs=!!e.xs,this.async=!1!==e.async,this.data=void 0!==e.data?e.data:null,this.agent=e.agent,this.isBinary=e.isBinary,this.supportsBinary=e.supportsBinary,this.enablesXDR=e.enablesXDR,this.requestTimeout=e.requestTimeout,this.pfx=e.pfx,this.key=e.key,this.passphrase=e.passphrase,this.cert=e.cert,this.ca=e.ca,this.ciphers=e.ciphers,this.rejectUnauthorized=e.rejectUnauthorized,this.extraHeaders=e.extraHeaders,this.create()}if(e.exports=c,e.exports.Request=u,i(c,s),c.prototype.supportsBinary=!0,c.prototype.request=function(e){return(e=e||{}).uri=this.uri(),e.xd=this.xd,e.xs=this.xs,e.agent=this.agent||!1,e.supportsBinary=this.supportsBinary,e.enablesXDR=this.enablesXDR,e.pfx=this.pfx,e.key=this.key,e.passphrase=this.passphrase,e.cert=this.cert,e.ca=this.ca,e.ciphers=this.ciphers,e.rejectUnauthorized=this.rejectUnauthorized,e.requestTimeout=this.requestTimeout,e.extraHeaders=this.extraHeaders,new u(e)},c.prototype.doWrite=function(e,t){var n=this.request({method:"POST",data:e,isBinary:"string"!=typeof e&&void 0!==e}),r=this;n.on("success",t),n.on("error",function(e){r.onError("xhr post error",e)}),this.sendXhr=n},c.prototype.doPoll=function(){a("xhr poll");var e=this.request(),t=this;e.on("data",function(e){t.onData(e)}),e.on("error",function(e){t.onError("xhr poll error",e)}),this.pollXhr=e},o(u.prototype),u.prototype.create=function(){var e={agent:this.agent,xdomain:this.xd,xscheme:this.xs,enablesXDR:this.enablesXDR};e.pfx=this.pfx,e.key=this.key,e.passphrase=this.passphrase,e.cert=this.cert,e.ca=this.ca,e.ciphers=this.ciphers,e.rejectUnauthorized=this.rejectUnauthorized;var t=this.xhr=new r(e),n=this;try{a("xhr open %s: %s",this.method,this.uri),t.open(this.method,this.uri,this.async);try{if(this.extraHeaders)for(var s in t.setDisableHeaderCheck&&t.setDisableHeaderCheck(!0),this.extraHeaders)this.extraHeaders.hasOwnProperty(s)&&t.setRequestHeader(s,this.extraHeaders[s])}catch(o){}if("POST"===this.method)try{t.setRequestHeader("Content-type",this.isBinary?"application/octet-stream":"text/plain;charset=UTF-8")}catch(o){}try{t.setRequestHeader("Accept","*/*")}catch(o){}"withCredentials"in t&&(t.withCredentials=!0),this.requestTimeout&&(t.timeout=this.requestTimeout),this.hasXDR()?(t.onload=function(){n.onLoad()},t.onerror=function(){n.onError(t.responseText)}):t.onreadystatechange=function(){if(2===t.readyState)try{var e=t.getResponseHeader("Content-Type");n.supportsBinary&&"application/octet-stream"===e&&(t.responseType="arraybuffer")}catch(o){}4===t.readyState&&(200===t.status||1223===t.status?n.onLoad():setTimeout(function(){n.onError(t.status)},0))},a("xhr data %s",this.data),t.send(this.data)}catch(o){return void setTimeout(function(){n.onError(o)},0)}"undefined"!=typeof document&&(this.index=u.requestsCount++,u.requests[this.index]=this)},u.prototype.onSuccess=function(){this.emit("success"),this.cleanup()},u.prototype.onData=function(e){this.emit("data",e),this.onSuccess()},u.prototype.onError=function(e){this.emit("error",e),this.cleanup(!0)},u.prototype.cleanup=function(e){if(null!=this.xhr){if(this.hasXDR()?this.xhr.onload=this.xhr.onerror=l:this.xhr.onreadystatechange=l,e)try{this.xhr.abort()}catch(t){}"undefined"!=typeof document&&delete u.requests[this.index],this.xhr=null}},u.prototype.onLoad=function(){var e;try{var t;try{t=this.xhr.getResponseHeader("Content-Type")}catch(n){}e="application/octet-stream"===t&&this.xhr.response||this.xhr.responseText}catch(n){this.onError(n)}null!=e&&this.onData(e)},u.prototype.hasXDR=function(){return"undefined"!=typeof XDomainRequest&&!this.xs&&this.enablesXDR},u.prototype.abort=function(){this.cleanup()},u.requestsCount=0,u.requests={},"undefined"!=typeof document)if("function"==typeof attachEvent)attachEvent("onunload",d);else if("function"==typeof addEventListener){var h="onpagehide"in self?"pagehide":"unload";addEventListener(h,d,!1)}function d(){for(var e in u.requests)u.requests.hasOwnProperty(e)&&u.requests[e].abort()}},1:function(e,t){},"14A5":function(e,t){var n=void 0!==n?n:"undefined"!=typeof WebKitBlobBuilder?WebKitBlobBuilder:"undefined"!=typeof MSBlobBuilder?MSBlobBuilder:"undefined"!=typeof MozBlobBuilder&&MozBlobBuilder,r=function(){try{return 2===new Blob(["hi"]).size}catch(e){return!1}}(),s=r&&function(){try{return 2===new Blob([new Uint8Array([1,2])]).size}catch(e){return!1}}(),o=n&&n.prototype.append&&n.prototype.getBlob;function i(e){return e.map(function(e){if(e.buffer instanceof ArrayBuffer){var t=e.buffer;if(e.byteLength!==t.byteLength){var n=new Uint8Array(e.byteLength);n.set(new Uint8Array(t,e.byteOffset,e.byteLength)),t=n.buffer}return t}return e})}function a(e,t){t=t||{};var r=new n;return i(e).forEach(function(e){r.append(e)}),t.type?r.getBlob(t.type):r.getBlob()}function l(e,t){return new Blob(i(e),t||{})}"undefined"!=typeof Blob&&(a.prototype=Blob.prototype,l.prototype=Blob.prototype),e.exports=r?s?Blob:l:o?a:void 0},"2Dig":function(e,t){e.exports=function(e,t,n){return e.on(t,n),{destroy:function(){e.removeListener(t,n)}}}},"2pII":function(e,t,n){var r=n("akSB"),s=n("cpc2"),o=n("NOtv")("engine.io-client:socket"),i=n("7jRU"),a=n("Wm4p"),l=n("Uxeu"),c=n("TypT");function u(e,t){if(!(this instanceof u))return new u(e,t);t=t||{},e&&"object"==typeof e&&(t=e,e=null),e?(e=l(e),t.hostname=e.host,t.secure="https"===e.protocol||"wss"===e.protocol,t.port=e.port,e.query&&(t.query=e.query)):t.host&&(t.hostname=l(t.host).host),this.secure=null!=t.secure?t.secure:"undefined"!=typeof location&&"https:"===location.protocol,t.hostname&&!t.port&&(t.port=this.secure?"443":"80"),this.agent=t.agent||!1,this.hostname=t.hostname||("undefined"!=typeof location?location.hostname:"localhost"),this.port=t.port||("undefined"!=typeof location&&location.port?location.port:this.secure?443:80),this.query=t.query||{},"string"==typeof this.query&&(this.query=c.decode(this.query)),this.upgrade=!1!==t.upgrade,this.path=(t.path||"/engine.io").replace(/\/$/,"")+"/",this.forceJSONP=!!t.forceJSONP,this.jsonp=!1!==t.jsonp,this.forceBase64=!!t.forceBase64,this.enablesXDR=!!t.enablesXDR,this.timestampParam=t.timestampParam||"t",this.timestampRequests=t.timestampRequests,this.transports=t.transports||["polling","websocket"],this.transportOptions=t.transportOptions||{},this.readyState="",this.writeBuffer=[],this.prevBufferLen=0,this.policyPort=t.policyPort||843,this.rememberUpgrade=t.rememberUpgrade||!1,this.binaryType=null,this.onlyBinaryUpgrades=t.onlyBinaryUpgrades,this.perMessageDeflate=!1!==t.perMessageDeflate&&(t.perMessageDeflate||{}),!0===this.perMessageDeflate&&(this.perMessageDeflate={}),this.perMessageDeflate&&null==this.perMessageDeflate.threshold&&(this.perMessageDeflate.threshold=1024),this.pfx=t.pfx||null,this.key=t.key||null,this.passphrase=t.passphrase||null,this.cert=t.cert||null,this.ca=t.ca||null,this.ciphers=t.ciphers||null,this.rejectUnauthorized=void 0===t.rejectUnauthorized||t.rejectUnauthorized,this.forceNode=!!t.forceNode,this.isReactNative="undefined"!=typeof navigator&&"string"==typeof navigator.product&&"reactnative"===navigator.product.toLowerCase(),("undefined"==typeof self||this.isReactNative)&&(t.extraHeaders&&Object.keys(t.extraHeaders).length>0&&(this.extraHeaders=t.extraHeaders),t.localAddress&&(this.localAddress=t.localAddress)),this.id=null,this.upgrades=null,this.pingInterval=null,this.pingTimeout=null,this.pingIntervalTimer=null,this.pingTimeoutTimer=null,this.open()}e.exports=u,u.priorWebsocketSuccess=!1,s(u.prototype),u.protocol=a.protocol,u.Socket=u,u.Transport=n("Gbct"),u.transports=n("akSB"),u.parser=n("Wm4p"),u.prototype.createTransport=function(e){o('creating transport "%s"',e);var t=function(e){var t={};for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}(this.query);t.EIO=a.protocol,t.transport=e;var n=this.transportOptions[e]||{};return this.id&&(t.sid=this.id),new r[e]({query:t,socket:this,agent:n.agent||this.agent,hostname:n.hostname||this.hostname,port:n.port||this.port,secure:n.secure||this.secure,path:n.path||this.path,forceJSONP:n.forceJSONP||this.forceJSONP,jsonp:n.jsonp||this.jsonp,forceBase64:n.forceBase64||this.forceBase64,enablesXDR:n.enablesXDR||this.enablesXDR,timestampRequests:n.timestampRequests||this.timestampRequests,timestampParam:n.timestampParam||this.timestampParam,policyPort:n.policyPort||this.policyPort,pfx:n.pfx||this.pfx,key:n.key||this.key,passphrase:n.passphrase||this.passphrase,cert:n.cert||this.cert,ca:n.ca||this.ca,ciphers:n.ciphers||this.ciphers,rejectUnauthorized:n.rejectUnauthorized||this.rejectUnauthorized,perMessageDeflate:n.perMessageDeflate||this.perMessageDeflate,extraHeaders:n.extraHeaders||this.extraHeaders,forceNode:n.forceNode||this.forceNode,localAddress:n.localAddress||this.localAddress,requestTimeout:n.requestTimeout||this.requestTimeout,protocols:n.protocols||void 0,isReactNative:this.isReactNative})},u.prototype.open=function(){var e;if(this.rememberUpgrade&&u.priorWebsocketSuccess&&-1!==this.transports.indexOf("websocket"))e="websocket";else{if(0===this.transports.length){var t=this;return void setTimeout(function(){t.emit("error","No transports available")},0)}e=this.transports[0]}this.readyState="opening";try{e=this.createTransport(e)}catch(n){return this.transports.shift(),void this.open()}e.open(),this.setTransport(e)},u.prototype.setTransport=function(e){o("setting transport %s",e.name);var t=this;this.transport&&(o("clearing existing transport %s",this.transport.name),this.transport.removeAllListeners()),this.transport=e,e.on("drain",function(){t.onDrain()}).on("packet",function(e){t.onPacket(e)}).on("error",function(e){t.onError(e)}).on("close",function(){t.onClose("transport close")})},u.prototype.probe=function(e){o('probing transport "%s"',e);var t=this.createTransport(e,{probe:1}),n=!1,r=this;function s(){r.onlyBinaryUpgrades&&(n=n||!this.supportsBinary&&r.transport.supportsBinary),n||(o('probe transport "%s" opened',e),t.send([{type:"ping",data:"probe"}]),t.once("packet",function(s){if(!n)if("pong"===s.type&&"probe"===s.data){if(o('probe transport "%s" pong',e),r.upgrading=!0,r.emit("upgrading",t),!t)return;u.priorWebsocketSuccess="websocket"===t.name,o('pausing current transport "%s"',r.transport.name),r.transport.pause(function(){n||"closed"!==r.readyState&&(o("changing transport and sending upgrade packet"),d(),r.setTransport(t),t.send([{type:"upgrade"}]),r.emit("upgrade",t),t=null,r.upgrading=!1,r.flush())})}else{o('probe transport "%s" failed',e);var i=new Error("probe error");i.transport=t.name,r.emit("upgradeError",i)}}))}function i(){n||(n=!0,d(),t.close(),t=null)}function a(n){var s=new Error("probe error: "+n);s.transport=t.name,i(),o('probe transport "%s" failed because of error: %s',e,n),r.emit("upgradeError",s)}function l(){a("transport closed")}function c(){a("socket closed")}function h(e){t&&e.name!==t.name&&(o('"%s" works - aborting "%s"',e.name,t.name),i())}function d(){t.removeListener("open",s),t.removeListener("error",a),t.removeListener("close",l),r.removeListener("close",c),r.removeListener("upgrading",h)}u.priorWebsocketSuccess=!1,t.once("open",s),t.once("error",a),t.once("close",l),this.once("close",c),this.once("upgrading",h),t.open()},u.prototype.onOpen=function(){if(o("socket open"),this.readyState="open",u.priorWebsocketSuccess="websocket"===this.transport.name,this.emit("open"),this.flush(),"open"===this.readyState&&this.upgrade&&this.transport.pause){o("starting upgrade probes");for(var e=0,t=this.upgrades.length;e0);return t}function u(){var e=c(+new Date);return e!==r?(a=0,r=e):e+"."+c(a++)}for(;l0&&e.jitter<=1?e.jitter:0,this.attempts=0}e.exports=n,n.prototype.duration=function(){var e=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var t=Math.random(),n=Math.floor(t*this.jitter*e);e=0==(1&Math.floor(10*t))?e-n:e+n}return 0|Math.min(e,this.max)},n.prototype.reset=function(){this.attempts=0},n.prototype.setMin=function(e){this.ms=e},n.prototype.setMax=function(e){this.max=e},n.prototype.setJitter=function(e){this.jitter=e}},CIKq:function(e,t,n){var r,s,o=n("Gbct"),i=n("Wm4p"),a=n("TypT"),l=n("Yvos"),c=n("Aplp"),u=n("NOtv")("engine.io-client:websocket");if("undefined"!=typeof WebSocket)r=WebSocket;else if("undefined"!=typeof self)r=self.WebSocket||self.MozWebSocket;else try{s=n(1)}catch(p){}var h=r||s;function d(e){e&&e.forceBase64&&(this.supportsBinary=!1),this.perMessageDeflate=e.perMessageDeflate,this.usingBrowserWebSocket=r&&!e.forceNode,this.protocols=e.protocols,this.usingBrowserWebSocket||(h=s),o.call(this,e)}e.exports=d,l(d,o),d.prototype.name="websocket",d.prototype.supportsBinary=!0,d.prototype.doOpen=function(){if(this.check()){var e=this.uri(),t=this.protocols,n={agent:this.agent,perMessageDeflate:this.perMessageDeflate};n.pfx=this.pfx,n.key=this.key,n.passphrase=this.passphrase,n.cert=this.cert,n.ca=this.ca,n.ciphers=this.ciphers,n.rejectUnauthorized=this.rejectUnauthorized,this.extraHeaders&&(n.headers=this.extraHeaders),this.localAddress&&(n.localAddress=this.localAddress);try{this.ws=this.usingBrowserWebSocket&&!this.isReactNative?t?new h(e,t):new h(e):new h(e,t,n)}catch(r){return this.emit("error",r)}void 0===this.ws.binaryType&&(this.supportsBinary=!1),this.ws.supports&&this.ws.supports.binary?(this.supportsBinary=!0,this.ws.binaryType="nodebuffer"):this.ws.binaryType="arraybuffer",this.addEventListeners()}},d.prototype.addEventListeners=function(){var e=this;this.ws.onopen=function(){e.onOpen()},this.ws.onclose=function(){e.onClose()},this.ws.onmessage=function(t){e.onData(t.data)},this.ws.onerror=function(t){e.onError("websocket error",t)}},d.prototype.write=function(e){var t=this;this.writable=!1;for(var n=e.length,r=0,s=n;r')}catch(e){(r=document.createElement("iframe")).name=n.iframeId,r.src="javascript:0"}r.id=n.iframeId,n.form.appendChild(r),n.iframe=r}this.form.action=this.uri(),u(),e=e.replace(a,"\\\n"),this.area.value=e.replace(i,"\\n");try{this.form.submit()}catch(h){}this.iframe.attachEvent?this.iframe.onreadystatechange=function(){"complete"===n.iframe.readyState&&c()}:this.iframe.onload=c}},FGiv:function(e,t){var n=1e3,r=6e4,s=36e5,o=24*s;function i(e,t,n){if(!(e0)return function(e){if(!((e=String(e)).length>100)){var t=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(e);if(t){var i=parseFloat(t[1]);switch((t[2]||"ms").toLowerCase()){case"years":case"year":case"yrs":case"yr":case"y":return 315576e5*i;case"days":case"day":case"d":return i*o;case"hours":case"hour":case"hrs":case"hr":case"h":return i*s;case"minutes":case"minute":case"mins":case"min":case"m":return i*r;case"seconds":case"second":case"secs":case"sec":case"s":return i*n;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return i;default:return}}}}(e);if("number"===l&&!1===isNaN(e))return t.long?i(a=e,o,"day")||i(a,s,"hour")||i(a,r,"minute")||i(a,n,"second")||a+" ms":function(e){return e>=o?Math.round(e/o)+"d":e>=s?Math.round(e/s)+"h":e>=r?Math.round(e/r)+"m":e>=n?Math.round(e/n)+"s":e+"ms"}(e);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(e))}},Gbct:function(e,t,n){var r=n("Wm4p"),s=n("cpc2");function o(e){this.path=e.path,this.hostname=e.hostname,this.port=e.port,this.secure=e.secure,this.query=e.query,this.timestampParam=e.timestampParam,this.timestampRequests=e.timestampRequests,this.readyState="",this.agent=e.agent||!1,this.socket=e.socket,this.enablesXDR=e.enablesXDR,this.pfx=e.pfx,this.key=e.key,this.passphrase=e.passphrase,this.cert=e.cert,this.ca=e.ca,this.ciphers=e.ciphers,this.rejectUnauthorized=e.rejectUnauthorized,this.forceNode=e.forceNode,this.isReactNative=e.isReactNative,this.extraHeaders=e.extraHeaders,this.localAddress=e.localAddress}e.exports=o,s(o.prototype),o.prototype.onError=function(e,t){var n=new Error(e);return n.type="TransportError",n.description=t,this.emit("error",n),this},o.prototype.open=function(){return"closed"!==this.readyState&&""!==this.readyState||(this.readyState="opening",this.doOpen()),this},o.prototype.close=function(){return"opening"!==this.readyState&&"open"!==this.readyState||(this.doClose(),this.onClose()),this},o.prototype.send=function(e){if("open"!==this.readyState)throw new Error("Transport not open");this.write(e)},o.prototype.onOpen=function(){this.readyState="open",this.writable=!0,this.emit("open")},o.prototype.onData=function(e){var t=r.decodePacket(e,this.socket.binaryType);this.onPacket(t)},o.prototype.onPacket=function(e){this.emit("packet",e)},o.prototype.onClose=function(){this.readyState="closed",this.emit("close")}},KFGy:function(e,t,n){var r=n("Vo14"),s=n("cpc2"),o=n("kSER"),i=n("2Dig"),a=n("QN7Q"),l=n("NOtv")("socket.io-client:socket"),c=n("TypT"),u=n("WLGk");e.exports=p;var h={connect:1,connect_error:1,connect_timeout:1,connecting:1,disconnect:1,error:1,reconnect:1,reconnect_attempt:1,reconnect_failed:1,reconnect_error:1,reconnecting:1,ping:1,pong:1},d=s.prototype.emit;function p(e,t,n){this.io=e,this.nsp=t,this.json=this,this.ids=0,this.acks={},this.receiveBuffer=[],this.sendBuffer=[],this.connected=!1,this.disconnected=!0,this.flags={},n&&n.query&&(this.query=n.query),this.io.autoConnect&&this.open()}s(p.prototype),p.prototype.subEvents=function(){if(!this.subs){var e=this.io;this.subs=[i(e,"open",a(this,"onopen")),i(e,"packet",a(this,"onpacket")),i(e,"close",a(this,"onclose"))]}},p.prototype.open=p.prototype.connect=function(){return this.connected?this:(this.subEvents(),this.io.open(),"open"===this.io.readyState&&this.onopen(),this.emit("connecting"),this)},p.prototype.send=function(){var e=o(arguments);return e.unshift("message"),this.emit.apply(this,e),this},p.prototype.emit=function(e){if(h.hasOwnProperty(e))return d.apply(this,arguments),this;var t=o(arguments),n={type:(void 0!==this.flags.binary?this.flags.binary:u(t))?r.BINARY_EVENT:r.EVENT,data:t,options:{}};return n.options.compress=!this.flags||!1!==this.flags.compress,"function"==typeof t[t.length-1]&&(l("emitting packet with ack id %d",this.ids),this.acks[this.ids]=t.pop(),n.id=this.ids++),this.connected?this.packet(n):this.sendBuffer.push(n),this.flags={},this},p.prototype.packet=function(e){e.nsp=this.nsp,this.io.packet(e)},p.prototype.onopen=function(){if(l("transport is open - connecting"),"/"!==this.nsp)if(this.query){var e="object"==typeof this.query?c.encode(this.query):this.query;l("sending connect packet with query %s",e),this.packet({type:r.CONNECT,query:e})}else this.packet({type:r.CONNECT})},p.prototype.onclose=function(e){l("close (%s)",e),this.connected=!1,this.disconnected=!0,delete this.id,this.emit("disconnect",e)},p.prototype.onpacket=function(e){if(e.nsp===this.nsp||e.type===r.ERROR&&"/"===e.nsp)switch(e.type){case r.CONNECT:this.onconnect();break;case r.EVENT:case r.BINARY_EVENT:this.onevent(e);break;case r.ACK:case r.BINARY_ACK:this.onack(e);break;case r.DISCONNECT:this.ondisconnect();break;case r.ERROR:this.emit("error",e.data)}},p.prototype.onevent=function(e){var t=e.data||[];l("emitting event %j",t),null!=e.id&&(l("attaching ack callback to event"),t.push(this.ack(e.id))),this.connected?d.apply(this,t):this.receiveBuffer.push(t)},p.prototype.ack=function(e){var t=this,n=!1;return function(){if(!n){n=!0;var s=o(arguments);l("sending ack %j",s),t.packet({type:u(s)?r.BINARY_ACK:r.ACK,id:e,data:s})}}},p.prototype.onack=function(e){var t=this.acks[e.id];"function"==typeof t?(l("calling ack %s with %j",e.id,e.data),t.apply(this,e.data),delete this.acks[e.id]):l("bad ack %s",e.id)},p.prototype.onconnect=function(){this.connected=!0,this.disconnected=!1,this.emit("connect"),this.emitBuffered()},p.prototype.emitBuffered=function(){var e;for(e=0;e=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))},t.storage="undefined"!=typeof chrome&&void 0!==chrome.storage?chrome.storage.local:function(){try{return window.localStorage}catch(e){}}(),t.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],t.formatters.j=function(e){try{return JSON.stringify(e)}catch(t){return"[UnexpectedJSONParseError]: "+t.message}},t.enable(r())},QN7Q:function(e,t){var n=[].slice;e.exports=function(e,t){if("string"==typeof t&&(t=e[t]),"function"!=typeof t)throw new Error("bind() requires a function");var r=n.call(arguments,2);return function(){return t.apply(e,r.concat(n.call(arguments)))}}},TypT:function(e,t){t.encode=function(e){var t="";for(var n in e)e.hasOwnProperty(n)&&(t.length&&(t+="&"),t+=encodeURIComponent(n)+"="+encodeURIComponent(e[n]));return t},t.decode=function(e){for(var t={},n=e.split("&"),r=0,s=n.length;r1?{type:p[s],data:e.substring(1)}:{type:p[s]}:f}s=new Uint8Array(e)[0];var o=i(e,1);return g&&"blob"===n&&(o=new g([o])),{type:p[s],data:o}},t.decodeBase64Packet=function(e,t){var n=p[e.charAt(0)];if(!r)return{type:n,data:{base64:!0,data:e.substr(1)}};var s=r.decode(e.substr(1));return"blob"===t&&g&&(s=new g([s])),{type:n,data:s}},t.encodePayload=function(e,n,r){"function"==typeof n&&(r=n,n=null);var s=o(e);return n&&s?g&&!h?t.encodePayloadAsBlob(e,r):t.encodePayloadAsArrayBuffer(e,r):e.length?void m(e,function(e,r){t.encodePacket(e,!!s&&n,!1,function(e){r(null,function(e){return e.length+":"+e}(e))})},function(e,t){return r(t.join(""))}):r("0:")},t.decodePayload=function(e,n,r){if("string"!=typeof e)return t.decodePayloadAsBinary(e,n,r);var s;if("function"==typeof n&&(r=n,n=null),""===e)return r(f,0,1);for(var o,i,a="",l=0,c=e.length;l0;){for(var a=new Uint8Array(s),l=0===a[0],c="",u=1;255!==a[u];u++){if(c.length>310)return r(f,0,1);c+=a[u]}s=i(s,2+c.length),c=parseInt(c);var h=i(s,0,c);if(l)try{h=String.fromCharCode.apply(null,new Uint8Array(h))}catch(g){var d=new Uint8Array(h);for(h="",u=0;u0&&!this.encoding){var e=this.packetBuffer.shift();this.packet(e)}},p.prototype.cleanup=function(){c("cleanup");for(var e=this.subs.length,t=0;t=this._reconnectionAttempts)c("reconnect failed"),this.backoff.reset(),this.emitAll("reconnect_failed"),this.reconnecting=!1;else{var t=this.backoff.duration();c("will wait %dms before reconnect attempt",t),this.reconnecting=!0;var n=setTimeout(function(){e.skipReconnect||(c("attempting reconnect"),e.emitAll("reconnect_attempt",e.backoff.attempts),e.emitAll("reconnecting",e.backoff.attempts),e.skipReconnect||e.open(function(t){t?(c("reconnect attempt error"),e.reconnecting=!1,e.reconnect(),e.emitAll("reconnect_error",t.data)):(c("reconnect success"),e.onreconnect())}))},t);this.subs.push({destroy:function(){clearTimeout(n)}})}},p.prototype.onreconnect=function(){var e=this.backoff.attempts;this.reconnecting=!1,this.backoff.reset(),this.updateSocketIds(),this.emitAll("reconnect",e)}},g5Dd:function(e,t){!function(){"use strict";for(var e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",n=new Uint8Array(256),r=0;r>2],o+=e[(3&r[n])<<4|r[n+1]>>4],o+=e[(15&r[n+1])<<2|r[n+2]>>6],o+=e[63&r[n+2]];return s%3==2?o=o.substring(0,o.length-1)+"=":s%3==1&&(o=o.substring(0,o.length-2)+"=="),o},t.decode=function(e){var t,r,s,o,i,a=.75*e.length,l=e.length,c=0;"="===e[e.length-1]&&(a--,"="===e[e.length-2]&&a--);var u=new ArrayBuffer(a),h=new Uint8Array(u);for(t=0;t>4,h[c++]=(15&s)<<4|o>>2,h[c++]=(3&o)<<6|63&i;return u}}()},gFX4:function(e,t,n){var r=n("zJ60"),s=n("Vo14"),o=n("eOtv"),i=n("NOtv")("socket.io-client");e.exports=t=l;var a=t.managers={};function l(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var n,s=r(e),l=s.source,c=s.id;return t.forceNew||t["force new connection"]||!1===t.multiplex||a[c]&&s.path in a[c].nsps?(i("ignoring socket cache for %s",l),n=o(l,t)):(a[c]||(i("new io instance for %s",l),a[c]=o(l,t)),n=a[c]),s.query&&!t.query&&(t.query=s.query),n.socket(s.path,t)}t.protocol=s.protocol,t.connect=l,t.Manager=n("eOtv"),t.Socket=n("KFGy")},kSER:function(e,t){e.exports=function(e,t){for(var n=[],r=(t=t||0)||0;r=55296&&t<=56319&&s=55296&&e<=57343){if(t)throw Error("Lone surrogate U+"+e.toString(16).toUpperCase()+" is not a scalar value");return!1}return!0}function l(e,t){return o(e>>t&63|128)}function c(e,t){if(0==(4294967168&e))return o(e);var n="";return 0==(4294965248&e)?n=o(e>>6&31|192):0==(4294901760&e)?(a(e,t)||(e=65533),n=o(e>>12&15|224),n+=l(e,6)):0==(4292870144&e)&&(n=o(e>>18&7|240),n+=l(e,12),n+=l(e,6)),n+o(63&e|128)}function u(){if(s>=r)throw Error("Invalid byte index");var e=255&n[s];if(s++,128==(192&e))return 63&e;throw Error("Invalid continuation byte")}function h(e){var t,o;if(s>r)throw Error("Invalid byte index");if(s==r)return!1;if(t=255&n[s],s++,0==(128&t))return t;if(192==(224&t)){if((o=(31&t)<<6|u())>=128)return o;throw Error("Invalid continuation byte")}if(224==(240&t)){if((o=(15&t)<<12|u()<<6|u())>=2048)return a(o,e)?o:65533;throw Error("Invalid continuation byte")}if(240==(248&t)&&(o=(7&t)<<18|u()<<12|u()<<6|u())>=65536&&o<=1114111)return o;throw Error("Invalid UTF-8 detected")}e.exports={version:"2.1.2",encode:function(e,t){for(var n=!1!==(t=t||{}).strict,r=i(e),s=r.length,o=-1,a="";++o65535&&(s+=o((t-=65536)>>>10&1023|55296),t=56320|1023&t),s+=o(t);return s}(c)}}},yeub:function(e,t){try{e.exports="undefined"!=typeof XMLHttpRequest&&"withCredentials"in new XMLHttpRequest}catch(n){e.exports=!1}},ypnn:function(e,t){e.exports=function(e,t,n){var r=e.byteLength;if(t=t||0,n=n||r,e.slice)return e.slice(t,n);if(t<0&&(t+=r),n<0&&(n+=r),n>r&&(n=r),t>=r||t>=n||0===r)return new ArrayBuffer(0);for(var s=new Uint8Array(e),o=new Uint8Array(n-t),i=t,a=0;i{throw e})}const a={closed:!0,next(e){},error(e){if(o.useDeprecatedSynchronousErrorHandling)throw e;i(e)},complete(){}},l=Array.isArray||(e=>e&&"number"==typeof e.length);function c(e){return null!==e&&"object"==typeof e}function u(e){return Error.call(this),this.message=e?`${e.length} errors occurred during unsubscription:\n${e.map((e,t)=>`${t+1}) ${e.toString()}`).join("\n ")}`:"",this.name="UnsubscriptionError",this.errors=e,this}u.prototype=Object.create(Error.prototype);const h=u,d=(()=>{class e{constructor(e){this.closed=!1,this._parent=null,this._parents=null,this._subscriptions=null,e&&(this._unsubscribe=e)}unsubscribe(){let e,t=!1;if(this.closed)return;let{_parent:n,_parents:s,_unsubscribe:o,_subscriptions:i}=this;this.closed=!0,this._parent=null,this._parents=null,this._subscriptions=null;let a=-1,u=s?s.length:0;for(;n;)n.remove(this),n=++ae.concat(t instanceof h?t.errors:t),[])}const f="function"==typeof Symbol?Symbol("rxSubscriber"):"@@rxSubscriber_"+Math.random();class g extends d{constructor(e,t,n){switch(super(),this.syncErrorValue=null,this.syncErrorThrown=!1,this.syncErrorThrowable=!1,this.isStopped=!1,arguments.length){case 0:this.destination=a;break;case 1:if(!e){this.destination=a;break}if("object"==typeof e){e instanceof g?(this.syncErrorThrowable=e.syncErrorThrowable,this.destination=e,e.add(this)):(this.syncErrorThrowable=!0,this.destination=new m(this,e));break}default:this.syncErrorThrowable=!0,this.destination=new m(this,e,t,n)}}[f](){return this}static create(e,t,n){const r=new g(e,t,n);return r.syncErrorThrowable=!1,r}next(e){this.isStopped||this._next(e)}error(e){this.isStopped||(this.isStopped=!0,this._error(e))}complete(){this.isStopped||(this.isStopped=!0,this._complete())}unsubscribe(){this.closed||(this.isStopped=!0,super.unsubscribe())}_next(e){this.destination.next(e)}_error(e){this.destination.error(e),this.unsubscribe()}_complete(){this.destination.complete(),this.unsubscribe()}_unsubscribeAndRecycle(){const{_parent:e,_parents:t}=this;return this._parent=null,this._parents=null,this.unsubscribe(),this.closed=!1,this.isStopped=!1,this._parent=e,this._parents=t,this}}class m extends g{constructor(e,t,n,s){let o;super(),this._parentSubscriber=e;let i=this;r(t)?o=t:t&&(o=t.next,n=t.error,s=t.complete,t!==a&&(r((i=Object.create(t)).unsubscribe)&&this.add(i.unsubscribe.bind(i)),i.unsubscribe=this.unsubscribe.bind(this))),this._context=i,this._next=o,this._error=n,this._complete=s}next(e){if(!this.isStopped&&this._next){const{_parentSubscriber:t}=this;o.useDeprecatedSynchronousErrorHandling&&t.syncErrorThrowable?this.__tryOrSetError(t,this._next,e)&&this.unsubscribe():this.__tryOrUnsub(this._next,e)}}error(e){if(!this.isStopped){const{_parentSubscriber:t}=this,{useDeprecatedSynchronousErrorHandling:n}=o;if(this._error)n&&t.syncErrorThrowable?(this.__tryOrSetError(t,this._error,e),this.unsubscribe()):(this.__tryOrUnsub(this._error,e),this.unsubscribe());else if(t.syncErrorThrowable)n?(t.syncErrorValue=e,t.syncErrorThrown=!0):i(e),this.unsubscribe();else{if(this.unsubscribe(),n)throw e;i(e)}}}complete(){if(!this.isStopped){const{_parentSubscriber:e}=this;if(this._complete){const t=()=>this._complete.call(this._context);o.useDeprecatedSynchronousErrorHandling&&e.syncErrorThrowable?(this.__tryOrSetError(e,t),this.unsubscribe()):(this.__tryOrUnsub(t),this.unsubscribe())}else this.unsubscribe()}}__tryOrUnsub(e,t){try{e.call(this._context,t)}catch(n){if(this.unsubscribe(),o.useDeprecatedSynchronousErrorHandling)throw n;i(n)}}__tryOrSetError(e,t,n){if(!o.useDeprecatedSynchronousErrorHandling)throw new Error("bad call");try{t.call(this._context,n)}catch(r){return o.useDeprecatedSynchronousErrorHandling?(e.syncErrorValue=r,e.syncErrorThrown=!0,!0):(i(r),!0)}return!1}_unsubscribe(){const{_parentSubscriber:e}=this;this._context=null,this._parentSubscriber=null,e.unsubscribe()}}const y="function"==typeof Symbol&&Symbol.observable||"@@observable";const v=(()=>{class e{constructor(e){this._isScalar=!1,e&&(this._subscribe=e)}lift(t){const n=new e;return n.source=this,n.operator=t,n}subscribe(e,t,n){const{operator:r}=this,s=function(e,t,n){if(e){if(e instanceof g)return e;if(e[f])return e[f]()}return e||t||n?new g(e,t,n):new g(a)}(e,t,n);if(s.add(r?r.call(s,this.source):this.source||o.useDeprecatedSynchronousErrorHandling&&!s.syncErrorThrowable?this._subscribe(s):this._trySubscribe(s)),o.useDeprecatedSynchronousErrorHandling&&s.syncErrorThrowable&&(s.syncErrorThrowable=!1,s.syncErrorThrown))throw s.syncErrorValue;return s}_trySubscribe(e){try{return this._subscribe(e)}catch(t){o.useDeprecatedSynchronousErrorHandling&&(e.syncErrorThrown=!0,e.syncErrorValue=t),function(e){for(;e;){const{closed:t,destination:n,isStopped:r}=e;if(t||r)return!1;e=n&&n instanceof g?n:null}return!0}(e)?e.error(t):console.warn(t)}}forEach(e,t){return new(t=_(t))((t,n)=>{let r;r=this.subscribe(t=>{try{e(t)}catch(s){n(s),r&&r.unsubscribe()}},n,t)})}_subscribe(e){const{source:t}=this;return t&&t.subscribe(e)}[y](){return this}pipe(...e){return 0===e.length?this:((t=e)?1===t.length?t[0]:function(e){return t.reduce((e,t)=>t(e),e)}:function(){})(this);var t}toPromise(e){return new(e=_(e))((e,t)=>{let n;this.subscribe(e=>n=e,e=>t(e),()=>e(n))})}}return e.create=(t=>new e(t)),e})();function _(e){if(e||(e=o.Promise||Promise),!e)throw new Error("no Promise impl found");return e}function b(){return Error.call(this),this.message="object unsubscribed",this.name="ObjectUnsubscribedError",this}b.prototype=Object.create(Error.prototype);const w=b;class C extends d{constructor(e,t){super(),this.subject=e,this.subscriber=t,this.closed=!1}unsubscribe(){if(this.closed)return;this.closed=!0;const e=this.subject,t=e.observers;if(this.subject=null,!t||0===t.length||e.isStopped||e.closed)return;const n=t.indexOf(this.subscriber);-1!==n&&t.splice(n,1)}}class x extends g{constructor(e){super(e),this.destination=e}}const k=(()=>{class e extends v{constructor(){super(),this.observers=[],this.closed=!1,this.isStopped=!1,this.hasError=!1,this.thrownError=null}[f](){return new x(this)}lift(e){const t=new E(this,this);return t.operator=e,t}next(e){if(this.closed)throw new w;if(!this.isStopped){const{observers:t}=this,n=t.length,r=t.slice();for(let s=0;snew E(e,t)),e})();class E extends k{constructor(e,t){super(),this.destination=e,this.source=t}next(e){const{destination:t}=this;t&&t.next&&t.next(e)}error(e){const{destination:t}=this;t&&t.error&&this.destination.error(e)}complete(){const{destination:e}=this;e&&e.complete&&this.destination.complete()}_subscribe(e){const{source:t}=this;return t?this.source.subscribe(e):d.EMPTY}}class A extends g{constructor(e,t,n){super(),this.parent=e,this.outerValue=t,this.outerIndex=n,this.index=0}_next(e){this.parent.notifyNext(this.outerValue,e,this.outerIndex,this.index++,this)}_error(e){this.parent.notifyError(e,this),this.unsubscribe()}_complete(){this.parent.notifyComplete(this),this.unsubscribe()}}const S=e=>t=>{for(let n=0,r=e.length;nt=>(e.then(e=>{t.closed||(t.next(e),t.complete())},e=>t.error(e)).then(null,i),t);function N(){return"function"==typeof Symbol&&Symbol.iterator?Symbol.iterator:"@@iterator"}const I=N(),O=e=>t=>{const n=e[I]();for(;;){const e=n.next();if(e.done){t.complete();break}if(t.next(e.value),t.closed)break}return"function"==typeof n.return&&t.add(()=>{n.return&&n.return()}),t},M=e=>t=>{const n=e[y]();if("function"!=typeof n.subscribe)throw new TypeError("Provided object does not correctly implement Symbol.observable");return n.subscribe(t)},D=e=>e&&"number"==typeof e.length&&"function"!=typeof e;function V(e){return!!e&&"function"!=typeof e.subscribe&&"function"==typeof e.then}const P=e=>{if(e instanceof v)return t=>e._isScalar?(t.next(e.value),void t.complete()):e.subscribe(t);if(e&&"function"==typeof e[y])return M(e);if(D(e))return S(e);if(V(e))return T(e);if(e&&"function"==typeof e[I])return O(e);{const t=c(e)?"an invalid object":`'${e}'`;throw new TypeError(`You provided ${t} where a stream was expected.`+" You can provide an Observable, Promise, Array, or Iterable.")}};function R(e,t,n,r,s=new A(e,n,r)){if(!s.closed)return P(t)(s)}class F extends g{notifyNext(e,t,n,r,s){this.destination.next(t)}notifyError(e,t){this.destination.error(e)}notifyComplete(e){this.destination.complete()}}function B(e,t){return function(n){if("function"!=typeof e)throw new TypeError("argument is not a function. Are you looking for `mapTo()`?");return n.lift(new j(e,t))}}class j{constructor(e,t){this.project=e,this.thisArg=t}call(e,t){return t.subscribe(new L(e,this.project,this.thisArg))}}class L extends g{constructor(e,t,n){super(e),this.project=t,this.count=0,this.thisArg=n||this}_next(e){let t;try{t=this.project.call(this.thisArg,e,this.count++)}catch(n){return void this.destination.error(n)}this.destination.next(t)}}function H(e,t){return new v(t?n=>{const r=new d;let s=0;return r.add(t.schedule(function(){s!==e.length?(n.next(e[s++]),n.closed||r.add(this.schedule())):n.complete()})),r}:S(e))}function U(e,t){if(!t)return e instanceof v?e:new v(P(e));if(null!=e){if(function(e){return e&&"function"==typeof e[y]}(e))return function(e,t){return new v(t?n=>{const r=new d;return r.add(t.schedule(()=>{const s=e[y]();r.add(s.subscribe({next(e){r.add(t.schedule(()=>n.next(e)))},error(e){r.add(t.schedule(()=>n.error(e)))},complete(){r.add(t.schedule(()=>n.complete()))}}))})),r}:M(e))}(e,t);if(V(e))return function(e,t){return new v(t?n=>{const r=new d;return r.add(t.schedule(()=>e.then(e=>{r.add(t.schedule(()=>{n.next(e),r.add(t.schedule(()=>n.complete()))}))},e=>{r.add(t.schedule(()=>n.error(e)))}))),r}:T(e))}(e,t);if(D(e))return H(e,t);if(function(e){return e&&"function"==typeof e[I]}(e)||"string"==typeof e)return function(e,t){if(!e)throw new Error("Iterable cannot be null");return new v(t?n=>{const r=new d;let s;return r.add(()=>{s&&"function"==typeof s.return&&s.return()}),r.add(t.schedule(()=>{s=e[I](),r.add(t.schedule(function(){if(n.closed)return;let e,t;try{const o=s.next();e=o.value,t=o.done}catch(r){return void n.error(r)}t?n.complete():(n.next(e),this.schedule())}))})),r}:O(e))}(e,t)}throw new TypeError((null!==e&&typeof e||e)+" is not observable")}class ${constructor(e,t=Number.POSITIVE_INFINITY){this.project=e,this.concurrent=t}call(e,t){return t.subscribe(new z(e,this.project,this.concurrent))}}class z extends F{constructor(e,t,n=Number.POSITIVE_INFINITY){super(e),this.project=t,this.concurrent=n,this.hasCompleted=!1,this.buffer=[],this.active=0,this.index=0}_next(e){this.active0?this._next(t.shift()):0===this.active&&this.hasCompleted&&this.destination.complete()}}function q(e){return e}function G(){return function(e){return e.lift(new W(e))}}class W{constructor(e){this.connectable=e}call(e,t){const{connectable:n}=this;n._refCount++;const r=new Z(e,n),s=t.subscribe(r);return r.closed||(r.connection=n.connect()),s}}class Z extends g{constructor(e,t){super(e),this.connectable=t}_unsubscribe(){const{connectable:e}=this;if(!e)return void(this.connection=null);this.connectable=null;const t=e._refCount;if(t<=0)return void(this.connection=null);if(e._refCount=t-1,t>1)return void(this.connection=null);const{connection:n}=this,r=e._connection;this.connection=null,!r||n&&r!==n||r.unsubscribe()}}const K=class extends v{constructor(e,t){super(),this.source=e,this.subjectFactory=t,this._refCount=0,this._isComplete=!1}_subscribe(e){return this.getSubject().subscribe(e)}getSubject(){const e=this._subject;return e&&!e.isStopped||(this._subject=this.subjectFactory()),this._subject}connect(){let e=this._connection;return e||(this._isComplete=!1,(e=this._connection=new d).add(this.source.subscribe(new Q(this.getSubject(),this))),e.closed?(this._connection=null,e=d.EMPTY):this._connection=e),e}refCount(){return G()(this)}}.prototype,Y={operator:{value:null},_refCount:{value:0,writable:!0},_subject:{value:null,writable:!0},_connection:{value:null,writable:!0},_subscribe:{value:K._subscribe},_isComplete:{value:K._isComplete,writable:!0},getSubject:{value:K.getSubject},connect:{value:K.connect},refCount:{value:K.refCount}};class Q extends x{constructor(e,t){super(e),this.connectable=t}_error(e){this._unsubscribe(),super._error(e)}_complete(){this.connectable._isComplete=!0,this._unsubscribe(),super._complete()}_unsubscribe(){const e=this.connectable;if(e){this.connectable=null;const t=e._connection;e._refCount=0,e._subject=null,e._connection=null,t&&t.unsubscribe()}}}function X(){return new k}const J="__parameters__";function ee(e,t,n){const r=function(e){return function(...t){if(e){const n=e(...t);for(const e in n)this[e]=n[e]}}}(t);function s(...e){if(this instanceof s)return r.apply(this,e),this;const t=new s(...e);return n.annotation=t,n;function n(e,n,r){const s=e.hasOwnProperty(J)?e[J]:Object.defineProperty(e,J,{value:[]})[J];for(;s.length<=r;)s.push(null);return(s[r]=s[r]||[]).push(t),e}}return n&&(s.prototype=Object.create(n.prototype)),s.prototype.ngMetadataName=e,s.annotationCls=s,s}const te=ee("Inject",e=>({token:e})),ne=ee("Optional"),re=ee("Self"),se=ee("SkipSelf");var oe=function(e){return e[e.Default=0]="Default",e[e.Host=1]="Host",e[e.Self=2]="Self",e[e.SkipSelf=4]="SkipSelf",e[e.Optional=8]="Optional",e}({});function ie(e){for(let t in e)if(e[t]===ie)return t;throw Error("Could not find renamed property on target object.")}function ae(e){return{providedIn:e.providedIn||null,factory:e.factory,value:void 0}}function le(e){return e&&e.hasOwnProperty(ce)?e[ce]:null}const ce=ie({ngInjectableDef:ie});function ue(e){if("string"==typeof e)return e;if(e instanceof Array)return"["+e.map(ue).join(", ")+"]";if(null==e)return""+e;if(e.overriddenName)return`${e.overriddenName}`;if(e.name)return`${e.name}`;const t=e.toString();if(null==t)return""+t;const n=t.indexOf("\n");return-1===n?t:t.substring(0,n)}const he=ie({__forward_ref__:ie});function de(e){return e.__forward_ref__=de,e.toString=function(){return ue(this())},e}function pe(e){const t=e;return"function"==typeof t&&t.hasOwnProperty(he)&&t.__forward_ref__===de?t():e}function fe(){const e="undefined"!=typeof globalThis&&globalThis,t="undefined"!=typeof window&&window,n="undefined"!=typeof self&&"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope&&self,r="undefined"!=typeof global&&global;return e||r||t||n}const ge=fe();let me,ye=void 0;function ve(e){const t=ye;return ye=e,t}class _e{constructor(e,t){this._desc=e,this.ngMetadataName="InjectionToken",this.ngInjectableDef=void 0,"number"==typeof t?this.__NG_ELEMENT_ID__=t:void 0!==t&&(this.ngInjectableDef=ae({providedIn:t.providedIn||"root",factory:t.factory}))}toString(){return`InjectionToken ${this._desc}`}}const be="__source",we=new Object,Ce=new _e("INJECTOR",-1);class xe{get(e,t=we){if(t===we){const t=new Error(`NullInjectorError: No provider for ${ue(e)}!`);throw t.name="NullInjectorError",t}return t}}const ke=(()=>{class e{static create(e,t){return Array.isArray(e)?new Ve(e,t):new Ve(e.providers,e.parent,e.name||null)}}return e.THROW_IF_NOT_FOUND=we,e.NULL=new xe,e.ngInjectableDef=ae({providedIn:"any",factory:()=>(function(e,t=oe.Default){return(me||function(e,t=oe.Default){if(void 0===ye)throw new Error("inject() must be called from an injection context");return null===ye?function(e,t,n){const r=le(e);if(r&&"root"==r.providedIn)return void 0===r.value?r.value=r.factory():r.value;if(n&oe.Optional)return null;throw new Error(`Injector: NOT_FOUND [${ue(e)}]`)}(e,0,t):ye.get(e,t&oe.Optional?null:void 0,t)})(e,t)})(Ce)}),e.__NG_ELEMENT_ID__=-1,e})(),Ee=function(e){return e},Ae=[],Se=Ee,Te=function(){return Array.prototype.slice.call(arguments)},Ne=ie({provide:String,useValue:ie}),Ie="ngTokenPath",Oe="ngTempTokenPath",Me=/\n/gm,De="\u0275";class Ve{constructor(e,t=ke.NULL,n=null){this.parent=t,this.source=n;const r=this._records=new Map;r.set(ke,{token:ke,fn:Ee,deps:Ae,value:this,useNew:!1}),r.set(Ce,{token:Ce,fn:Ee,deps:Ae,value:this,useNew:!1}),function e(t,n){if(n)if((n=pe(n))instanceof Array)for(let r=0;re.push(ue(n))),`StaticInjector[${e.join(", ")}]`}}function Pe(e){return Fe("Cannot mix multi providers and regular providers",e)}function Re(e,t,n,r=null){e=e&&"\n"===e.charAt(0)&&e.charAt(1)==De?e.substr(2):e;let s=ue(t);if(t instanceof Array)s=t.map(ue).join(" -> ");else if("object"==typeof t){let e=[];for(let n in t)if(t.hasOwnProperty(n)){let r=t[n];e.push(n+":"+("string"==typeof r?JSON.stringify(r):ue(r)))}s=`{${e.join(", ")}}`}return`${n}${r?"("+r+")":""}[${s}]: ${e.replace(Me,"\n ")}`}function Fe(e,t){return new Error(Re(e,t,"StaticInjectorError"))}const Be="ngDebugContext",je="ngOriginalError",Le="ngErrorLogger",He=function(){var e={Emulated:0,Native:1,None:2,ShadowDom:3};return e[e.Emulated]="Emulated",e[e.Native]="Native",e[e.None]="None",e[e.ShadowDom]="ShadowDom",e}(),Ue=(()=>("undefined"!=typeof requestAnimationFrame&&requestAnimationFrame||setTimeout).bind(ge))();function $e(e){return e[Be]}function ze(e){return e[je]}function qe(e,...t){e.error(...t)}class Ge{constructor(){this._console=console}handleError(e){const t=this._findOriginalError(e),n=this._findContext(e),r=function(e){return e[Le]||qe}(e);r(this._console,"ERROR",e),t&&r(this._console,"ORIGINAL ERROR",t),n&&r(this._console,"ERROR CONTEXT",n)}_findContext(e){return e?$e(e)?$e(e):this._findContext(ze(e)):null}_findOriginalError(e){let t=ze(e);for(;t&&ze(t);)t=ze(t);return t}}let We=!0,Ze=!1;function Ke(){return Ze=!0,We}class Ye{constructor(e){if(this.defaultDoc=e,this.inertDocument=this.defaultDoc.implementation.createHTMLDocument("sanitization-inert"),this.inertBodyElement=this.inertDocument.body,null==this.inertBodyElement){const e=this.inertDocument.createElement("html");this.inertDocument.appendChild(e),this.inertBodyElement=this.inertDocument.createElement("body"),e.appendChild(this.inertBodyElement)}this.inertBodyElement.innerHTML='',!this.inertBodyElement.querySelector||this.inertBodyElement.querySelector("svg")?(this.inertBodyElement.innerHTML='